/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function ArtifactController($scope, $routeParams, workspace, jolokia) {
        $scope.row = {
            groupId: $routeParams["group"] || "",
            artifactId: $routeParams["artifact"] || "",
            version: $routeParams["version"] || "",
            classifier: $routeParams["classifier"] || "",
            packaging: $routeParams["packaging"] || ""
        };
        var row = $scope.row;

        $scope.id = Maven.getName(row);

        Maven.addMavenFunctions($scope, workspace);

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateTableContents, 50);
        });

        $scope.$watch('workspace.selection', function () {
            updateTableContents();
        });

        function updateTableContents() {
            var mbean = Maven.getMavenIndexerMBean(workspace);

            // lets query the name and description of the GAV
            if (mbean) {
                jolokia.execute(mbean, "search", row.groupId, row.artifactId, row.version, row.packaging, row.classifier, "", onSuccess(render));
            } else {
                console.log("No MavenIndexerMBean!");
            }
        }

        function render(response) {
            if (response && response.length) {
                var first = response[0];
                row.name = first.name;
                row.description = first.description;
            }
            Core.$apply($scope);
        }
    }
    Maven.ArtifactController = ArtifactController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function DependenciesController($scope, $routeParams, $location, workspace, jolokia) {
        $scope.artifacts = [];
        $scope.group = $routeParams["group"] || "";
        $scope.artifact = $routeParams["artifact"] || "";
        $scope.version = $routeParams["version"] || "";
        $scope.classifier = $routeParams["classifier"] || "";
        $scope.packaging = $routeParams["packaging"] || "";

        $scope.dependencyTree = null;

        Maven.addMavenFunctions($scope, workspace);

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateTableContents, 50);
        });

        $scope.$watch('workspace.selection', function () {
            updateTableContents();
        });

        $scope.onSelectNode = function (node) {
            $scope.selected = node;
        };

        $scope.onRootNode = function (rootNode) {
            // process the rootNode
        };

        $scope.validSelection = function () {
            return $scope.selected && $scope.selected !== $scope.rootDependency;
        };

        $scope.viewDetails = function () {
            var dependency = Core.pathGet($scope.selected, ["dependency"]);
            var link = $scope.detailLink(dependency);
            if (link) {
                var path = Core.trimLeading(link, "#");
                console.log("going to view " + path);
                $location.path(path);
            }
        };

        function updateTableContents() {
            var mbean = Maven.getAetherMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "resolveJson(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)", $scope.group, $scope.artifact, $scope.version, $scope.packaging, $scope.classifier, onSuccess(render));
            } else {
                console.log("No AetherMBean!");
            }
        }

        function render(response) {
            if (response) {
                var json = JSON.parse(response);
                if (json) {
                    //console.log("Found json: " + JSON.stringify(json, null, "  "));
                    $scope.dependencyTree = new Folder("Dependencies");
                    $scope.dependencyActivations = [];
                    addChildren($scope.dependencyTree, json);
                    $scope.dependencyActivations.reverse();
                    $scope.rootDependency = $scope.dependencyTree.children[0];
                }
            }
            Core.$apply($scope);
        }

        function addChildren(folder, dependency) {
            var name = Maven.getName(dependency);
            var node = new Folder(name);
            node.key = name.replace(/\//g, '_');
            node["dependency"] = dependency;
            $scope.dependencyActivations.push(node.key);

            /*
            var imageUrl = Camel.getRouteNodeIcon(value);
            node.icon = imageUrl;
            //node.tooltip = tooltip;
            */
            folder.children.push(node);

            var children = dependency["children"];
            angular.forEach(children, function (child) {
                addChildren(node, child);
            });
        }
    }
    Maven.DependenciesController = DependenciesController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function PomXmlController($scope) {
        $scope.mavenPomXml = "\n" + "  <dependency>\n" + "    <groupId>" + orBlank($scope.row.groupId) + "</groupId>\n" + "    <artifactId>" + orBlank($scope.row.artifactId) + "</artifactId>\n" + "    <version>" + orBlank($scope.row.version) + "</version>\n" + "  </dependency>\n";

        function orBlank(text) {
            return text || "";
        }
    }
    Maven.PomXmlController = PomXmlController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function SearchController($scope, $location, workspace, jolokia) {
        var log = Logger.get("Maven");

        $scope.artifacts = [];
        $scope.selected = [];
        $scope.done = false;
        $scope.inProgress = false;
        $scope.form = {
            searchText: ""
        };
        $scope.search = "";
        $scope.searchForm = 'app/maven/html/searchForm.html';

        Maven.addMavenFunctions($scope, workspace);

        var columnDefs = [
            {
                field: 'groupId',
                displayName: 'Group'
            },
            {
                field: 'artifactId',
                displayName: 'Artifact',
                cellTemplate: '<div class="ngCellText" title="Name: {{row.entity.name}}">{{row.entity.artifactId}}</div>'
            },
            {
                field: 'version',
                displayName: 'Version',
                cellTemplate: '<div class="ngCellText" title="Name: {{row.entity.name}}"><a ng-href="{{detailLink(row.entity)}}">{{row.entity.version}}</a</div>'
            }
        ];

        $scope.gridOptions = {
            data: 'artifacts',
            displayFooter: true,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            columnDefs: columnDefs,
            rowDetailTemplateId: "artifactDetailTemplate",
            filterOptions: {
                filterText: 'search'
            }
        };

        $scope.hasAdvancedSearch = function (form) {
            return form.searchGroup || form.searchArtifact || form.searchVersion || form.searchPackaging || form.searchClassifier || form.searchClassName;
        };

        $scope.doSearch = function () {
            $scope.done = false;
            $scope.inProgress = true;
            $scope.artifacts = [];

            // ensure ui is updated with search in progress...
            setTimeout(function () {
                Core.$apply($scope);
            }, 50);

            var mbean = Maven.getMavenIndexerMBean(workspace);
            var form = $scope.form;
            if (mbean) {
                var searchText = form.searchText;
                var kind = form.artifactType;
                if (kind) {
                    if (kind === "className") {
                        log.debug("Search for: " + form.searchText + " className");
                        jolokia.execute(mbean, "searchClasses", searchText, onSuccess(render));
                    } else {
                        var paths = kind.split('/');
                        var packaging = paths[0];
                        var classifier = paths[1];
                        log.debug("Search for: " + form.searchText + " packaging " + packaging + " classifier " + classifier);
                        jolokia.execute(mbean, "searchTextAndPackaging", searchText, packaging, classifier, onSuccess(render));
                    }
                } else if (searchText) {
                    log.debug("Search text is: " + form.searchText);
                    jolokia.execute(mbean, "searchText", form.searchText, onSuccess(render));
                } else if ($scope.hasAdvancedSearch(form)) {
                    log.debug("Searching for " + form.searchGroup + "/" + form.searchArtifact + "/" + form.searchVersion + "/" + form.searchPackaging + "/" + form.searchClassifier + "/" + form.searchClassName);

                    jolokia.execute(mbean, "search", form.searchGroup || "", form.searchArtifact || "", form.searchVersion || "", form.searchPackaging || "", form.searchClassifier || "", form.searchClassName || "", onSuccess(render));
                }
            } else {
                notification("error", "Cannot find the Maven Indexer MBean!");
            }
        };

        // cap ui table at one thousand
        var RESPONSE_LIMIT = 1000;
        var SERVER_RESPONSE_LIMIT = (10 * RESPONSE_LIMIT) + 1;

        function render(response) {
            log.debug("Search done, preparing result.");
            $scope.done = true;
            $scope.inProgress = false;

            // let's limit the reponse to avoid blowing up
            // the browser until we start using a widget
            // that supports pagination
            if (response.length > RESPONSE_LIMIT) {
                var serverLimit = response.length === SERVER_RESPONSE_LIMIT;
                if (serverLimit) {
                    $scope.tooManyResponses = "This search returned more than " + (SERVER_RESPONSE_LIMIT - 1) + " artifacts, showing the first " + RESPONSE_LIMIT + ", please refine your search";
                } else {
                    $scope.tooManyResponses = "This search returned " + response.length + " artifacts, showing the first " + RESPONSE_LIMIT + ", please refine your search";
                }
            } else {
                $scope.tooManyResponses = "";
            }
            $scope.artifacts = response.first(RESPONSE_LIMIT);

            Core.$apply($scope);
        }
    }
    Maven.SearchController = SearchController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function VersionsController($scope, $routeParams, workspace, jolokia) {
        $scope.artifacts = [];
        $scope.group = $routeParams["group"] || "";
        $scope.artifact = $routeParams["artifact"] || "";
        $scope.version = "";
        $scope.classifier = $routeParams["classifier"] || "";
        $scope.packaging = $routeParams["packaging"] || "";

        var id = $scope.group + "/" + $scope.artifact;
        if ($scope.classifier) {
            id += "/" + $scope.classifier;
        }
        if ($scope.packaging) {
            id += "/" + $scope.packaging;
        }
        var columnTitle = id + " versions";

        var columnDefs = [
            {
                field: 'version',
                displayName: columnTitle,
                cellTemplate: '<div class="ngCellText"><a href="#/maven/artifact/{{row.entity.groupId}}/{{row.entity.artifactId}}/{{row.entity.version}}">{{row.entity.version}}</a></div>'
            }
        ];

        $scope.gridOptions = {
            data: 'artifacts',
            displayFooter: true,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            columnDefs: columnDefs,
            rowDetailTemplateId: "artifactDetailTemplate",
            sortInfo: { field: 'versionNumber', direction: 'DESC' },
            filterOptions: {
                filterText: 'search'
            }
        };

        Maven.addMavenFunctions($scope, workspace);

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateTableContents, 50);
        });

        $scope.$watch('workspace.selection', function () {
            updateTableContents();
        });

        function updateTableContents() {
            var mbean = Maven.getMavenIndexerMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "versionComplete", $scope.group, $scope.artifact, $scope.version, $scope.packaging, $scope.classifier, onSuccess(render));
            } else {
                console.log("No MavenIndexerMBean!");
            }
        }

        function render(response) {
            $scope.artifacts = [];
            angular.forEach(response, function (version) {
                var versionNumberArray = Core.parseVersionNumbers(version);
                var versionNumber = 0;
                for (var i = 0; i <= 4; i++) {
                    var num = (i >= versionNumberArray.length) ? 0 : versionNumberArray[i];
                    versionNumber *= 1000;
                    versionNumber += num;
                }

                $scope.artifacts.push({
                    groupId: $scope.group,
                    artifactId: $scope.artifact,
                    packaging: $scope.packaging,
                    classifier: $scope.classifier,
                    version: version,
                    versionNumber: versionNumber
                });
            });
            Core.$apply($scope);
        }
    }
    Maven.VersionsController = VersionsController;
})(Maven || (Maven = {}));
/**
* @module Maven
* @main Maven
*/
var Maven;
(function (Maven) {
    var pluginName = 'maven';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'datatable', 'tree', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/maven/search', { templateUrl: 'app/maven/html/search.html' }).when('/maven/advancedSearch', { templateUrl: 'app/maven/html/advancedSearch.html' }).when('/maven/artifact/:group/:artifact/:version/:classifier/:packaging', { templateUrl: 'app/maven/html/artifact.html' }).when('/maven/artifact/:group/:artifact/:version/:classifier', { templateUrl: 'app/maven/html/artifact.html' }).when('/maven/artifact/:group/:artifact/:version', { templateUrl: 'app/maven/html/artifact.html' }).when('/maven/dependencies/:group/:artifact/:version/:classifier/:packaging', { templateUrl: 'app/maven/html/dependencies.html' }).when('/maven/dependencies/:group/:artifact/:version/:classifier', { templateUrl: 'app/maven/html/dependencies.html' }).when('/maven/dependencies/:group/:artifact/:version', { templateUrl: 'app/maven/html/dependencies.html' }).when('/maven/versions/:group/:artifact/:classifier/:packaging', { templateUrl: 'app/maven/html/versions.html' }).when('/maven/view/:group/:artifact/:version/:classifier/:packaging', { templateUrl: 'app/maven/html/view.html' }).when('/maven/test', { templateUrl: 'app/maven/html/test.html' });
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['maven'] = "app/maven/html/layoutMaven.html";

        workspace.topLevelTabs.push({
            id: "maven",
            content: "Maven",
            title: "Search maven repositories for artifacts",
            isValid: function (workspace) {
                return Maven.getMavenIndexerMBean(workspace);
            },
            href: function () {
                return "#/maven/search";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("/maven");
            }
        });

        helpRegistry.addUserDoc('maven', 'app/maven/doc/help.md', function () {
            return Maven.getMavenIndexerMBean(workspace) !== null;
        });
        helpRegistry.addDevDoc("maven", 'app/maven/doc/developer.md');
    });

    hawtioPluginLoader.addModule(pluginName);
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function ViewController($scope, $location, workspace, jolokia) {
        $scope.$watch('workspace.tree', function () {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            setTimeout(loadData, 50);
        });

        function loadData() {
        }
    }
    Maven.ViewController = ViewController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    function TestController($scope, workspace, jolokia, $q, $templateCache) {
        $scope.html = "text/html";

        $scope.someUri = '';
        $scope.uriParts = [];
        $scope.mavenCompletion = $templateCache.get("mavenCompletionTemplate");

        $scope.$watch('someUri', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.uriParts = newValue.split("/");
            }
        });

        $scope.$watch('uriParts', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (newValue.length === 1 && newValue.length < oldValue.length) {
                    if (oldValue.last() !== '' && newValue.first().has(oldValue.last())) {
                        var merged = oldValue.first(oldValue.length - 1).include(newValue.first());
                        $scope.someUri = merged.join('/');
                    }
                }
            }
        }, true);

        $scope.doCompletionMaven = function (something) {
            return Maven.completeMavenUri($q, $scope, workspace, jolokia, something);
        };
    }
    Maven.TestController = TestController;
})(Maven || (Maven = {}));
/**
* @module Maven
*/
var Maven;
(function (Maven) {
    Maven.log = Logger.get("Maven");

    /**
    * Returns the maven indexer mbean (from the hawtio-maven-indexer library)
    * @method getMavenIndexerMBean
    * @for Maven
    * @param {Core.Workspace} workspace
    * @return {String}
    */
    function getMavenIndexerMBean(workspace) {
        if (workspace) {
            var mavenStuff = workspace.mbeanTypesToDomain["Indexer"] || {};
            var object = mavenStuff["hawtio"] || {};
            return object.objectName;
        } else
            return null;
    }
    Maven.getMavenIndexerMBean = getMavenIndexerMBean;

    function getAetherMBean(workspace) {
        if (workspace) {
            var mavenStuff = workspace.mbeanTypesToDomain["AetherFacade"] || {};
            var object = mavenStuff["hawtio"] || {};
            return object.objectName;
        } else
            return null;
    }
    Maven.getAetherMBean = getAetherMBean;

    function mavenLink(url) {
        var path = null;
        if (url) {
            if (url.startsWith("mvn:")) {
                path = url.substring(4);
            } else {
                var idx = url.indexOf(":mvn:");
                if (idx > 0) {
                    path = url.substring(idx + 5);
                }
            }
        }
        return path ? "#/maven/artifact/" + path : null;
    }
    Maven.mavenLink = mavenLink;

    function getName(row) {
        var id = (row.group || row.groupId) + "/" + (row.artifact || row.artifactId);
        if (row.version) {
            id += "/" + row.version;
        }
        if (row.classifier) {
            id += "/" + row.classifier;
        }
        if (row.packaging) {
            id += "/" + row.packaging;
        }
        return id;
    }
    Maven.getName = getName;

    function completeMavenUri($q, $scope, workspace, jolokia, query) {
        var mbean = getMavenIndexerMBean(workspace);
        if (!angular.isDefined(mbean)) {
            return $q.when([]);
        }

        var parts = query.split('/');
        if (parts.length === 1) {
            // still searching the groupId
            return Maven.completeGroupId(mbean, $q, $scope, workspace, jolokia, query, null, null);
        }
        if (parts.length === 2) {
            // have the groupId, guess we're looking for the artifactId
            return Maven.completeArtifactId(mbean, $q, $scope, workspace, jolokia, parts[0], parts[1], null, null);
        }
        if (parts.length === 3) {
            // guess we're searching for the version
            return Maven.completeVersion(mbean, $q, $scope, workspace, jolokia, parts[0], parts[1], parts[2], null, null);
        }

        return $q.when([]);
    }
    Maven.completeMavenUri = completeMavenUri;

    function completeVersion(mbean, $q, $scope, workspace, jolokia, groupId, artifactId, partial, packaging, classifier) {
        /*
        if (partial.length < 5) {
        return $q.when([]);
        }
        */
        var deferred = $q.defer();

        jolokia.request({
            type: 'exec',
            mbean: mbean,
            operation: 'versionComplete(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)',
            arguments: [groupId, artifactId, partial, packaging, classifier]
        }, {
            method: 'POST',
            success: function (response) {
                $scope.$apply(function () {
                    deferred.resolve(response.value.sortBy().first(15));
                });
            },
            error: function (response) {
                $scope.$apply(function () {
                    console.log("got back an error: ", response);
                    deferred.reject();
                });
            }
        });

        return deferred.promise;
    }
    Maven.completeVersion = completeVersion;

    function completeArtifactId(mbean, $q, $scope, workspace, jolokia, groupId, partial, packaging, classifier) {
        var deferred = $q.defer();

        jolokia.request({
            type: 'exec',
            mbean: mbean,
            operation: 'artifactIdComplete(java.lang.String, java.lang.String, java.lang.String, java.lang.String)',
            arguments: [groupId, partial, packaging, classifier]
        }, {
            method: 'POST',
            success: function (response) {
                $scope.$apply(function () {
                    deferred.resolve(response.value.sortBy().first(15));
                });
            },
            error: function (response) {
                $scope.$apply(function () {
                    console.log("got back an error: ", response);
                    deferred.reject();
                });
            }
        });

        return deferred.promise;
    }
    Maven.completeArtifactId = completeArtifactId;

    function completeGroupId(mbean, $q, $scope, workspace, jolokia, partial, packaging, classifier) {
        // let's go easy on the indexer
        if (partial.length < 5) {
            return $q.when([]);
        }

        var deferred = $q.defer();

        jolokia.request({
            type: 'exec',
            mbean: mbean,
            operation: 'groupIdComplete(java.lang.String, java.lang.String, java.lang.String)',
            arguments: [partial, packaging, classifier]
        }, {
            method: 'POST',
            success: function (response) {
                $scope.$apply(function () {
                    deferred.resolve(response.value.sortBy().first(15));
                });
            },
            error: function (response) {
                console.log("got back an error: ", response);
                $scope.$apply(function () {
                    deferred.reject();
                });
            }
        });

        return deferred.promise;
    }
    Maven.completeGroupId = completeGroupId;

    function addMavenFunctions($scope, workspace) {
        $scope.detailLink = function (row) {
            var group = row.groupId;
            var artifact = row.artifactId;
            var version = row.version || "";
            var classifier = row.classifier || "";
            var packaging = row.packaging || "";
            if (group && artifact) {
                return "#/maven/artifact/" + group + "/" + artifact + "/" + version + "/" + classifier + "/" + packaging;
            }
            return "";
        };

        $scope.javadocLink = function (row) {
            var group = row.groupId;
            var artifact = row.artifactId;
            var version = row.version;
            if (group && artifact && version) {
                return "javadoc/" + group + ":" + artifact + ":" + version + "/";
            }
            return "";
        };

        $scope.versionsLink = function (row) {
            var group = row.groupId;
            var artifact = row.artifactId;
            var classifier = row.classifier || "";
            var packaging = row.packaging || "";
            if (group && artifact) {
                return "#/maven/versions/" + group + "/" + artifact + "/" + classifier + "/" + packaging;
            }
            return "";
        };

        $scope.dependenciesLink = function (row) {
            var group = row.groupId;
            var artifact = row.artifactId;
            var classifier = row.classifier || "";
            var packaging = row.packaging || "";
            var version = row.version;
            if (group && artifact) {
                return "#/maven/dependencies/" + group + "/" + artifact + "/" + version + "/" + classifier + "/" + packaging;
            }
            return "";
        };

        $scope.hasDependencyMBean = function () {
            var mbean = Maven.getAetherMBean(workspace);
            return angular.isDefined(mbean);
        };

        $scope.sourceLink = function (row) {
            var group = row.groupId;
            var artifact = row.artifactId;
            var version = row.version;
            if (group && artifact && version) {
                return "#/source/index/" + group + ":" + artifact + ":" + version + "/";
            }
            return "";
        };
    }
    Maven.addMavenFunctions = addMavenFunctions;
})(Maven || (Maven = {}));
var Apollo;
(function (Apollo) {
    function VirtualHostController($scope, $http, $location, localStorage, workspace) {
        $scope.virtual_host = {};
        $scope.init = function (virtual_host_name) {
            $scope.ajax("GET", "/broker/virtual-hosts/" + virtual_host_name, function (host) {
                $scope.virtual_host = host;
            });
        };
    }
    Apollo.VirtualHostController = VirtualHostController;
})(Apollo || (Apollo = {}));
/**
* @module Apollo
* @main Apollo
*/
var Apollo;
(function (Apollo) {
    var pluginName = 'apollo';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/apollo', { templateUrl: 'app/apollo/html/layout-apollo.html' });
        //otherwise({templateUrl: 'app/apollo/html/layout-apollo.html'})
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['apollo'] = "app/apollo/html/layout-apollo.html";
        helpRegistry.addUserDoc('apollo', 'app/apollo/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("org.apache.apollo");
        });

        workspace.topLevelTabs.push({
            id: "apollo",
            content: "Apollo",
            title: "Manage your Apollo Broker",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("org.apache.apollo");
            },
            href: function () {
                return '#/apollo/virtual-hosts';
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("apollo");
            }
        });
    });
    hawtioPluginLoader.addModule(pluginName);
})(Apollo || (Apollo = {}));
var Apollo;
(function (Apollo) {
    function ApolloController($scope, $http, $location, localStorage, workspace) {
        var jolokia = workspace.jolokia;
        $scope.broker = {};
        $scope.online = true;
        $scope.route = function () {
            return $location.path();
        };
        $scope.apollo = {
            version: jolokia.getAttribute('org.apache.apollo:type=broker,name="default"', "Version", onSuccess(null)),
            url: jolokia.getAttribute('org.apache.apollo:type=broker,name="default"', "WebAdminUrl", onSuccess(null))
        };

        var default_error_handler = function (data, status, headers, config) {
            if (status === 401) {
                alert("Action not authorized.");
            } else {
                alert("Error: " + status);
            }
        };

        $scope.ajax = function (type, path, success, error, data, binary_options) {
            if (!error) {
                error = default_error_handler;
            }
            var username = "admin";
            var password = "password";

            var ajax_options = {
                method: type,
                url: $scope.apollo.url + "/api/json" + path,
                headers: {
                    AuthPrompt: 'false',
                    Accept: "application/json",
                    ContentType: "application/json",
                    Authorization: Core.getBasicAuthHeader(username, password)
                },
                cache: false,
                data: null
            };
            if (binary_options) {
                ajax_options.headers["Accept"] = binary_options.Accept || "application/octet-stream";
                ajax_options.headers["ContentType"] || "application/octet-stream";
                ajax_options.data = binary_options.data;
            }

            return $http(ajax_options).success(function (data, status, headers, config) {
                $scope.online = true;
                if (success) {
                    success(data, status, headers, config);
                }
            }).error(function (data, status, headers, config) {
                if (status === 0) {
                    $scope.online = false;
                } else {
                    $scope.online = true;
                    error(data, status, headers, config);
                }
            });
        };

        var reload = function () {
            if ($scope.apollo.url) {
                $scope.ajax("GET", "/broker", function (broker) {
                    $scope.broker = broker;
                    if ($scope.apollo.selected_virtual_host === undefined) {
                        $scope.apollo.selected_virtual_host = broker.virtual_hosts[0];
                    }
                }, function (error) {
                    alert("fail:" + error);
                });
            } else {
                $scope.broker = {};
            }
        };

        var schedule_refresh = function () {
        };
        schedule_refresh = function () {
            setTimeout(function () {
                reload();
                schedule_refresh();
            }, 1000);
        };
        schedule_refresh();

        $scope.$watch('apollo.url', reload);
        $scope.$watch('online', function () {
            // alert("online: "+$scope.online)
        });
    }
    Apollo.ApolloController = ApolloController;
})(Apollo || (Apollo = {}));
/**
* @module Jetty
*/
var Jetty;
(function (Jetty) {
    function iconClass(state) {
        if (state) {
            switch (state.toString().toLowerCase()) {
                case 'started':
                    return "green icon-play-circle";
                case 'true':
                    return "green icon-play-circle";
            }
        }
        return "orange icon-off";
    }
    Jetty.iconClass = iconClass;

    /**
    * Returns true if the state of the item begins with the given state - or one of the given states
    * @method isState
    * @for Jetty
    * @param {any} item the item which has a State
    * @param {any} state a value or an array of states
    * @return {Boolean}
    */
    function isState(item, state) {
        var value = (item.state || "").toLowerCase();
        if (angular.isArray(state)) {
            return state.any(function (stateText) {
                return value.startsWith(stateText);
            });
        } else {
            return value.startsWith(state);
        }
    }
    Jetty.isState = isState;
})(Jetty || (Jetty = {}));
/**
* @module Jetty
*/
var Jetty;
(function (Jetty) {
    function JettyController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}">' + '<i class="{{row.getProperty(col.field) | jettyIconClass}}"></i>' + '</div>';
        var urlTemplate = '<div class="ngCellText" title="{{row.getProperty(col.field)}}">' + '<a ng-href="{{row.getProperty(col.field)}}" target="_blank">{{row.getProperty(col.field)}}</a>' + '</div>';

        $scope.uninstallDialog = new UI.Dialog();

        $scope.httpPort;
        $scope.httpScheme = "http";

        $scope.webapps = [];
        $scope.selected = [];

        var columnDefs = [
            {
                field: 'state',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'displayName',
                displayName: 'Name',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'contextPath',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'url',
                displayName: 'Url',
                cellTemplate: urlTemplate,
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'webapps',
            displayFooter: true,
            selectedItems: $scope.selected,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            },
            title: "Web applications"
        };

        // function to control the web applications
        $scope.controlWebApps = function (op) {
            // grab id of mbean names to control
            var mbeanNames = $scope.selected.map(function (b) {
                return b.mbean;
            });
            if (!angular.isArray(mbeanNames)) {
                mbeanNames = [mbeanNames];
            }

            // execute operation on each mbean
            var lastIndex = (mbeanNames.length || 1) - 1;
            angular.forEach(mbeanNames, function (mbean, idx) {
                var onResponse = (idx >= lastIndex) ? $scope.onLastResponse : $scope.onResponse;
                jolokia.request({
                    type: 'exec',
                    mbean: mbean,
                    operation: op,
                    arguments: null
                }, onSuccess(onResponse, { error: onResponse }));
            });
        };

        $scope.stop = function () {
            $scope.controlWebApps('stop');
        };

        $scope.start = function () {
            $scope.controlWebApps('start');
        };

        $scope.uninstall = function () {
            $scope.controlWebApps('destroy');
            $scope.uninstallDialog.close();
        };

        $scope.anySelectionHasState = function (state) {
            var selected = $scope.selected || [];
            return selected.length && selected.any(function (s) {
                return Jetty.isState(s, state);
            });
        };

        $scope.everySelectionHasState = function (state) {
            var selected = $scope.selected || [];
            return selected.length && selected.every(function (s) {
                return Jetty.isState(s, state);
            });
        };

        // function to trigger reloading page
        $scope.onLastResponse = function (response) {
            $scope.onResponse(response);

            // we only want to force updating the data on the last response
            loadData();
        };

        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
        };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        // grab server information once
        $scope.jettyServerVersion = "";
        $scope.jettyServerStartupTime = "";

        var servers = jolokia.search("org.eclipse.jetty.server:type=server,*");
        if (servers && servers.length === 1) {
            $scope.jettyServerVersion = jolokia.getAttribute(servers[0], "version");
            $scope.jettyServerStartupTime = jolokia.getAttribute(servers[0], "startupTime");
        } else {
            console.log("Cannot find jetty server or there was more than one server. response is: " + servers);
        }

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading Jetty webapp data...");

            // must load connectors first, before showing applications, so we do this call synchronously
            // jetty 7/8
            var connectors = jolokia.search("org.eclipse.jetty.server.nio:type=selectchannelconnector,*");
            if (!connectors) {
                // jetty 9
                connectors = jolokia.search("org.eclipse.jetty.server:type=serverconnector,*");
            }
            if (connectors) {
                var found = false;
                angular.forEach(connectors, function (key, value) {
                    var mbean = key;
                    if (!found) {
                        var data = jolokia.request({ type: "read", mbean: mbean, attribute: ["port", "protocols"] });
                        if (data && data.value && data.value.protocols && data.value.protocols.toString().toLowerCase().startsWith("http")) {
                            found = true;
                            $scope.httpPort = data.value.port;
                            $scope.httpScheme = "http";
                        }
                    }
                });
            }

            // support embedded jetty which may use morbay mbean names
            jolokia.search("org.mortbay.jetty.plugin:type=jettywebappcontext,*", onSuccess(render));
            jolokia.search("org.eclipse.jetty.webapp:type=webappcontext,*", onSuccess(render));
            jolokia.search("org.eclipse.jetty.servlet:type=servletcontexthandler,*", onSuccess(render));
        }

        function render(response) {
            $scope.webapps = [];
            $scope.mbeanIndex = {};
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    if (!obj.state) {
                        // lets leave the state as it is if it is defined
                        obj.state = obj['running'] === undefined || obj['running'] ? "started" : "stopped";
                    }

                    // compute the url for the webapp, and we want to use http as scheme
                    var hostname = Core.extractTargetUrl($location, $scope.httpScheme, $scope.httpPort);
                    obj.url = hostname + obj['contextPath'];

                    var mbean = obj.mbean;
                    if (mbean) {
                        var idx = $scope.mbeanIndex[mbean];
                        if (angular.isDefined(idx)) {
                            $scope.webapps[mbean] = obj;
                        } else {
                            $scope.mbeanIndex[mbean] = $scope.webapps.length;
                            $scope.webapps.push(obj);
                        }
                        Core.$apply($scope);
                    }
                }
            }

            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean, attribute: [] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
    }
    Jetty.JettyController = JettyController;
})(Jetty || (Jetty = {}));
/**
* @module Jetty
* @main Jetty
*/
var Jetty;
(function (Jetty) {
    var pluginName = 'jetty';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ui.bootstrap.dialog', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/jetty/server', { templateUrl: 'app/jetty/html/server.html' }).when('/jetty/applications', { templateUrl: 'app/jetty/html/applications.html' }).when('/jetty/connectors', { templateUrl: 'app/jetty/html/connectors.html' }).when('/jetty/threadpools', { templateUrl: 'app/jetty/html/threadpools.html' });
    }).filter('jettyIconClass', function () {
        return Jetty.iconClass;
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['jetty'] = "app/jetty/html/layoutJettyTabs.html";
        helpRegistry.addUserDoc('jetty', 'app/jetty/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("org.eclipse.jetty.server");
        });

        workspace.topLevelTabs.push({
            id: "jetty",
            content: "Jetty",
            title: "Manage your Jetty container",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("org.eclipse.jetty.server");
            },
            href: function () {
                return "#/jetty/applications";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("jetty");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Jetty || (Jetty = {}));
/**
* @module Jetty
*/
var Jetty;
(function (Jetty) {
    function ThreadPoolsController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | jettyIconClass}}"></i></div>';

        $scope.threadpools = [];

        var columnDefs = [
            {
                field: 'running',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'threads',
                displayName: 'Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'minThreads',
                displayName: 'Min Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'maxThreads',
                displayName: 'Max Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'idleThreads',
                displayName: 'Idle Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'idleTimeout',
                displayName: 'Idle Timeout (ms)',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'name',
                displayName: 'Name',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'threadpools',
            displayFooter: true,
            canSelectRows: false,
            columnDefs: columnDefs,
            title: "Thread Pools"
        };

        function render78(response) {
            $scope.threadpools = [];

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    // jetty78 vs jetty9 is a bit different
                    obj.running = obj['running'] !== undefined ? obj['running'] : obj['state'] == "STARTED";
                    obj.idleTimeout = obj['idleTimeout'] !== undefined ? obj['idleTimeout'] : obj['maxIdleTimeMs'];
                    $scope.threadpools.push(obj);
                }
            }

            // create structure for each response
            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean, attribute: [] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading Jetty thread pool data...");
            var tree = workspace.tree;

            jolokia.search("org.eclipse.jetty.util.thread:type=queuedthreadpool,*", onSuccess(render78));
        }
    }
    Jetty.ThreadPoolsController = ThreadPoolsController;
})(Jetty || (Jetty = {}));
/**
* @module Jetty
*/
var Jetty;
(function (Jetty) {
    function ConnectorsController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | jettyIconClass}}"></i></div>';

        $scope.connectors = [];
        $scope.selected = [];

        var columnDefs = [
            {
                field: 'running',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'port',
                displayName: 'Port',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'protocols',
                displayName: 'Protocols',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'default',
                displayName: 'Default',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'connectors',
            displayFooter: true,
            selectedItems: $scope.selected,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            },
            title: "Connectors"
        };

        // function to control the connectors
        $scope.controlConnectors = function (op) {
            // grab id of mbean names to control
            var mbeanNames = $scope.selected.map(function (b) {
                return b.mbean;
            });
            if (!angular.isArray(mbeanNames)) {
                mbeanNames = [mbeanNames];
            }

            // execute operation on each mbean
            var lastIndex = (mbeanNames.length || 1) - 1;
            angular.forEach(mbeanNames, function (mbean, idx) {
                var onResponse = (idx >= lastIndex) ? $scope.onLastResponse : $scope.onResponse;
                jolokia.request({
                    type: 'exec',
                    mbean: mbean,
                    operation: op,
                    arguments: null
                }, onSuccess(onResponse, { error: onResponse }));
            });
        };

        $scope.stop = function () {
            $scope.controlConnectors('stop');
        };

        $scope.start = function () {
            $scope.controlConnectors('start');
        };

        $scope.anySelectionIsRunning = function () {
            var selected = $scope.selected || [];
            return selected.length && selected.any(function (s) {
                return s.running;
            });
        };

        $scope.everySelectionIsRunning = function (state) {
            var selected = $scope.selected || [];
            return selected.length && selected.every(function (s) {
                return s.running;
            });
        };

        function render78(response) {
            $scope.connectors = [];
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    // split each into 2 rows as we want http and https on each row
                    obj.mbean = response.request.mbean;
                    obj.protocols = "[http]";
                    obj.default = "http";
                    obj.port = obj.port;
                    obj.running = obj['running'] !== undefined ? obj['running'] : true;
                    $scope.connectors.push(obj);
                    if (obj.confidentialPort) {
                        // create a clone of obj for https
                        var copyObj = {
                            protocols: "[https]",
                            default: "https",
                            port: obj.confidentialPort,
                            running: obj.running,
                            mbean: obj.mbean
                        };
                        $scope.connectors.push(copyObj);
                    }
                    Core.$apply($scope);
                }
            }

            // create structure for each response
            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean, attribute: [] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        function render9(response) {
            $scope.connectors = [];
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    obj.protocols = obj['protocols'];
                    obj.default = obj['defaultProtocol'];
                    obj.port = obj.port;
                    obj.running = obj['state'] == "STARTED";
                    $scope.connectors.push(obj);
                    Core.$apply($scope);
                }
            }

            // create structure for each response
            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean, attribute: [] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        // function to trigger reloading page
        $scope.onLastResponse = function (response) {
            $scope.onResponse(response);

            // we only want to force updating the data on the last response
            loadData();
        };

        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
        };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading Jetty connector data...");
            var tree = workspace.tree;

            jolokia.search("org.eclipse.jetty.server.nio:type=selectchannelconnector,*", onSuccess(render78));
            jolokia.search("org.eclipse.jetty.server:type=serverconnector,*", onSuccess(render9));
        }
    }
    Jetty.ConnectorsController = ConnectorsController;
})(Jetty || (Jetty = {}));
/**
* @module Git
* @main Git
*/
var Git;
(function (Git) {
    

    /**
    * A default implementation which uses jolokia and the
    * GitFacadeMXBean over JMX
    *
    * @class JolokiaGit
    * @uses GitRepository
    *
    */
    var JolokiaGit = (function () {
        function JolokiaGit(mbean, jolokia, localStorage, userDetails, branch) {
            if (typeof branch === "undefined") { branch = "master"; }
            this.mbean = mbean;
            this.jolokia = jolokia;
            this.localStorage = localStorage;
            this.userDetails = userDetails;
            this.branch = branch;
        }
        JolokiaGit.prototype.getRepositoryLabel = function (fn, error) {
            return this.jolokia.request({ type: "read", mbean: this.mbean, attribute: ["RepositoryLabel"] }, onSuccess(function (result) {
                fn(result.value.RepositoryLabel);
            }, { error: error }));
        };

        JolokiaGit.prototype.exists = function (branch, path, fn) {
            return this.jolokia.execute(this.mbean, "exists", branch, path, onSuccess(fn));
        };

        JolokiaGit.prototype.read = function (branch, path, fn) {
            return this.jolokia.execute(this.mbean, "read", branch, path, onSuccess(fn));
        };

        JolokiaGit.prototype.write = function (branch, path, commitMessage, contents, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();
            return this.jolokia.execute(this.mbean, "write", branch, path, commitMessage, authorName, authorEmail, contents, onSuccess(fn));
        };

        JolokiaGit.prototype.writeBase64 = function (branch, path, commitMessage, contents, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();
            return this.jolokia.execute(this.mbean, "writeBase64", branch, path, commitMessage, authorName, authorEmail, contents, onSuccess(fn));
        };

        JolokiaGit.prototype.createDirectory = function (branch, path, commitMessage, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();

            return this.jolokia.execute(this.mbean, "createDirectory", branch, path, commitMessage, authorName, authorEmail, onSuccess(fn));
        };

        JolokiaGit.prototype.revertTo = function (branch, objectId, blobPath, commitMessage, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();

            return this.jolokia.execute(this.mbean, "revertTo", branch, objectId, blobPath, commitMessage, authorName, authorEmail, onSuccess(fn));
        };

        JolokiaGit.prototype.rename = function (branch, oldPath, newPath, commitMessage, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();

            return this.jolokia.execute(this.mbean, "rename", branch, oldPath, newPath, commitMessage, authorName, authorEmail, onSuccess(fn));
        };

        JolokiaGit.prototype.remove = function (branch, path, commitMessage, fn) {
            var authorName = this.getUserName();
            var authorEmail = this.getUserEmail();

            return this.jolokia.execute(this.mbean, "remove", branch, path, commitMessage, authorName, authorEmail, onSuccess(fn));
        };

        JolokiaGit.prototype.completePath = function (branch, completionText, directoriesOnly, fn) {
            return this.jolokia.execute(this.mbean, "completePath", branch, completionText, directoriesOnly, onSuccess(fn));
        };

        JolokiaGit.prototype.history = function (branch, objectId, path, limit, fn) {
            return this.jolokia.execute(this.mbean, "history", branch, objectId, path, limit, onSuccess(fn));
        };

        JolokiaGit.prototype.commitTree = function (commitId, fn) {
            return this.jolokia.execute(this.mbean, "getCommitTree", commitId, onSuccess(fn));
        };

        JolokiaGit.prototype.commitInfo = function (commitId, fn) {
            return this.jolokia.execute(this.mbean, "getCommitInfo", commitId, onSuccess(fn));
        };

        JolokiaGit.prototype.diff = function (objectId, baseObjectId, path, fn) {
            return this.jolokia.execute(this.mbean, "diff", objectId, baseObjectId, path, onSuccess(fn));
        };

        JolokiaGit.prototype.getContent = function (objectId, blobPath, fn) {
            return this.jolokia.execute(this.mbean, "getContent", objectId, blobPath, onSuccess(fn));
        };

        JolokiaGit.prototype.readJsonChildContent = function (path, nameWildcard, search, fn) {
            return this.jolokia.execute(this.mbean, "readJsonChildContent", this.branch, path, nameWildcard, search, onSuccess(fn));
        };

        JolokiaGit.prototype.branches = function (fn) {
            return this.jolokia.execute(this.mbean, "branches", onSuccess(fn));
        };

        // TODO move...
        JolokiaGit.prototype.getUserName = function () {
            return this.localStorage["gitUserName"] || this.userDetails.username || "anonymous";
        };

        JolokiaGit.prototype.getUserEmail = function () {
            return this.localStorage["gitUserEmail"] || "anonymous@gmail.com";
        };
        return JolokiaGit;
    })();
    Git.JolokiaGit = JolokiaGit;
})(Git || (Git = {}));
/**
* @module Git
*/
var Git;
(function (Git) {
    function createGitRepository(workspace, jolokia, localStorage) {
        var mbean = getGitMBean(workspace);
        if (mbean && jolokia) {
            return new Git.JolokiaGit(mbean, jolokia, localStorage, workspace.userDetails);
        }

        // TODO use local storage to make a little wiki thingy?
        return null;
    }
    Git.createGitRepository = createGitRepository;

    Git.jmxDomain = "hawtio";
    Git.mbeanType = "GitFacade";

    function hasGit(workspace) {
        return getGitMBean(workspace) !== null;
    }
    Git.hasGit = hasGit;

    /**
    * Returns the JMX ObjectName of the git mbean
    * @method getGitMBean
    * @for Git
    * @param {Workspace} workspace
    * @return {String}
    */
    function getGitMBean(workspace) {
        return Core.getMBeanTypeObjectName(workspace, Git.jmxDomain, Git.mbeanType);
    }
    Git.getGitMBean = getGitMBean;

    /**
    * Returns the Folder for the git mbean if it can be found
    * @method getGitMBeanFolder
    * @for Git
    * @param {Workspace} workspace
    * @return {Folder}
    */
    function getGitMBeanFolder(workspace) {
        return Core.getMBeanTypeFolder(workspace, Git.jmxDomain, Git.mbeanType);
    }
    Git.getGitMBeanFolder = getGitMBeanFolder;

    /**
    * Returns true if the git mbean is a fabric configuration repository
    * (so we can use it for the fabric plugin)
    * @method isGitMBeanFabric
    * @for Git
    * @param {Workspace} workspace
    * @return {Boolean}
    */
    function isGitMBeanFabric(workspace) {
        var folder = getGitMBeanFolder(workspace);
        return folder && folder.entries["repo"] === "fabric";
    }
    Git.isGitMBeanFabric = isGitMBeanFabric;
})(Git || (Git = {}));
var Fabric;
(function (Fabric) {
    function CreateBrokerController($scope, localStorage, $routeParams, $location, jolokia, workspace, $compile, $templateCache) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.defaultGroup = "default";
        $scope.defaultBrokerName = "brokerName";

        $scope.groups = [];
        $scope.possibleNetworks = [];
        $scope.profiles = [];
        $scope.parentProfiles = [];
        $scope.entity = {
            // default options
            group: $scope.defaultGroup,
            ssl: true
        };
        $scope.otherEntity = {
            networkConnectAll: false
        };

        // holds all the form objects from nested child scopes
        $scope.forms = {};

        $scope.onSubmit = function (json, form) {
            $scope.message = ($scope.entity.brokerName || "unknown") + " in group " + ($scope.entity.group || "unknown");
            notification("info", "Creating broker " + $scope.message);
            var tmpJson = JSON.stringify($scope.entity, null, '  ');
            jolokia.execute(Fabric.mqManagerMBean, "saveBrokerConfigurationJSON", tmpJson, onSuccess(onSave));

            // now lets switch to the brokers view
            $location.path("/fabric/mq/brokers");
            Core.$apply($scope);
        };

        $scope.brokerNameExists = function () {
            var name = $scope.entity.brokerName;
            return name && $scope.brokerNames.indexOf(name) >= 0;
        };

        function updatePossibleNetworks() {
            var group = $scope.entity.group;
            $scope.possibleNetworks = [].concat($scope.groups);
            if (group) {
                $scope.possibleNetworks = $scope.possibleNetworks.remove(group);
            }
        }

        $scope.$watch("entity.group", updatePossibleNetworks);
        $scope.$watch("otherEntity.networkConnectAll", function () {
            if ($scope.otherEntity.networkConnectAll) {
                $scope.entity.networks = $scope.possibleNetworks;
            }
        });

        // default parameters from the URL
        angular.forEach(["group", "profile"], function (param) {
            var value = $routeParams[param];
            if (value) {
                $scope.entity[param] = value;
            }
        });
        if (!$scope.entity.kind) {
            $scope.entity.kind = "MasterSlave";
        }

        Fabric.getDtoSchema("brokerConfig", "io.fabric8.api.jmx.MQBrokerConfigDTO", jolokia, function (schema) {
            $scope.schema = schema;
            configureSchema(schema);
            jolokia.execute(Fabric.mqManagerMBean, "loadBrokerStatus()", onSuccess(onBrokerData));
            Core.$apply($scope);
        });

        function configureSchema(schema) {
            delete schema.properties['username'];
            delete schema.properties['password'];

            // avoid the properties field for now as we don't yet have a generated UI for key/value pairs...
            delete schema.properties['properties'];

            var isReplicated = "entity.kind == 'Replicated'";
            var isStandalone = "entity.kind == 'StandAlone'";

            Core.pathSet(schema.properties, ['group', 'required'], true);
            Core.pathSet(schema.properties, ['group', 'tooltip'], 'The peer group name of message brokers. The group is name is used by messaging clients to connect to a broker; so it represents a peer group of brokers used for load balancing.');
            Core.pathSet(schema.properties, ['group', 'input-attributes', 'typeahead'], 'title for title in groups | filter:$viewValue');
            Core.pathSet(schema.properties, ['group', 'input-attributes', 'typeahead-editable'], 'true');

            Core.pathSet(schema.properties, ['brokerName', 'required'], true);
            Core.pathSet(schema.properties, ['brokerName', 'tooltip'], 'The name of the broker.');
            Core.pathSet(schema.properties, ['brokerName', 'input-attributes', 'autofocus'], 'true');

            Core.pathSet(schema.properties, ['parentProfile', 'tooltip'], 'The parent profile used by the profile.');
            Core.pathSet(schema.properties, ['parentProfile', 'input-attributes', 'typeahead'], 'p.id for p in parentProfiles | filter:$viewValue');
            Core.pathSet(schema.properties, ['parentProfile', 'input-attributes', 'typeahead-editable'], 'false');
            Core.pathSet(schema.properties, ['parentProfile', 'input-attributes', "placeholder"], "{{" + isReplicated + " ? 'mq-replicated' : 'mq-base'}}");

            Core.pathSet(schema.properties, ['profile', 'tooltip'], 'The profile to create instances of this broker.');
            Core.pathSet(schema.properties, ['profile', 'input-attributes', 'typeahead'], 'title for title in profiles | filter:$viewValue');
            Core.pathSet(schema.properties, ['profile', 'input-attributes', 'typeahead-editable'], 'true');
            Core.pathSet(schema.properties, ['profile', 'input-attributes', "placeholder"], "mq-broker-{{entity.group || 'default'}}.{{entity.brokerName || 'brokerName'}}");

            Core.pathSet(schema.properties, ['clientProfile', 'tooltip'], 'The profile used by messaging clients to connect to this group of brokers.');
            Core.pathSet(schema.properties, ['clientProfile', 'input-attributes', 'typeahead'], 'title for title in profiles | filter:$viewValue');
            Core.pathSet(schema.properties, ['clientProfile', 'input-attributes', 'typeahead-editable'], 'true');
            Core.pathSet(schema.properties, ['clientProfile', 'input-attributes', "placeholder"], "mq-client-{{entity.group || 'default'}}");

            Core.pathSet(schema.properties, ['clientParentProfile', 'tooltip'], 'The parent profile used by the client profile.');
            Core.pathSet(schema.properties, ['clientParentProfile', 'input-attributes', 'typeahead'], 'p.id for p in parentProfiles | filter:$viewValue');
            Core.pathSet(schema.properties, ['clientParentProfile', 'input-attributes', 'typeahead-editable'], 'false');
            Core.pathSet(schema.properties, ['clientParentProfile', 'input-attributes', 'placeholder'], 'mq-client-base');
            Core.pathSet(schema.properties, ['parentProfile', 'input-attributes', "placeholder"], "default");

            Core.pathSet(schema.properties, ['data', 'input-attributes', "placeholder"], "${karaf.base}/data/{{entity.brokerName || 'brokerName'}}");
            Core.pathSet(schema.properties, ['configUrl', 'input-attributes', "placeholder"], "profile:broker.xml");

            Core.pathSet(schema.properties, ['replicas', 'control-group-attributes', "ng-show"], isReplicated);
            Core.pathSet(schema.properties, ['replicas', 'input-attributes', "value"], "{{3}}");
            Core.pathSet(schema.properties, ['replicas', 'input-attributes', "min"], "1");
            Core.pathSet(schema.properties, ['minimumInstances', 'control-group-attributes', "ng-hide"], isReplicated);
            Core.pathSet(schema.properties, ['minimumInstances', 'input-attributes', "value"], "{{" + isStandalone + " ? 1 : 2}}");
            Core.pathSet(schema.properties, ['minimumInstances', 'input-attributes', "min"], "1");

            Core.pathSet(schema.properties, ['networksPassword', 'type'], 'password');
            Core.pathSet(schema.properties, ['networks', 'items', 'input-attributes', 'typeahead-editable'], 'true');
            Core.pathSet(schema.properties, ['networks', 'input-attributes', "ng-hide"], "otherEntity.networkConnectAll");
            Core.pathSet(schema.properties, ['networks', 'tooltip'], 'The broker groups to create a store and forward network to');

            // add an extra property to make it easy to connect to all / none
            Core.pathSet(schema.properties, ['networkConnectAll', 'type'], 'boolean');
            Core.pathSet(schema.properties, ['networkConnectAll', 'input-attributes', 'ng-model'], "otherEntity.networkConnectAll");
            Core.pathSet(schema.properties, ['networkConnectAll', 'label'], 'Network to all groups');
            Core.pathSet(schema.properties, ['networkConnectAll', 'tooltip'], 'Should this broker create a store and forward network to all the known groups of brokers');

            schema['tabs'] = {
                'Default': ['group', 'brokerName', 'kind', 'profile', 'clientProfile', 'data', 'configUrl', 'replicas', 'minimumInstances', 'networkConnectAll', 'networks'],
                'Advanced': ['parentProfile', 'clientParentProfile', 'networksUserName', 'networksPassword', '*']
            };
        }

        function onBrokerData(brokerStatuses) {
            var networkNames = brokerStatuses.map(function (s) {
                return s.networks;
            }).flatten().unique();
            var groups = brokerStatuses.map(function (s) {
                return s.group;
            }).unique();

            $scope.groups = networkNames.concat(groups).unique().sort();
            $scope.profiles = brokerStatuses.map(function (s) {
                return s.profile;
            }).unique().sort();
            $scope.brokerNames = brokerStatuses.map(function (s) {
                return s.brokerName;
            }).unique().sort();

            updatePossibleNetworks();

            var version = brokerStatuses.map(function (s) {
                return s.version;
            }).find(function (s) {
                return s;
            }) || "1.0";
            if (version) {
                jolokia.execute(Fabric.managerMBean, "getProfiles(java.lang.String,java.util.List)", version, ["id", "abstract"], onSuccess(onProfileData));
            }
            Core.$apply($scope);
        }

        function onProfileData(profileData) {
            if (profileData) {
                $scope.parentProfiles = profileData.filter(function (p) {
                    return !p.abstract;
                }).sortBy("id");
            }
        }

        function onSave(response) {
            notification("success", "Created broker " + $scope.message);
            Core.$apply($scope);
        }
    }
    Fabric.CreateBrokerController = CreateBrokerController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function MigrateContainersController($scope, jolokia, $location) {
        $scope.versions = [];
        $scope.containers = [];
        $scope.containersResponse = [];

        $scope.selectedVersion = [];
        $scope.selectedContainers = [];

        $scope.showApply = false;

        $scope.versionGridOptions = {
            data: 'versions',
            selectedItems: $scope.selectedVersion,
            showSelectionCheckbox: true,
            multiSelect: false,
            keepLastSelected: true,
            columnDefs: [{
                    field: 'id',
                    displayName: 'Version Name',
                    width: '94%'
                }],
            filterOptions: {
                filterText: ''
            }
        };

        $scope.containerGridOptions = {
            data: 'containers',
            selectedItems: $scope.selectedContainers,
            showSelectionCheckbox: true,
            multiSelect: true,
            keepLastSelected: false,
            columnDefs: [{
                    field: 'id',
                    displayName: 'Container Name',
                    width: '94%'
                }],
            filterOptions: {
                filterText: ''
            }
        };

        $scope.canApply = function () {
            return !($scope.selectedVersion.length > 0 && $scope.selectedContainers.length > 0);
        };

        $scope.render = function (response) {
            if (response.request.operation === 'versions()') {
                if (!Object.equal($scope.versions, response.value)) {
                    $scope.versions = response.value;
                    Core.$apply($scope);
                }
            }

            if (response.request.operation === 'containerIds()') {
                if (!Object.equal($scope.containersResponse, response.value)) {
                    $scope.containersResponse = response.value;

                    $scope.containers = [];

                    $scope.containersResponse.each(function (container) {
                        $scope.containers.push({
                            id: container
                        });
                    });
                    Core.$apply($scope);
                }
            }
        };

        $scope.migrateContainers = function () {
            var containerIds = $scope.selectedContainers.map(function (container) {
                return container.id;
            });
            var versionId = $scope.selectedVersion[0].id;

            notification('info', "Moving containers to version " + versionId);
            $location.path("/fabric/containers");

            Fabric.migrateContainers(jolokia, versionId, containerIds, function () {
                notification('success', "Successfully migrated containers");
            }, function (response) {
                notification('error', "Failed to migrate containers due to " + response.error);
            });
        };

        Core.register(jolokia, $scope, [
            { type: 'exec', mbean: Fabric.managerMBean, operation: 'versions()' },
            { type: 'exec', mbean: Fabric.managerMBean, operation: 'containerIds()' }
        ], onSuccess($scope.render));
    }
    Fabric.MigrateContainersController = MigrateContainersController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function CreateFabricController($scope, jolokia, $location, workspace, branding) {
        $scope.$on('$routeChangeSuccess', function () {
            if (workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "Fabric" })) {
                $location.url("/fabric/view");
            }
        });

        Fabric.getSchema('createEnsemble', 'io.fabric8.api.CreateEnsembleOptions', jolokia, function (schema) {
            $scope.schema = schema;
            Core.$apply($scope);
        });

        $scope.creating = false;

        $scope.entity = {
            zooKeeperServerPort: 2181,
            globalResolver: 'localhostname',
            resolver: 'localhostname',
            agentEnabled: true,
            autoImportEnabled: true,
            minimumPort: 0,
            maximumPort: 65535,
            profiles: ['fabric', 'hawtio']
        };

        if (branding.profile) {
            $scope.entity.profiles.push(branding.profile);
        }

        // console.log("entity: ", $scope.entity);
        $scope.forms = {};

        $scope.onSubmit = function (json, form) {
            json = Fabric.sanitizeJson(json);

            setTimeout(function () {
                jolokia.execute(Fabric.clusterBootstrapManagerMBean, 'createCluster(java.util.Map)', angular.toJson(json), {
                    method: 'post',
                    success: function (response) {
                        notification('success', "Created fabric!");
                        $location.url("/fabric/containers");
                        Core.$apply($scope);
                    },
                    error: function (response) {
                        notification('error', "Error creating fabric: " + response.error);
                        Core.$apply($scope);
                    }
                });
                notification('info', "Creating fabric, please wait...");
                $location.url("/openlogs");
                Core.$apply($scope);
            }, 30);
        };
    }
    Fabric.CreateFabricController = CreateFabricController;
})(Fabric || (Fabric = {}));
/**
* @module Fabric
*/
var Fabric;
(function (Fabric) {
    Fabric.log = Logger.get("Fabric");

    Fabric.jmxDomain = 'io.fabric8';

    Fabric.managerMBean = Fabric.jmxDomain + ":type=Fabric";
    Fabric.clusterManagerMBean = Fabric.jmxDomain + ":type=ClusterServiceManager";
    Fabric.clusterBootstrapManagerMBean = Fabric.jmxDomain + ":type=ClusterBootstrapManager";
    Fabric.openShiftFabricMBean = Fabric.jmxDomain + ":type=OpenShift";
    Fabric.mqManagerMBean = Fabric.jmxDomain + ":type=MQManager";

    var schemaLookupDomain = "hawtio";
    var schemaLookupType = "SchemaLookup";

    Fabric.schemaLookupMBean = schemaLookupDomain + ":type=" + schemaLookupType;

    Fabric.useDirectoriesInGit = true;
    Fabric.fabricTopLevel = "fabric/profiles/";
    Fabric.profileSuffix = ".profile";

    Fabric.jolokiaWebAppGroupId = Fabric.jmxDomain + ".fabric-jolokia";

    function fabricCreated(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "Fabric" });
    }
    Fabric.fabricCreated = fabricCreated;

    function canBootstrapFabric(workspace) {
        return hasClusterBootstrapManager(workspace);
    }
    Fabric.canBootstrapFabric = canBootstrapFabric;

    function hasClusterBootstrapManager(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "ClusterBootstrapManager" });
    }
    Fabric.hasClusterBootstrapManager = hasClusterBootstrapManager;

    function hasClusterServiceManager(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "ClusterServiceManager" });
    }
    Fabric.hasClusterServiceManager = hasClusterServiceManager;

    function hasZooKeeper(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "ZooKeeper" });
    }
    Fabric.hasZooKeeper = hasZooKeeper;

    function hasOpenShiftFabric(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "OpenShift" });
    }
    Fabric.hasOpenShiftFabric = hasOpenShiftFabric;

    function hasMQManager(workspace) {
        return workspace.treeContainsDomainAndProperties(Fabric.jmxDomain, { type: "MQManager" });
    }
    Fabric.hasMQManager = hasMQManager;

    function hasSchemaMBean(workspace) {
        return workspace.treeContainsDomainAndProperties(schemaLookupDomain, { type: schemaLookupType });
    }
    Fabric.hasSchemaMBean = hasSchemaMBean;

    function hasGitMBean(workspace) {
        return workspace.treeContainsDomainAndProperties(Git.jmxDomain, { type: Git.mbeanType });
    }
    Fabric.hasGitMBean = hasGitMBean;

    function isFMCContainer(workspace) {
        var hasFabric = Fabric.hasFabric(workspace);
        var hasSchemaMBean = Fabric.hasSchemaMBean(workspace);
        var hasGitMBean = Fabric.hasGitMBean(workspace);

        // Too noisy...
        // log.debug("is FMC container, hasFabric: ", hasFabric, " hasSchemaMBean:", hasSchemaMBean, " hasGitMBean:", hasGitMBean);
        return hasFabric && hasSchemaMBean && hasGitMBean;
    }
    Fabric.isFMCContainer = isFMCContainer;

    function hasFabric(workspace) {
        // lets make sure we only have a fabric if we have
        // the ClusterServiceManager or ClusterBootstrapManager available
        // so that we hide Fabric for 6.0 or earlier of JBoss Fuse
        // which doesn't have the necessary mbeans for hawtio awesomeness
        return fabricCreated(workspace) && (hasClusterServiceManager(workspace) || hasClusterBootstrapManager(workspace) || hasZooKeeper(workspace));
    }
    Fabric.hasFabric = hasFabric;

    /**
    * Adds a bunch of common helper functions to the given scope
    * @method initScope
    * @for Fabric
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @paran {*} jolokia
    * @param {Workspace} workspace
    */
    function initScope($scope, $location, jolokia, workspace) {
        // Let's avoid re-defining everything if the $scope
        // has already been initialized here
        if ($scope.fabricInitialized) {
            return;
        } else {
            $scope.fabricInitialized = true;
        }

        $scope.gotoProfile = function (versionId, profileId) {
            Fabric.gotoProfile(workspace, jolokia, workspace.localStorage, $location, versionId, profileId);
        };

        $scope.getStatusTitle = function (container) {
            return Fabric.statusTitle(container);
        };

        $scope.isCurrentContainer = function (container) {
            if (!container) {
                return false;
            }
            if (Core.isBlank(Fabric.currentContainerId)) {
                return false;
            }
            if (angular.isObject(container)) {
                return container['id'] === Fabric.currentContainerId;
            }
            if (angular.isString(container)) {
                return container === Fabric.currentContainerId;
            }

            return false;
        };

        $scope.canConnect = function (container) {
            if (!container) {
                return false;
            }
            if (Core.isBlank(container['jolokiaUrl'])) {
                return false;
            }

            if (!Core.parseBooleanValue(container['alive'])) {
                return false;
            }
            return true;
        };

        $scope.refreshProfile = function (versionId, profileId) {
            Fabric.log.debug('Refreshing profile: ' + profileId + '/' + versionId);
            if (!versionId || !profileId) {
                return;
            }
            jolokia.request({
                type: 'exec',
                mbean: Fabric.managerMBean,
                operation: 'refreshProfile',
                arguments: [versionId, profileId]
            }, {
                method: 'POST',
                success: function () {
                    notification('success', 'Triggered refresh of profile ' + profileId + '/' + versionId);
                    Core.$apply($scope);
                },
                error: function (response) {
                    Fabric.log.warn('Failed to trigger refresh for profile ' + profileId + '/' + versionId + ' due to: ', response.error);
                    Fabric.log.info("Stack trace: ", response.stacktrace);
                    Core.$apply($scope);
                }
            });
        };

        $scope.getVersionsToExclude = function () {
            if (!$scope.selectedContainers || $scope.selectedContainers.length === 0) {
                return [];
            }
            var answer = $scope.selectedContainers.map(function (c) {
                return c['versionId'];
            });
            answer = answer.unique();
            if (answer.length > 1) {
                return [];
            } else {
                return answer;
            }
        };

        $scope.hasFabricWiki = function () {
            return Git.isGitMBeanFabric(workspace);
        };

        $scope.showContainer = function (container) {
            $location.path('/fabric/container/' + container.id);
        };

        $scope.createRequiredContainers = function (profile) {
            var profileId = profile.id;
            var args = {};
            if (profileId) {
                args["profileIds"] = profileId;
            }
            var versionId = profile.versionId || profile.version;
            if (versionId) {
                args["versionId"] = versionId;
            }
            var requirements = profile.requirements;
            if (requirements) {
                var min = requirements.minimumInstances;
                if (min) {
                    var delta = min - (profile.count || 0);
                    if (delta > 1) {
                        args["number"] = delta;
                    }
                }
            }
            $location.url('/fabric/containers/createContainer').search(args);
        };

        $scope.createContainer = function () {
            var kind = null;

            // lets see if there is an openshift option
            var providers = registeredProviders(jolokia);
            angular.forEach(["openshift", "docker", "jclouds"], function (value) {
                if (!kind && providers[value]) {
                    kind = value;
                }
            });
            if (!kind) {
                kind = 'child';
            }
            $location.url('/fabric/containers/createContainer').search('tab', kind);
        };

        $scope.createChildContainer = function (container) {
            if (!container.root || !container.alive) {
                return;
            }
            $location.url('/fabric/containers/createContainer').search({ 'tab': 'child', 'parentId': container.id });
        };

        $scope.showProfile = function (profile) {
            var version = profile.versionId || profile.version || $scope.activeVersionId;
            Fabric.gotoProfile(workspace, jolokia, localStorage, $location, version, profile);
        };

        $scope.getSelectedClass = function (obj) {
            var answer = [];
            if (obj.selected) {
                answer.push('selected');
            }
            if (angular.isDefined(obj['root']) && obj['root'] === false) {
                answer.push('child-container');
            }
            return answer.join(' ');
        };

        $scope.statusIcon = function (row) {
            return Fabric.statusIcon(row);
        };

        $scope.isEnsembleContainer = function (containerId) {
            if ($scope.ensembleContainerIds) {
                return $scope.ensembleContainerIds.any(containerId);
            }
            return false;
        };

        // for connection dialog
        $scope.connect = {
            dialog: new UI.Dialog(),
            saveCredentials: false,
            userName: null,
            password: null,
            container: null,
            view: null,
            onOK: function () {
                var userName = $scope.connect.userName;
                var password = $scope.connect.password;
                var container = $scope.connect.container;
                Fabric.log.info("Logging into container " + container + " with user " + userName);

                if ($scope.connect.saveCredentials) {
                    $scope.connect.saveCredentials = false;
                    if (userName) {
                        localStorage['fabric.userName'] = userName;
                    }
                    if (password) {
                        localStorage['fabric.password'] = password;
                    }
                }
                console.log("Connecting as user " + userName);
                var options = new Core.ConnectToServerOptions();
                options.view = $scope.connect.view;
                Fabric.connect(localStorage, container, userName, password, true, options);
                $scope.connect.container = {};
                setTimeout(function () {
                    $scope.connect.dialog.close();
                    Core.$apply($scope);
                }, 100);
            }
        };

        $scope.doConnect = function (container, view) {
            if (!$scope.canConnect(container)) {
                return;
            }

            // TODO at least obfusicate this
            $scope.connect.userName = Core.username || localStorage['fabric.userName'];
            $scope.connect.password = Core.password || localStorage['fabric.password'];
            $scope.connect.container = container;
            $scope.connect.view = view || "/openlogs";

            var alwaysPrompt = localStorage['fabricAlwaysPrompt'];
            if ((alwaysPrompt && alwaysPrompt !== "false") || !$scope.connect.userName || !$scope.connect.password) {
                $scope.connect.dialog.open();
            } else {
                $scope.connect.onOK();
            }
        };

        $scope.confirmDeleteDialog = {
            dialog: new UI.Dialog(),
            onOk: function () {
                $scope.confirmDeleteDialog.dialog.close();
                if (angular.isDefined($scope.containerId)) {
                    // avoid any nasty errors that the container doesn't existing anymore
                    Core.unregister(jolokia, $scope);
                    $location.path('/fabric/containers');

                    Fabric.doDeleteContainer($scope, jolokia, $scope.containerId);
                } else if (angular.isDefined($scope.selectedContainers)) {
                    $scope.selectedContainers.each(function (c) {
                        doDeleteContainer($scope, jolokia, c.id);
                    });
                } else {
                    // bail...
                    Fabric.log.info("Asked to delete containers but no containerId or selectedContainers attributes available");
                }
            },
            open: function () {
                $scope.confirmDeleteDialog.dialog.open();
            },
            close: function () {
                $scope.confirmDeleteDialog.dialog.close();
            }
        };

        $scope.createVersionDialog = {
            dialog: new UI.Dialog(),
            newVersionName: "",
            open: function () {
                Fabric.log.debug("Opening version dialog, $scope: ", $scope);
                $scope.createVersionDialog.newVersionName = "";
                $scope.createVersionDialog.dialog.open();
            },
            onOk: function () {
                Fabric.doCreateVersion($scope, jolokia, $location, $scope.createVersionDialog.newVersionName);
                $scope.createVersionDialog.newVersionName = "";
                $scope.createVersionDialog.dialog.close();
            }
        };

        $scope.$watch('selectedContainers', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                var num = $scope.selectedContainers.length;
                $scope.versionTitle = "Migrate " + Core.maybePlural(num, "Container") + " to:";
            }
        });

        $scope.onVersionChange = function (version) {
            var containerIds = [];

            if (angular.isDefined($scope.selectedContainers)) {
                containerIds = $scope.selectedContainers.map(function (c) {
                    return c.id;
                });
            } else if (angular.isDefined($scope.row)) {
                containerIds = [$scope.row.id];
            } else {
                return;
            }

            Fabric.log.info("Setting version to " + version + " on containers: " + containerIds);

            Fabric.migrateContainers(jolokia, version, containerIds, function () {
                notification('success', "Initiated container migration to version <strong>" + version + "</strong>, changes make take some time to complete");
                Core.$apply($scope);
            }, function (response) {
                Fabric.log.error("Failed to migrate containers due to ", response.error);
                Fabric.log.info("Stack trace: ", response.stacktrace);
                Core.$apply($scope);
            });
        };

        var verbose = workspace.localStorage['fabricVerboseNotifications'];
        $scope.fabricVerboseNotifications = verbose && verbose !== "false";
    }
    Fabric.initScope = initScope;

    function doCreateVersion($scope, jolokia, $location, newVersionName) {
        var success = function (response) {
            notification('success', "Created version <strong>" + response.value.id + "</strong>, switching to this new version");

            // broadcast events to force reloads
            var $rootScope = $scope.$root || $scope.$rootScope || $scope;
            if ($rootScope) {
                $rootScope.$broadcast('wikiBranchesUpdated');
            }

            var defaultTarget = '/wiki/branch/' + response.value.id + '/view/fabric/profiles';

            var path = $location.path();
            var branch = $scope.branch || $scope.$parent.branch;

            if (!path.startsWith('/wiki/branch/') || !branch) {
                $location.path(defaultTarget);
            } else {
                path = path.replace('/branch/' + branch, '/branch/' + response.value.id);
                $location.path(path);
            }
            Core.$apply($scope);
        };

        var error = function (response) {
            Fabric.log.error("Failed to create version due to :", response.error);
            Fabric.log.info("stack trace: ", response.stacktrace);
            Core.$apply($scope);
        };

        if (!Core.isBlank(newVersionName)) {
            Fabric.createVersionWithId(jolokia, newVersionName, success, error);
        } else {
            Fabric.createVersion(jolokia, success, error);
        }
    }
    Fabric.doCreateVersion = doCreateVersion;

    function sortVersions(versions, order) {
        return (versions || []).sortBy(function (v) {
            var answer = parseFloat(v['id']);
            if (answer === NaN) {
                answer = v['id'];
            }
            return answer;
        }, order);
    }
    Fabric.sortVersions = sortVersions;

    /**
    * Converts the given path from the wiki into a profile ID
    * @method pagePathToProfileId
    * @param {String} pageId
    * @return {String}
    */
    function pagePathToProfileId(pageId) {
        var answer = null;
        if (angular.isDefined(pageId) && pageId.has(Fabric.fabricTopLevel) && pageId !== Fabric.fabricTopLevel) {
            var profileId = pageId.remove(Fabric.fabricTopLevel);
            if ((Fabric.useDirectoriesInGit || !profileId.has("/"))) {
                var profileSeparator = profileId.indexOf(Fabric.profileSuffix + "/");
                var endsWithSuffix = profileId.endsWith(Fabric.profileSuffix);
                if (!Fabric.useDirectoriesInGit || endsWithSuffix || profileSeparator > 0) {
                    if (Fabric.useDirectoriesInGit) {
                        if (endsWithSuffix) {
                            profileId = Core.trimTrailing(profileId, Fabric.profileSuffix);
                        } else if (profileSeparator > 0) {
                            profileId = profileId.substring(0, profileSeparator);
                        }
                        profileId = profileId.replace(/\//g, "-");
                    }
                    answer = profileId;
                }
            }
        }
        return answer;
    }
    Fabric.pagePathToProfileId = pagePathToProfileId;

    function profilePath(profileId) {
        if (profileId) {
            return profileId.replace(/-/g, "/") + Fabric.profileSuffix;
        } else {
            return null;
        }
    }
    Fabric.profilePath = profilePath;

    function profileLink(workspace, jolokia, localStorage, versionId, profileId) {
        var path;
        if (Wiki.isWikiEnabled(workspace, jolokia, localStorage)) {
            path = "/wiki/branch/" + versionId + "/view/fabric/profiles/" + Fabric.profilePath(profileId);
        } else {
            path = "/fabric/profile/" + versionId + "/" + profileId;
        }
        return path;
    }
    Fabric.profileLink = profileLink;

    /**
    * Returns the CSS style for the number of containers badge
    * @method containerCountBadgeStyle
    * @param {Number} min
    * @param {number} count
    * @return {string}
    */
    function containerCountBadgeStyle(min, count) {
        if (min) {
            if (!count) {
                return "badge-important";
            } else {
                return min <= count ? "badge-success" : "badge-warning";
            }
        }
        return "";
    }
    Fabric.containerCountBadgeStyle = containerCountBadgeStyle;

    function gotoProfile(workspace, jolokia, localStorage, $location, versionId, profile) {
        var path = '';
        if (angular.isString(profile)) {
            path = profileLink(workspace, jolokia, localStorage, versionId, profile);
        } else {
            path = profileLink(workspace, jolokia, localStorage, versionId, profile.id);
        }
        if (!Core.isBlank(path)) {
            $location.url(path);
        }
    }
    Fabric.gotoProfile = gotoProfile;

    function setSelect(selection, group) {
        if (!angular.isDefined(selection)) {
            return group[0];
        }
        var answer = group.findIndex(function (item) {
            return item.id === selection.id;
        });
        if (answer !== -1) {
            return group[answer];
        } else {
            return group[0];
        }
    }
    Fabric.setSelect = setSelect;

    function doDeleteContainer($scope, jolokia, name, onDelete) {
        if (typeof onDelete === "undefined") { onDelete = null; }
        notification('info', "Deleting " + name);
        destroyContainer(jolokia, name, function () {
            notification('success', "Deleted " + name);
            if (onDelete) {
                onDelete();
            }
            Core.$apply($scope);
        }, function (response) {
            notification('error', "Failed to delete " + name + " due to " + response.error);
            Core.logJolokiaStackTrace(response);
            Core.$apply($scope);
        });
    }
    Fabric.doDeleteContainer = doDeleteContainer;

    function doStartContainer($scope, jolokia, name) {
        if ($scope.fabricVerboseNotifications) {
            notification('info', "Starting " + name);
        }
        startContainer(jolokia, name, function () {
            notification('success', "Started " + name);
            Core.$apply($scope);
        }, function (response) {
            notification('error', "Failed to start " + name + " due to " + response.error);
            Core.logJolokiaStackTrace(response);
            Core.$apply($scope);
        });
    }
    Fabric.doStartContainer = doStartContainer;

    function doStopContainer($scope, jolokia, name) {
        if ($scope.fabricVerboseNotifications) {
            notification('info', "Stopping " + name);
        }
        stopContainer(jolokia, name, function () {
            notification('success', "Stopped " + name);
            Core.$apply($scope);
        }, function (response) {
            notification('error', "Failed to stop " + name + " due to " + response.error);
            Core.logJolokiaStackTrace(response);
            Core.$apply($scope);
        });
    }
    Fabric.doStopContainer = doStopContainer;

    Fabric.urlResolvers = ['http:', 'ftp:', 'mvn:'];

    function completeUri($q, $scope, workspace, jolokia, something) {
    }
    Fabric.completeUri = completeUri;

    function applyPatches(jolokia, files, targetVersion, newVersionName, proxyUser, proxyPass, success, error) {
        doAction('applyPatches(java.util.List,java.lang.String,java.lang.String,java.lang.String,java.lang.String)', jolokia, [files, targetVersion, newVersionName, proxyUser, proxyPass], success, error);
    }
    Fabric.applyPatches = applyPatches;

    function setContainerProperty(jolokia, containerId, property, value, success, error) {
        doAction('setContainerProperty(java.lang.String, java.lang.String, java.lang.Object)', jolokia, [containerId, property, value], success, error);
    }
    Fabric.setContainerProperty = setContainerProperty;

    function deleteConfigFile(jolokia, version, profile, pid, success, error) {
        doAction('deleteConfigurationFile(java.lang.String, java.lang.String, java.lang.String)', jolokia, [version, profile, pid], success, error);
    }
    Fabric.deleteConfigFile = deleteConfigFile;

    function newConfigFile(jolokia, version, profile, pid, success, error) {
        doAction('setConfigurationFile(java.lang.String, java.lang.String, java.lang.String, java.lang.String)', jolokia, [version, profile, pid, ''], success, error);
    }
    Fabric.newConfigFile = newConfigFile;

    function saveConfigFile(jolokia, version, profile, pid, data, success, error) {
        doAction('setConfigurationFile(java.lang.String, java.lang.String, java.lang.String, java.lang.String)', jolokia, [version, profile, pid, data], success, error);
    }
    Fabric.saveConfigFile = saveConfigFile;

    function addProfilesToContainer(jolokia, container, profiles, success, error) {
        doAction('addProfilesToContainer(java.lang.String, java.util.List)', jolokia, [container, profiles], success, error);
    }
    Fabric.addProfilesToContainer = addProfilesToContainer;

    function removeProfilesFromContainer(jolokia, container, profiles, success, error) {
        doAction('removeProfilesFromContainer(java.lang.String, java.util.List)', jolokia, [container, profiles], success, error);
    }
    Fabric.removeProfilesFromContainer = removeProfilesFromContainer;

    function applyProfiles(jolokia, version, profiles, containers, success, error) {
        doAction('applyProfilesToContainers(java.lang.String, java.util.List, java.util.List)', jolokia, [version, profiles, containers], success, error);
    }
    Fabric.applyProfiles = applyProfiles;

    function migrateContainers(jolokia, version, containers, success, error) {
        doAction('applyVersionToContainers(java.lang.String, java.util.List)', jolokia, [version, containers], success, error);
    }
    Fabric.migrateContainers = migrateContainers;

    function changeProfileParents(jolokia, version, id, parents, success, error) {
        doAction('changeProfileParents(java.lang.String, java.lang.String, java.util.List)', jolokia, [version, id, parents], success, error);
    }
    Fabric.changeProfileParents = changeProfileParents;

    function createProfile(jolokia, version, id, parents, success, error) {
        doAction('createProfile(java.lang.String, java.lang.String, java.util.List)', jolokia, [version, id, parents], success, error);
    }
    Fabric.createProfile = createProfile;

    function copyProfile(jolokia, version, sourceName, targetName, force, success, error) {
        doAction('copyProfile(java.lang.String, java.lang.String, java.lang.String, boolean)', jolokia, [version, sourceName, targetName, force], success, error);
    }
    Fabric.copyProfile = copyProfile;

    function createVersionWithParentAndId(jolokia, base, id, success, error) {
        doAction('createVersion(java.lang.String, java.lang.String)', jolokia, [base, id], success, error);
    }
    Fabric.createVersionWithParentAndId = createVersionWithParentAndId;

    function createVersionWithId(jolokia, id, success, error) {
        doAction('createVersion(java.lang.String)', jolokia, [id], success, error);
    }
    Fabric.createVersionWithId = createVersionWithId;

    function createVersion(jolokia, success, error) {
        doAction('createVersion()', jolokia, [], success, error);
    }
    Fabric.createVersion = createVersion;

    function deleteVersion(jolokia, id, success, error) {
        doAction('deleteVersion(java.lang.String)', jolokia, [id], success, error);
    }
    Fabric.deleteVersion = deleteVersion;

    // TODO cache the current active version? Then clear the cached value if we delete it
    function getActiveVersion($location) {
        return $location.search()['cv'] || "1.0";
    }
    Fabric.getActiveVersion = getActiveVersion;

    function getContainerIdsForProfile(jolokia, version, profileId) {
        return jolokia.execute(Fabric.managerMBean, "containerIdsForProfile", version, profileId, { method: 'POST' });
    }
    Fabric.getContainerIdsForProfile = getContainerIdsForProfile;

    function deleteProfile(jolokia, version, id, success, error) {
        doAction('deleteProfile(java.lang.String, java.lang.String)', jolokia, [version, id], success, error);
    }
    Fabric.deleteProfile = deleteProfile;

    function profileWebAppURL(jolokia, webAppId, profileId, versionId, success, error) {
        doAction('profileWebAppURL', jolokia, [webAppId, profileId, versionId], success, error);
    }
    Fabric.profileWebAppURL = profileWebAppURL;

    function onJolokiaUrlCreateJolokia(response, fn) {
        var jolokia = null;
        if (response) {
            var url = response.value;
            if (url) {
                // lets use a proxy if the URL is external
                url = Core.useProxyIfExternal(url);
                jolokia = Fabric.createJolokia(url);
            } else {
                if (response.error) {
                    Fabric.log.warn(response.error, response.stacktrace);
                }
            }
            if (fn) {
                fn(jolokia);
            }
        }
        return jolokia;
    }

    /**
    * Attempts to create a jolokia for the given profile and version passing the created object
    * into the onJolokia function
    * @method profileJolokia
    *
    * @paran {*} jolokia
    * @param {String} profileId
    * @param {String} versionId
    * @param {Function} onJolokia a function to receive the jolokia object or null if one cannot be created
    */
    function profileJolokia(jolokia, profileId, versionId, onJolokia) {
        function onJolokiaUrl(response) {
            return onJolokiaUrlCreateJolokia(response, onJolokia);
        }
        if (profileId && versionId) {
            return Fabric.profileWebAppURL(jolokia, Fabric.jolokiaWebAppGroupId, profileId, versionId, onJolokiaUrl, onJolokiaUrl);
        } else {
            onJolokia(null);
        }
    }
    Fabric.profileJolokia = profileJolokia;

    /**
    * Attempts to create a jolokia for the given container id, passing the created object
    * into the onJolokia function
    * @method containerJolokia
    * @paran {*} jolokia
    * @param {String} containerId the id of the container to connect to
    * @param {Function} onJolokia a function to receive the jolokia object or null if one cannot be created
    */
    function containerJolokia(jolokia, containerId, onJolokia) {
        function onJolokiaUrl(response) {
            return onJolokiaUrlCreateJolokia(response, onJolokia);
        }
        return Fabric.containerWebAppURL(jolokia, Fabric.jolokiaWebAppGroupId, containerId, onJolokiaUrl, onJolokiaUrl);
    }
    Fabric.containerJolokia = containerJolokia;

    function containerWebAppURL(jolokia, webAppId, containerId, success, error) {
        doAction('containerWebAppURL', jolokia, [webAppId, containerId], success, error);
    }
    Fabric.containerWebAppURL = containerWebAppURL;

    function doAction(action, jolokia, arguments, success, error) {
        jolokia.request({
            type: 'exec', mbean: Fabric.managerMBean,
            operation: action,
            arguments: arguments
        }, {
            method: 'POST',
            success: success,
            error: error
        });
    }
    Fabric.doAction = doAction;

    function stopContainer(jolokia, id, success, error) {
        doAction('stopContainer(java.lang.String)', jolokia, [id], success, error);
    }
    Fabric.stopContainer = stopContainer;

    function destroyContainer(jolokia, id, success, error) {
        doAction('destroyContainer(java.lang.String)', jolokia, [id], success, error);
    }
    Fabric.destroyContainer = destroyContainer;

    function startContainer(jolokia, id, success, error) {
        doAction('startContainer(java.lang.String)', jolokia, [id], success, error);
    }
    Fabric.startContainer = startContainer;

    function getServiceList(container) {
        var answer = [];
        var javaContainer = true;
        if (angular.isDefined(container) && angular.isDefined(container.jmxDomains) && angular.isArray(container.jmxDomains) && container.alive) {
            answer = Fabric.serviceIconRegistry.getIcons(container.jmxDomains);
        }
        return answer;
    }
    Fabric.getServiceList = getServiceList;

    function getTypeIcon(container) {
        var type = container.type;

        // use the type in the metadata if it's there...
        if (container.metadata && container.metadata.containerType) {
            type = container.metadata.containerType;
        }
        var answer = Fabric.containerIconRegistry.getIcon(type);
        Fabric.log.debug("Icon for ", container, " : ", answer);
        if (!answer) {
            return Fabric.javaIcon;
        } else {
            return answer;
        }
    }
    Fabric.getTypeIcon = getTypeIcon;

    /**
    * Returns the default version ID for the current fabric
    * @param jolokia
    * @returns the version ID as a string; or defaults to 1.0 if not available
    */
    function getDefaultVersionId(jolokia) {
        return (getDefaultVersion(jolokia) || {})["id"] || "1.0";
    }
    Fabric.getDefaultVersionId = getDefaultVersionId;

    /**
    * Returns the default version object for the current fabric
    * @param jolokia
    * @returns the version object
    */
    function getDefaultVersion(jolokia) {
        return jolokia.execute(Fabric.managerMBean, "defaultVersion()");
    }
    Fabric.getDefaultVersion = getDefaultVersion;

    /**
    * Default the values that are missing in the returned JSON
    * @method defaultContainerValues
    * @param {Workspace} workspace
    * @param {any} $scope
    * @param {Array} values
    */
    function defaultContainerValues(workspace, $scope, values) {
        var map = {};
        angular.forEach(values, function (row) {
            var profileIds = row["profileIds"];
            if (profileIds) {
                angular.forEach(profileIds, function (profileId) {
                    var containers = map[profileId];
                    if (!containers) {
                        containers = [];
                        map[profileId] = containers;
                    }
                    containers.push(row);
                });
            }
            $scope.profileMap = map;
            row["link"] = containerLinks(workspace, row["id"]);
            row["profileLinks"] = profileLinks(workspace, row["versionId"], profileIds);

            var versionId = row["versionId"];
            var versionHref = url("#/fabric/profiles?v=" + versionId);
            var versionLink = "<a href='" + versionHref + "'>" + versionId + "</a>";
            row["versionHref"] = versionHref;
            row["versionLink"] = versionLink;

            var id = row['id'] || "";
            var title = "container " + id + " ";
            var img = "red-dot.png";
            if (row['managed'] === false) {
                img = "spacer.gif";
            } else if (!row['alive']) {
                img = "gray-dot.png";
            } else if (row['provisionPending']) {
                img = "pending.gif";
            } else if (row['provisionStatus'] === 'success') {
                img = "green-dot.png";
            }
            img = "img/dots/" + img;
            row["statusImageHref"] = img;
            row["link"] = "<img src='" + img + "' title='" + title + "'/> " + (row["link"] || id);
        });
        return values;
    }
    Fabric.defaultContainerValues = defaultContainerValues;

    // TODO move to core?
    function toCollection(values) {
        var collection = values;
        if (!angular.isArray(values)) {
            collection = [values];
        }
        return collection;
    }
    Fabric.toCollection = toCollection;

    function containerLinks(workspace, values) {
        var answer = "";
        angular.forEach(toCollection(values), function (value, key) {
            var prefix = "";
            if (answer.length > 0) {
                prefix = " ";
            }
            answer += prefix + "<a href='" + url("#/fabric/container/" + value + workspace.hash()) + "'>" + value + "</a>";
        });
        return answer;
    }
    Fabric.containerLinks = containerLinks;

    function profileLinks(workspace, versionId, values) {
        var answer = "";
        angular.forEach(toCollection(values), function (value, key) {
            var prefix = "";
            if (answer.length > 0) {
                prefix = " ";
            }
            answer += prefix + "<a href='" + url("#/fabric/profile/" + versionId + "/" + value + workspace.hash()) + "'>" + value + "</a>";
        });
        return answer;
    }
    Fabric.profileLinks = profileLinks;

    /**
    * Default the values that are missing in the returned JSON
    * @method defaultProfileValues
    * @param {Workspace} workspace
    * @param {String} versionId
    * @param {Array} values
    */
    function defaultProfileValues(workspace, versionId, values) {
        angular.forEach(values, function (row) {
            var id = row["id"];
            row["link"] = profileLinks(workspace, versionId, id);
            row["parentLinks"] = profileLinks(workspace, versionId, row["parentIds"]);
            var containersHref = url("#/fabric/containers?p=" + id);
            var containerCount = row["containerCount"];
            var containersLink = "";
            if (containerCount) {
                containersLink = "<a href='" + containersHref + "'>" + containerCount + "</a>";
            }
            row["containersCountLink"] = containersLink;
            row["containersHref"] = containersHref;
        });
        return values;
    }
    Fabric.defaultProfileValues = defaultProfileValues;

    function getZooKeeperFacadeMBean(workspace) {
        var folder = workspace.findMBeanWithProperties(Fabric.jmxDomain, { type: "ZooKeeper" });
        return Core.pathGet(folder, "objectName");
    }
    Fabric.getZooKeeperFacadeMBean = getZooKeeperFacadeMBean;

    function statusTitle(container) {
        var answer = 'Alive';
        if (!container.alive) {
            answer = 'Not Running';
        } else {
            answer += ' - ' + humanizeValue(container.provisionResult);
        }
        return answer;
    }
    Fabric.statusTitle = statusTitle;

    function statusIcon(row) {
        if (row) {
            if (row.alive) {
                switch (row.provisionResult) {
                    case 'success':
                        return "green icon-play-circle";
                    case 'downloading':
                        return "icon-download-alt";
                    case 'installing':
                        return "icon-hdd";
                    case 'analyzing':
                    case 'finalizing':
                        return "icon-refresh icon-spin";
                    case 'resolving':
                        return "icon-sitemap";
                    case 'error':
                        return "red icon-warning-sign";
                }
            } else {
                return "orange icon-off";
            }
        }
        return "icon-refresh icon-spin";
    }
    Fabric.statusIcon = statusIcon;

    /**
    * Opens a window connecting to the given container row details if the jolokiaUrl is available
    * @method connect
    * @param {any} localStorage
    * @param {any} row
    * @param {String} userName
    * @param {String} password
    * @param {Boolean} useProxy
    * @param {ConnectToServerOptions} options
    */
    function connect(localStorage, row, userName, password, useProxy, options) {
        if (typeof userName === "undefined") { userName = ""; }
        if (typeof password === "undefined") { password = ""; }
        if (typeof useProxy === "undefined") { useProxy = true; }
        if (typeof options === "undefined") { options = new Core.ConnectToServerOptions(); }
        options.name = row.id;
        options.jolokiaUrl = row.jolokiaUrl;
        options.userName = userName;
        options.password = password;
        options.useProxy = useProxy;

        Core.connectToServer(localStorage, options);
    }
    Fabric.connect = connect;

    /**
    * Creates a jolokia object for connecting to the container with the given remote jolokia URL
    * @method createJolokia
    * @param {String} url
    */
    function createJolokia(url) {
        // lets default to the user/pwd for the login
        // TODO maybe allow these to be configured to other values?
        var username = Core.username;
        var password = Core.password;
        if (!username) {
            // lets try reverse engineer the user/pwd from the stored user/pwd
            var jsonText = localStorage[url];
            if (jsonText) {
                var obj = Wiki.parseJson(jsonText);
                if (obj) {
                    username = obj["username"];
                    password = obj["password"];
                }
            }
        }
        Fabric.log.info("Logging into remote jolokia " + url + " using username: " + username);
        return Core.createJolokia(url, username, password);
    }
    Fabric.createJolokia = createJolokia;

    function registeredProviders(jolokia) {
        var providers = jolokia.execute(Fabric.managerMBean, 'registeredProviders()');
        var answer = {};
        angular.forEach(providers, function (value, key) {
            answer[key] = {
                id: key,
                className: value
            };
        });
        return answer;
    }
    Fabric.registeredProviders = registeredProviders;

    function getSchema(id, className, jolokia, cb) {
        jolokia.execute(Fabric.schemaLookupMBean, 'getSchemaForClass(java.lang.String)', className, {
            method: 'POST',
            success: function (value) {
                cb(Fabric.customizeSchema(id, angular.fromJson(value)));
            }
        });
    }
    Fabric.getSchema = getSchema;

    function getDtoSchema(id, className, jolokia, cb) {
        jolokia.execute(Fabric.schemaLookupMBean, 'getSchemaForClass(java.lang.String)', className, {
            method: 'POST',
            success: function (value) {
                cb(angular.fromJson(value));
            }
        });
    }
    Fabric.getDtoSchema = getDtoSchema;

    function getCurrentContainer(jolokia, fields) {
        var name = jolokia.getAttribute(Fabric.managerMBean, 'CurrentContainerName', { method: 'POST' });
        return jolokia.execute(Fabric.managerMBean, "getContainer(java.lang.String, java.util.List)", name, fields, { method: 'POST' });
    }
    Fabric.getCurrentContainer = getCurrentContainer;

    function getContainerFields(jolokia, name, fields) {
        return jolokia.execute(Fabric.managerMBean, "getContainer(java.lang.String, java.util.List)", name, fields, { method: 'POST' });
    }
    Fabric.getContainerFields = getContainerFields;

    function getRootContainers(jolokia) {
        var fields = ["id", "root"];
        var answer = jolokia.execute(Fabric.managerMBean, "containers(java.util.List)", fields, { method: 'POST' });
        return answer.filter({ root: true }).map(function (v) {
            return v["id"];
        });
    }
    Fabric.getRootContainers = getRootContainers;

    /**
    * Queries the given fields on the contianers in the fabric invoking the given function or returning the results if the fn is null
    * @param jolokia
    * @param fields
    * @param fn
    * @return the result if fn is null
    */
    function getContainersFields(jolokia, fields, fn) {
        if (typeof fn === "undefined") { fn = null; }
        return jolokia.execute(Fabric.managerMBean, "containers(java.util.List)", fields, onSuccess(fn));
    }
    Fabric.getContainersFields = getContainersFields;

    function getOpenShiftDomains(workspace, jolokia, serverUrl, login, password, fn, onError) {
        if (typeof fn === "undefined") { fn = null; }
        if (typeof onError === "undefined") { onError = null; }
        if (hasOpenShiftFabric(workspace) && serverUrl && login && password) {
            var options = onSuccess(fn, { error: onError });
            return jolokia.execute(Fabric.openShiftFabricMBean, "getDomains", serverUrl, login, password, options);
        } else {
            if (fn) {
                fn([]);
            }
            return [];
        }
    }
    Fabric.getOpenShiftDomains = getOpenShiftDomains;

    function getOpenShiftGearProfiles(workspace, jolokia, serverUrl, login, password, fn) {
        if (typeof fn === "undefined") { fn = null; }
        if (hasOpenShiftFabric(workspace) && serverUrl && login && password) {
            return jolokia.execute(Fabric.openShiftFabricMBean, "getGearProfiles", serverUrl, login, password, onSuccess(fn));
        } else {
            if (fn) {
                fn([]);
            }
            return [];
        }
    }
    Fabric.getOpenShiftGearProfiles = getOpenShiftGearProfiles;

    function filterProfiles(jolokia, versionId, profileIds) {
        var profiles = [];
        if (versionId) {
            profiles = jolokia.execute(Fabric.managerMBean, "getProfiles(java.lang.String, java.util.List)", versionId, ['id', 'hidden', 'abstract'], { method: 'POST' });
        }

        profiles = profiles.filter(function (profile) {
            return profileIds.some(function (id) {
                return profile.id === id;
            });
        });
        profiles = profiles.filter((function (profile) {
            return !profile.abstract && !profile.hidden;
        }));

        return profiles.map(function (p) {
            return p.id;
        });
    }
    Fabric.filterProfiles = filterProfiles;

    function getProfileData(jolokia, versionId, profileId, fields) {
        return jolokia.execute(Fabric.managerMBean, "getProfile(java.lang.String, java.lang.String, java.util.List)", versionId, profileId, fields, { method: 'POST' });
    }
    Fabric.getProfileData = getProfileData;

    function getConfigFile(jolokia, versionId, profileId, fileName, fn) {
        if (typeof fn === "undefined") { fn = null; }
        function onResults(answer) {
            return answer ? answer.decodeBase64() : null;
        }

        var callback = !fn ? null : function (result) {
            fn(onResults(result));
        };
        var answer = jolokia.execute(Fabric.managerMBean, "getConfigurationFile(java.lang.String, java.lang.String, java.lang.String)", versionId, profileId, fileName, onSuccess(callback));
        return fn ? answer : onResults(answer);
    }
    Fabric.getConfigFile = getConfigFile;

    /**
    * Creates a link to the given broker configuration so we can connect in the UI
    * @param workspace
    * @param jolokia
    * @param localStorage
    * @param brokerVersion
    * @param brokerProfile
    * @param brokerId
    * @return the link to the broker page
    */
    function brokerConfigLink(workspace, jolokia, localStorage, brokerVersion, brokerProfile, brokerId) {
        var path = Fabric.profileLink(workspace, jolokia, localStorage, brokerVersion, brokerProfile);
        path += "/org.fusesource.mq.fabric.server-" + brokerId + ".properties";
        return path;
    }
    Fabric.brokerConfigLink = brokerConfigLink;

    /**
    * Connects to the broker in a new window
    */
    function connectToBroker($scope, container, postfix) {
        if (typeof postfix === "undefined") { postfix = null; }
        var view = "/jmx/attributes?tab=activemq";
        if (postfix) {
            view += "&" + postfix;
        }
        $scope.doConnect(container, view);
    }
    Fabric.connectToBroker = connectToBroker;

    /**
    * Removes any attributes from the object that are set to an empty string.
    *
    * @method sanitizeJson
    * @for Fabric
    * @param {Object} json
    * @return {Object}
    */
    function sanitizeJson(json) {
        angular.forEach(json, function (value, key) {
            if (value === "") {
                delete json[key];
            }
        });
        return json;
    }
    Fabric.sanitizeJson = sanitizeJson;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function FabricBrokersController($scope, localStorage, $routeParams, $location, jolokia, workspace, $compile, $templateCache) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.maps = {
            group: {},
            profile: {},
            broker: {},
            container: {}
        };

        $scope.showBroker = function (broker) {
            var brokerVersion = broker.version;
            var brokerProfile = broker.profile;
            var brokerId = broker.id;
            var path = Fabric.brokerConfigLink(workspace, jolokia, localStorage, brokerVersion, brokerProfile, brokerId);
            $location.path(path);
        };

        $scope.connectToBroker = function (container, broker) {
            Fabric.connectToBroker($scope, container);
        };

        $scope.createBroker = function (group, profile) {
            var args = {};
            if (group) {
                var groupId = group["id"];
                if (groupId) {
                    args["group"] = groupId;
                }
            }
            if (profile) {
                var profileId = profile["id"];
                if (profileId) {
                    args["profile"] = profileId;
                }
            }
            $location.url("/fabric/mq/createBroker").search(args);
        };

        function matchesFilter(text) {
            var filter = $scope.searchFilter;
            return !filter || (text && text.toLowerCase().has(filter.toLowerCase()));
        }

        $scope.groupMatchesFilter = function (group) {
            return matchesFilter(group.id) || group.profiles.find(function (item) {
                return $scope.profileMatchesFilter(item);
            });
        };

        $scope.profileMatchesFilter = function (profile) {
            return matchesFilter(profile.id) || matchesFilter(profile.group) || matchesFilter(profile.version) || profile.brokers.find(function (item) {
                return $scope.brokerMatchesFilter(item);
            });
        };

        $scope.brokerMatchesFilter = function (broker) {
            return matchesFilter(broker.id) || matchesFilter(broker.group) || matchesFilter(broker.version) || broker.containers.find(function (item) {
                return $scope.containerMatchesFilter(item);
            });
        };

        $scope.containerMatchesFilter = function (container) {
            return matchesFilter(container.id) || matchesFilter(container.group) || matchesFilter(container.profile) || matchesFilter(container.version) || matchesFilter(container.brokerName) || (container.master && $scope.searchFilter && $scope.searchFilter.has("master"));
        };

        if (Fabric.hasMQManager) {
            Core.register(jolokia, $scope, { type: 'exec', mbean: Fabric.mqManagerMBean, operation: "loadBrokerStatus()" }, onSuccess(onBrokerData));
        }

        function onBrokerData(response) {
            if (response) {
                var responseJson = angular.toJson(response.value);
                if ($scope.responseJson === responseJson) {
                    return;
                }

                $scope.responseJson = responseJson;

                var brokers = response.value;

                function findByIdOrCreate(collection, id, map, fn) {
                    var value = collection.find(function (v) {
                        return v && v['id'] === id;
                    });
                    if (!value) {
                        value = fn();
                        value["id"] = id;
                        collection.push(value);

                        var old = map[id];

                        // copy any view related across
                        value["expanded"] = old ? old["expanded"] : true;
                        map[id] = value;
                    }
                    return value;
                }

                $scope.groups = [];
                var maps = $scope.maps;

                angular.forEach(brokers, function (brokerStatus) {
                    var groupId = brokerStatus.group || "Unknown";
                    var profileId = brokerStatus.profile || "Unknown";
                    var brokerId = brokerStatus.brokerName || "Unknown";
                    var containerId = brokerStatus.container;
                    var versionId = brokerStatus.version || "1.0";

                    var group = findByIdOrCreate($scope.groups, groupId, maps.group, function () {
                        return {
                            profiles: []
                        };
                    });
                    var profile = findByIdOrCreate(group.profiles, profileId, maps.profile, function () {
                        return {
                            group: groupId,
                            version: versionId,
                            requirements: {
                                minimumInstances: brokerStatus.minimumInstances
                            },
                            brokers: [],
                            containers: {}
                        };
                    });
                    var connectTo = (brokerStatus.networks || []).join(",");
                    var broker = findByIdOrCreate(profile.brokers, brokerId, maps.broker, function () {
                        return {
                            group: groupId,
                            profile: profileId,
                            version: versionId,
                            containers: [],
                            connectTo: connectTo
                        };
                    });
                    if (containerId) {
                        // lets create a container object per broker for the N+1 case
                        var container = findByIdOrCreate(broker.containers, containerId, maps.container, function () {
                            return brokerStatus;
                        });
                        if (container.master) {
                            container.masterTooltip = " is the master for broker: " + brokerId;
                        }
                        profile.containers[containerId] = container;
                    }
                });

                // update the stats
                angular.forEach($scope.groups, function (group) {
                    angular.forEach(group.profiles, function (profile) {
                        angular.forEach(profile.brokers, function (broker) {
                            broker.containers = broker.containers.sortBy(function (c) {
                                return c.id;
                            });
                        });
                        var count = Object.values(profile.containers).length;
                        var required = profile.requirements.minimumInstances || 0;
                        profile.requireStyle = Fabric.containerCountBadgeStyle(required, count);
                        profile.count = count;
                        profile.requiredToolTip = "this profile requires " + Core.maybePlural(required, "container") + " to be running but is currently running " + Core.maybePlural(count, "container");
                        if (required > count) {
                            profile.requiredToolTip += ". click here to create more containers";
                        }
                    });
                });

                Core.$apply($scope);
            }
        }
    }
    Fabric.FabricBrokersController = FabricBrokersController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    var ProfileSelector = (function () {
        function ProfileSelector() {
            this.restrict = 'A';
            this.replace = true;
            this.templateUrl = Fabric.templatePath + "profileSelector.html";
            this.scope = {
                selectedProfiles: '=fabricProfileSelector',
                versionId: '=',
                filterWatch: '@',
                selectedWatch: '@',
                clearOnVersionChange: '@',
                noLinks: '@',
                showHeader: '@',
                useCircles: '@',
                expanded: '@',
                excludedProfiles: '=',
                includedProfiles: '='
            };
            this.controller = function ($scope, $element, $attrs, workspace, jolokia, localStorage, $location) {
                $scope.profiles = [];
                $scope.responseJson = '';
                $scope.filterText = '';
                $scope.clearOnVersionChange = false;
                $scope.noLinks = false;
                $scope.selectedAll = false;
                $scope.indeterminate = false;
                $scope.showFilter = true;
                $scope.useCircles = false;
                $scope.expanded = false;
                $scope.tree = [];

                $scope.showProfile = function (profile) {
                    return Core.matchFilterIgnoreCase(profile.id, $scope.filterText);
                };

                $scope.showBranch = function (branch) {
                    return $scope.filterText.isBlank() || branch.profiles.some(function (profile) {
                        return Core.matchFilterIgnoreCase(profile.id, $scope.filterText);
                    });
                };

                $scope.goto = function (profile) {
                    Fabric.gotoProfile(workspace, jolokia, localStorage, $location, $scope.versionId, profile);
                };

                $scope.render = function (response) {
                    var responseJson = angular.toJson(response.value);
                    if ($scope.responseJson !== responseJson) {
                        $scope.responseJson = responseJson;
                        var selected = $scope.selectedProfiles;
                        $scope.profiles = response.value.sortBy(function (profile) {
                            return profile.id;
                        });
                        angular.forEach(selected, function (profile) {
                            var p = $scope.profiles.find(function (p) {
                                return p.id === profile.id;
                            });
                            if (p && profile) {
                                p.selected = profile.selected;
                            }
                        });

                        $scope.profiles = $scope.profiles.exclude(function (p) {
                            return p.hidden;
                        });

                        if ($scope.excludedProfiles) {
                            $scope.profiles = $scope.excluded();
                        }

                        if ($scope.includedProfiles) {
                            $scope.profiles = $scope.included();
                        }

                        var paths = [];

                        $scope.profiles.each(function (profile) {
                            var path = profile.id.split('-');
                            profile.name = path.last();
                            profile.path = path.exclude(profile.name).join(' / ');
                            paths.push(profile.path);
                        });

                        paths = paths.unique().sortBy('length').sortBy(function (n) {
                            return n;
                        });
                        var tree = [];
                        paths.forEach(function (path) {
                            var branch = {
                                expanded: $scope.expanded,
                                path: path,
                                profiles: $scope.profiles.filter(function (profile) {
                                    return profile.path === path;
                                })
                            };
                            tree.push(branch);
                        });
                        $scope.tree = tree;

                        Core.$apply($scope);
                    }
                };

                $scope.excluded = function () {
                    return $scope.profiles.exclude(function (p) {
                        return $scope.excludedProfiles.some(function (e) {
                            return e === p.id;
                        });
                    });
                };

                $scope.included = function () {
                    return $scope.profiles.exclude(function (p) {
                        return $scope.includedProfiles.none(function (e) {
                            return e === p.id;
                        });
                    });
                };

                $scope.isOpen = function (branch) {
                    if ($scope.filterText !== '') {
                        return "opened";
                    }
                    if (branch.expanded) {
                        return "opened";
                    }
                    return "closed";
                };

                $scope.isOpenIcon = function (branch) {
                    if (branch.expanded) {
                        return "icon-folder-open";
                    }
                    return "icon-folder-closed";
                };

                $scope.$watch('includedProfiles', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        $scope.init();
                    }
                }, true);

                $scope.$watch('excludedProfiles', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        $scope.init();
                    }
                }, true);

                $scope.abstract = function () {
                    return $scope.profiles.filter(function (profile) {
                        return profile['abstract'];
                    });
                };

                $scope.selected = function () {
                    return $scope.profiles.filter(function (profile) {
                        return profile['selected'];
                    });
                };

                $scope.selectAll = function () {
                    $scope.profiles.each(function (profile) {
                        profile.selected = true;
                    });
                };

                $scope.selectNone = function () {
                    $scope.profiles.each(function (profile) {
                        delete profile.selected;
                    });
                };

                $scope.$parent.profileSelectAll = $scope.selectAll;
                $scope.$parent.profileSelectNone = $scope.selectNone;

                $scope.getSelectedClass = function (profile) {
                    if (profile.abstract) {
                        return "abstract";
                    }
                    if (profile.selected) {
                        return "selected";
                    }
                    return "";
                };

                $scope.$watch('selectedAll', function (newValue, oldValue) {
                    if (!$scope.indeterminate && newValue !== oldValue) {
                        if (newValue) {
                            $scope.selectAll();
                        } else {
                            $scope.selectNone();
                        }
                    }
                });

                $scope.$watch('profiles', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        $scope.selectedProfiles = $scope.selected();
                    }
                }, true);

                $scope.$on("fabricProfileRefresh", function () {
                    if ($scope.versionId) {
                        jolokia.request({
                            type: 'exec', mbean: Fabric.managerMBean,
                            operation: 'getProfiles(java.lang.String, java.util.List)',
                            arguments: [$scope.versionId, ['id', 'hidden']]
                        }, {
                            method: 'POST',
                            success: function (response) {
                                $scope.render(response);
                            }
                        });
                    }
                });

                $scope.init = function () {
                    Fabric.log.debug("Initializing profile selector, version: ", $scope.versionId);
                    $scope.responseJson = null;
                    Core.unregister(jolokia, $scope);
                    if ($scope.versionId !== '') {
                        if ($scope.clearOnVersionChange) {
                            $scope.selectNone();
                        }
                        if ($scope.versionId) {
                            Core.register(jolokia, $scope, {
                                type: 'exec',
                                mbean: Fabric.managerMBean,
                                operation: 'getProfiles(java.lang.String, java.util.List)',
                                arguments: [$scope.versionId, ['id', 'hidden', 'abstract']]
                            }, onSuccess($scope.render, {
                                error: function (response) {
                                    // TODO somewhere this directive is kinda getting leaked, need to track down
                                    Fabric.log.debug("Error fetching profiles: ", response.error, " unregistering poller");
                                    Core.unregister(jolokia, $scope);
                                }
                            }));
                        }
                    }
                };

                $scope.$watch('versionId', function (newValue, oldValue) {
                    Fabric.log.debug("versionId, newValue: ", newValue, " oldValue: ", oldValue);
                    if ($scope.versionId && $scope.versionId !== '') {
                        Fabric.log.debug("Unregistering old poller");
                        Core.unregister(jolokia, $scope);
                        jolokia.request({
                            type: 'exec',
                            mbean: Fabric.managerMBean,
                            operation: 'versions()',
                            arguments: []
                        }, {
                            method: 'POST',
                            success: function (response) {
                                if (response.value.some(function (version) {
                                    return version.id === newValue;
                                })) {
                                    Fabric.log.debug("registering new poller");
                                    $scope.init();
                                    Core.$apply($scope);
                                }
                            },
                            error: function (response) {
                                Core.$apply($scope);
                            }
                        });
                    }
                });
            };
            this.link = function ($scope, $element, $attrs) {
                var selector = $element.find('#selector');

                if (!angular.isDefined($attrs['showHeader'])) {
                    $scope.showFilter = true;
                } else {
                    $scope.showFilter = $attrs['showHeader'];
                }

                if (angular.isDefined($attrs['filterWatch'])) {
                    $scope.$parent.$watch($attrs['filterWatch'], function (newValue, oldValue) {
                        if (newValue !== oldValue) {
                            $scope.filterText = newValue;
                        }
                    });
                }

                $scope.$watch('indeterminate', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        selector.prop('indeterminate', $scope.indeterminate);
                    }
                });

                $scope.$watch('selectedProfiles', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if ($scope.selectedProfiles.length > 0) {
                            if ($scope.selectedProfiles.length !== $scope.profiles.length) {
                                $scope.indeterminate = true;
                                $scope.selectedAll = false;

                                $scope.$parent.profileSomeSelected = true;
                                $scope.$parent.profileNoneSelected = false;
                                $scope.$parent.profileAllSelected = false;
                            } else {
                                $scope.indeterminate = false;
                                $scope.selectedAll = true;

                                $scope.$parent.profileSomeSelected = false;
                                $scope.$parent.profileNoneSelected = false;
                                $scope.$parent.profileAllSelected = true;
                            }
                        } else {
                            $scope.indeterminate = false;
                            $scope.selectedAll = false;

                            $scope.$parent.profileSomeSelected = false;
                            $scope.$parent.profileNoneSelected = true;
                            $scope.$parent.profileAllSelected = false;
                        }
                    }
                }, true);
            };
        }
        return ProfileSelector;
    })();
    Fabric.ProfileSelector = ProfileSelector;
})(Fabric || (Fabric = {}));
/**
* @module Fabric
* @main Fabric
*/
var Fabric;
(function (Fabric) {
    Fabric.templatePath = 'app/fabric/html/';
    Fabric.activeMQTemplatePath = 'app/activemq/html/';

    Fabric.currentContainerId = '';
    Fabric.currentContainer = {};

    angular.module('fabric', ['bootstrap', 'ui.bootstrap', 'ui.bootstrap.dialog', 'ngResource', 'ngGrid', 'hawtio-forms', 'hawtioCore', 'ngDragDrop', 'wiki']).config(function ($routeProvider) {
        $routeProvider.when('/fabric/containers/createContainer', { templateUrl: Fabric.templatePath + 'createContainer.html', reloadOnSearch: false }).when('/fabric/map', { templateUrl: Fabric.templatePath + 'map.html' }).when('/fabric/clusters/*page', { templateUrl: Fabric.templatePath + 'clusters.html' }).when('/fabric/containers', { templateUrl: Fabric.templatePath + 'containers.html', reloadOnSearch: false }).when('/fabric/container/:containerId', { templateUrl: Fabric.templatePath + 'container.html', reloadOnSearch: false }).when('/fabric/assignProfile', { templateUrl: Fabric.templatePath + 'assignProfile.html' }).when('/fabric/activeProfiles', { templateUrl: Fabric.templatePath + 'activeProfiles.html' }).when('/wiki/profile/:versionId/:profileId/editFeatures', { templateUrl: Fabric.templatePath + 'editFeatures.html' }).when('/fabric/profile/:versionId/:profileId/:fname', { templateUrl: Fabric.templatePath + 'pid.html' }).when('/fabric/view', { templateUrl: Fabric.templatePath + 'fabricView.html', reloadOnSearch: false }).when('/fabric/migrate', { templateUrl: Fabric.templatePath + 'migrateVersions.html' }).when('/fabric/patching', { templateUrl: Fabric.templatePath + 'patching.html' }).when('/fabric/configurations/:versionId/:profileId', { templateUrl: 'app/osgi/html/configurations.html' }).when('/fabric/configuration/:versionId/:profileId/:pid', { templateUrl: 'app/osgi/html/pid.html' }).when('/fabric/configuration/:versionId/:profileId/:pid/:factoryPid', { templateUrl: 'app/osgi/html/pid.html' }).when('/fabric/mq/brokers', { templateUrl: Fabric.templatePath + 'brokers.html' }).when('/fabric/mq/brokerDiagram', { templateUrl: Fabric.activeMQTemplatePath + 'brokerDiagram.html', reloadOnSearch: false }).when('/fabric/mq/brokerNetwork', { templateUrl: Fabric.templatePath + 'brokerNetwork.html' }).when('/fabric/mq/createBroker', { templateUrl: Fabric.templatePath + 'createBroker.html' }).when('/fabric/camel/diagram', { templateUrl: 'app/camel/html/fabricDiagram.html', reloadOnSearch: false }).when('/fabric/api', { templateUrl: Fabric.templatePath + 'apis.html' }).when('/fabric/api/wsdl', { templateUrl: 'app/api/html/wsdl.html' }).when('/fabric/api/wadl', { templateUrl: 'app/api/html/wadl.html' }).when('/fabric/test', { templateUrl: Fabric.templatePath + 'test.html' });
    }).directive('fabricVersionSelector', function ($templateCache) {
        return Fabric.VersionSelector($templateCache);
    }).directive('fabricProfileSelector', function () {
        return new Fabric.ProfileSelector();
    }).directive('fabricContainerList', function () {
        return new Fabric.ContainerList();
    }).directive('fabricProfileDetails', function () {
        return new Fabric.ProfileDetails();
    }).directive('fabricActiveProfileList', function () {
        return new Fabric.ActiveProfileList();
    }).directive('fabricProfileLink', function (workspace, jolokia, localStorage) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attrs) {
                var profileId = $attrs['fabricProfileLink'];

                if (profileId && !profileId.isBlank() && Fabric.fabricCreated(workspace)) {
                    var container = Fabric.getCurrentContainer(jolokia, ['versionId']);
                    var versionId = container['versionId'];
                    if (versionId && !versionId.isBlank()) {
                        var url = '#' + Fabric.profileLink(workspace, jolokia, localStorage, versionId, profileId);
                        if (angular.isDefined($attrs['file'])) {
                            url = url + "/" + $attrs['file'];
                        }

                        $element.attr('href', url);
                    }
                }
            }
        };
    }).directive('fabricContainers', function ($location, jolokia, workspace, $compile) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attrs) {
                var model = $attrs['fabricContainers'];
                var profileId = $attrs['profile'];
                var version = $scope.versionId || $scope.version || "1.0";
                if (model && !model.isBlank() && profileId && !profileId.isBlank()) {
                    // lets expose the $scope.connect object!
                    Fabric.initScope($scope, $location, jolokia, workspace);
                    var containerIds = Fabric.getContainerIdsForProfile(jolokia, version, profileId);
                    Fabric.log.info("Searching for containers for profile: " + profileId + " version " + version + ". Found: " + containerIds);
                    $scope[model] = containerIds;

                    $scope["onCancel"] = function () {
                        console.log("In our new cancel thingy!");
                    };

                    // now lets add the connect dialog
                    var dialog = $("<div ng-include=\"'app/fabric/html/connectToContainerDialog.html'\"></div>");
                    var answer = $compile(dialog)($scope);
                    $element.append(answer);
                }
            }
        };
    }).directive('fabricContainerLink', function ($location, jolokia, workspace) {
        return {
            restrict: 'A',
            scope: { containerModel: '@fabricContainerLink' },
            link: function ($scope, $element, $attrs) {
                $scope.$watch("containerModel", function (nv) {
                    var modelName = $scope.containerModel;
                    var containerId = modelName;
                    var container = null;
                    if (modelName && !modelName.isBlank()) {
                        // lets check if the value is a model object containing the container details
                        var modelValue = Core.pathGet($scope, modelName);
                        if (angular.isObject(modelValue)) {
                            var id = modelValue["container"] || modelValue["containerId"] || modelValue["id"];
                            if (id && modelValue["provisionResult"]) {
                                container = modelValue;
                                containerId = id;
                            }
                        }
                        if (!container) {
                            var fields = ["alive", "provisionResult", "versionId", "jmxDomains"];
                            container = Fabric.getContainerFields(jolokia, containerId, fields);
                        }

                        var link = "#/fabric/container/" + containerId;
                        var title = Fabric.statusTitle(container) || "container " + containerId;
                        var icon = Fabric.statusIcon(container) || "";

                        var html = "<a href='" + link + "' title='" + title + "'><i class='" + icon + "'></i> " + containerId + "</a>";
                        $element.html(html);
                    } else {
                        $element.html(" ");
                    }
                });
            }
        };
    }).directive('fabricContainerConnect', function ($location, jolokia) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attrs) {
                var containerId = $attrs['fabricContainerConnect'];
                var view = $attrs['view'];
                if (containerId && !containerId.isBlank()) {
                    //var fields = ["parentId", "profileIds", "versionId", "provisionResult", "jolokiaUrl", "root", 'jmxDomains'];
                    var fields = ["jolokiaUrl"];

                    //Fabric.initScope($scope, $location, jolokia, workspace);
                    var connectFn = function () {
                        var container = Fabric.getContainerFields(jolokia, containerId, fields);
                        Fabric.log.info("Connecting to container id " + containerId + " view + " + view);
                        container["id"] = containerId;
                        $scope.doConnect(container, view);
                        Core.$apply($scope);
                    };
                    $element.on("click", connectFn);
                }
            }
        };
    }).directive('fabricVersionLink', function (workspace, jolokia, localStorage) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attrs) {
                var versionLink = $attrs['fabricVersionLink'];

                if (versionLink && !versionLink.isBlank() && Fabric.fabricCreated(workspace)) {
                    var container = Fabric.getCurrentContainer(jolokia, ['versionId']);
                    var versionId = container['versionId'] || "1.0";
                    if (versionId && !versionId.isBlank()) {
                        var url = "#/wiki/branch/" + versionId + "/" + Core.trimLeading(versionLink, "/");
                        $element.attr('href', url);
                    }
                }
            }
        };
    }).factory('serviceIconRegistry', function () {
        return Fabric.serviceIconRegistry;
    }).factory('containerIconRegistry', function () {
        return Fabric.containerIconRegistry;
    }).run(function ($location, workspace, jolokia, viewRegistry, pageTitle, helpRegistry, $rootScope, postLoginTasks, preferencesRegistry) {
        viewRegistry['fabric'] = Fabric.templatePath + 'layoutFabric.html';

        pageTitle.addTitleElement(function () {
            return Fabric.currentContainerId;
        });

        postLoginTasks.addTask('fabricFetchContainerName', function () {
            if (Fabric.currentContainerId === '' && Fabric.fabricCreated(workspace)) {
                jolokia.request({
                    type: 'exec',
                    mbean: Fabric.managerMBean,
                    operation: 'currentContainer()',
                    arguments: []
                }, onSuccess(function (response) {
                    if (!response.value) {
                        return;
                    }
                    Fabric.currentContainer = response.value;

                    Fabric.currentContainerId = Fabric.currentContainer['id'];
                    if ('container' in Perspective.metadata) {
                        Core.pathSet(Perspective.metadata, ['container', 'label'], Fabric.currentContainerId);
                        Core.pathSet(Perspective.metadata, ['container', 'icon'], Fabric.getTypeIcon(Fabric.currentContainer));
                    }
                    Core.$apply($rootScope);
                }));
            }
        });

        preferencesRegistry.addTab("Fabric", "app/fabric/html/preferences.html", function () {
            return Fabric.isFMCContainer(workspace);
        });

        workspace.topLevelTabs.push({
            id: "fabric.runtime",
            content: "Runtime",
            title: "Manage your containers in this fabric",
            isValid: function (workspace) {
                return Fabric.isFMCContainer(workspace);
            },
            href: function () {
                return "#/fabric/containers";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("fabric");
            }
        });
        workspace.topLevelTabs.push({
            id: "fabric.configuration",
            content: "Wiki",
            title: "View the documentation and configuration of your profiles in Fabric",
            isValid: function (workspace) {
                var answer = Fabric.isFMCContainer(workspace);
                if (answer) {
                    // must be in fabric perspective as we have wiki in container perspective as well which is not this plugin
                    var currentId = Perspective.currentPerspectiveId($location, workspace, jolokia, localStorage);
                    answer = "fabric" === currentId;
                }
                return answer;
            },
            href: function () {
                return "#/wiki/branch/" + Fabric.getActiveVersion($location) + "/view/fabric/profiles";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("/wiki") && (workspace.linkContains("fabric", "profiles") || workspace.linkContains("editFeatures"));
            }
        });
        workspace.topLevelTabs.push({
            id: "fabric.insight",
            content: "Insight",
            title: "View insight into your fabric looking at logs, metrics and messages across the fabric",
            isValid: function (workspace) {
                return Fabric.isFMCContainer(workspace) && Insight.hasInsight(workspace);
            },
            href: function () {
                return "#/insight/all?p=insight";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("/insight");
            }
        });

        helpRegistry.addUserDoc('fabric', 'app/fabric/doc/help.md', function () {
            return Fabric.isFMCContainer(workspace);
        });

        // don't need to pass the isValid parameter in subsequent calls...
        helpRegistry.addDevDoc("fabric", 'app/fabric/doc/developer.md');
    });

    hawtioPluginLoader.addModule('fabric');
})(Fabric || (Fabric = {}));
/**
* @module Fabric
*/
var Fabric;
(function (Fabric) {
    function PreferencesController($scope, localStorage) {
        Core.initPreferenceScope($scope, localStorage, {
            'fabricAlwaysPrompt': {
                'value': false,
                'converter': Core.parseBooleanValue
            },
            'fabricEnableMaps': {
                'value': true,
                'converter': Core.parseBooleanValue
            },
            'fabricVerboseNotifications': {
                'value': true,
                'converter': Core.parseBooleanValue
            }
        });
    }
    Fabric.PreferencesController = PreferencesController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    /**
    * Returns the resolvers for the given schema id (child, ssh, jclouds, openshift, docker)
    */
    function getResolvers(id) {
        var answer;
        switch (id) {
            case 'child':
                answer = [];
                break;
            case 'ssh':
                answer = ['localip', 'localhostname', 'publicip', 'publichostname', 'manualip'];
                break;
            case 'jclouds':
                answer = ['localip', 'localhostname', 'publicip', 'publichostname', 'manualip'];
                break;
            case 'openshift':
                answer = [];
                break;
            case 'docker':
                answer = [];
                break;
        }
        return answer;
    }
    Fabric.getResolvers = getResolvers;

    function customizeSchema(id, schema) {
        // console.log("Schema: ", schema);
        Core.pathSet(schema, ["properties", "name", "required"], true);
        Core.pathSet(schema, ['properties', 'name', 'input-attributes', 'ng-pattern'], "/^[a-zA-Z0-9_-]*$/");

        delete schema.properties['metadataMap'];
        delete schema.properties['zookeeperUrl'];
        delete schema.properties['zookeeperPassword'];
        delete schema.properties['globalResolver'];
        delete schema.properties['zooKeeperServerPort'];
        delete schema.properties['zooKeeperServerConnectionPort'];
        delete schema.properties['agentEnabled'];
        delete schema.properties['autoImportEnabled'];
        delete schema.properties['importPath'];
        delete schema.properties['users'];

        // we cannot pass in system properties (you can use jvmOpt instead)
        delete schema.properties['systemProperties'];

        [
            'zooKeeperServerInitLimit',
            'zooKeeperServerTickTime',
            'zooKeeperServerSyncLimit',
            'zooKeeperServerDataDir',
            'waitForProvision',
            'ensembleStart',
            'migrationTimeout',
            'dataStoreProperties'].forEach(function (attr) {
            Core.pathSet(schema, ['properties', attr, 'control-attributes', 'ng-show'], 'entity.ensembleServer');
        });

        Core.pathSet(schema, ['properties', 'providerType', 'type'], 'hidden');
        Core.pathSet(schema, ['properties', 'profiles', 'type'], 'hidden');
        Core.pathSet(schema, ['properties', 'version', 'type'], 'hidden');

        Core.pathSet(schema.properties, ['name', 'label'], 'Container Name');
        Core.pathSet(schema.properties, ['name', 'tooltip'], 'Name of the container to create (or prefix of the container name if you create multiple containers)');

        Core.pathSet(schema.properties, ['number', 'label'], 'Number of containers');
        Core.pathSet(schema.properties, ['number', 'tooltip'], 'The number of containers to create; when set higher than 1 a number will be appended to each container name. Max value: 99');
        Core.pathSet(schema.properties, ['number', 'input-attributes', 'min'], '1');
        Core.pathSet(schema.properties, ['number', 'input-attributes', 'max'], '99');
        Core.pathSet(schema.properties, ['number', 'input-attributes', 'ng-pattern'], "/^[1-9][0-9]?$/");
        Core.pathSet(schema.properties, ['number', 'required'], true);

        // mark properties as autofill to avoid issues with angular missing autofill events
        Core.pathSet(schema.properties, ['login', 'input-attributes', "autofill"], "true");
        Core.pathSet(schema.properties, ['password', 'input-attributes', "autofill"], "true");

        Core.pathSet(schema.properties, ['jmxUser', 'input-attributes', "autofill"], "true");
        Core.pathSet(schema.properties, ['jmxUser', 'tooltip'], 'The username for connecting to the container using JMX');

        Core.pathSet(schema.properties, ['jmxPassword', 'input-attributes', "autofill"], "true");
        Core.pathSet(schema.properties, ['jmxPassword', 'tooltip'], 'The password for connecting to the container using JMX');

        Core.pathSet(schema.properties, ['resolver', 'input-element'], "select");
        Core.pathSet(schema.properties, ['resolver', 'input-attributes', "ng-options"], "r for r in resolvers");

        switch (id) {
            case 'child':
                delete schema.properties['manualIp'];
                delete schema.properties['preferredAddress'];
                delete schema.properties['resolver'];
                delete schema.properties['ensembleServer'];
                delete schema.properties['proxyUri'];
                delete schema.properties['adminAccess'];
                delete schema.properties['minimumPort'];
                delete schema.properties['maximumPort'];
                schema.properties['jmxPassword']['type'] = 'password';
                schema.properties['saveJmxCredentials'] = {
                    'type': 'boolean'
                };
                Core.pathSet(schema.properties, ['saveJmxCredentials', 'tooltip'], 'Remember credentials when connecting to container (avoid prompting user to enter credentials)');

                Core.pathSet(schema.properties, ['parent', 'label'], 'Parent Container');
                Core.pathSet(schema.properties, ['parent', 'tooltip'], 'The name of the parent container used to create the child container');
                Core.pathSet(schema.properties, ['parent', 'input-element'], "select");
                Core.pathSet(schema.properties, ['parent', 'input-attributes', "ng-options"], "c for c in child.rootContainers");

                bulkSet(schema, ["jmxUser", "jmxPassword", "parent"], 'required', true);
                schema['tabs'] = {
                    'Common': ['name', 'parent', 'jmxUser', 'jmxPassword', 'saveJmxCredentials', 'number'],
                    'Advanced': ['*']
                };
                break;

            case 'ssh':
                delete schema.properties['jmxUser'];
                delete schema.properties['jmxPassword'];
                delete schema.properties['parent'];

                bulkSet(schema, ['host'], 'required', true);
                Core.pathSet(schema.properties, ['password', 'type'], 'password');

                schema['tabs'] = {
                    'Common': ['name', 'host', 'port', 'username', 'password', 'privateKeyFile', 'passPhrase'],
                    'Advanced': ['*']
                };
                break;

            case 'jclouds':
                delete schema.properties['jmxUser'];
                delete schema.properties['jmxPassword'];
                delete schema.properties['parent'];

                schema['tabs'] = {
                    'Common': ['name', 'owner', 'credential', 'providerName', 'imageId', 'hardwareId', 'locationId', 'number', 'instanceType'],
                    'Advanced': ['*']
                };
                break;

            case 'openshift':
                delete schema.properties['jmxUser'];
                delete schema.properties['jmxPassword'];
                delete schema.properties['parent'];
                delete schema.properties['manualIp'];
                delete schema.properties['preferredAddress'];
                delete schema.properties['ensembleServer'];
                delete schema.properties['proxyUri'];
                delete schema.properties['adminAccess'];
                delete schema.properties['path'];
                delete schema.properties['bindAddress'];
                delete schema.properties['hostNameContext'];
                delete schema.properties['resolver'];

                schema.properties['serverUrl']['default'] = 'openshift.redhat.com';

                // openshift must select publichostname as the resolver
                Core.pathSet(schema.properties, ['resolver', 'default'], 'publichostname');
                Core.pathSet(schema.properties, ['serverUrl', 'label'], 'OpenShift Broker');
                Core.pathSet(schema.properties, ['serverUrl', 'tooltip'], 'The OpenShift broker host name of the cloud to create the container inside. This is either the URL for your local OpenShift Enterprise installation, or its the public OpenShift online URL: openshift.redhat.com');
                Core.pathSet(schema.properties, ['login', 'label'], 'OpenShift Login');
                Core.pathSet(schema.properties, ['login', 'tooltip'], 'Your personal login to the OpenShift portal');
                Core.pathSet(schema.properties, ['login', 'input-attributes', "autofill"], "true");
                Core.pathSet(schema.properties, ['password', 'label'], 'OpenShift Password');
                Core.pathSet(schema.properties, ['password', 'tooltip'], 'Your personal password on the OpenShift portal');
                Core.pathSet(schema.properties, ['password', 'type'], 'password');

                // openshift only allows lowercase a-z and numbers
                Core.pathSet(schema.properties, ['name', 'input-attributes', 'ng-pattern'], "/^[a-z0-9]*$/");

                // add an extra property to make it easy to login
                /*
                */
                /*
                Core.pathSet(schema.properties, ['tryLogin', 'label'], 'Try');
                */
                /*
                Core.pathSet(schema.properties, ['tryLogin', 'input-element'], "button");
                Core.pathSet(schema.properties, ['tryLogin', 'input-attributes', "class"], "btn");
                Core.pathSet(schema.properties, ['tryLogin', 'input-attributes', "ng-click"], "openShift.login()");
                */
                //Core.pathSet(schema.properties, ['tryLogin', 'input-attributes', "ng-disable"], "!entity.login || !entity.password || !entity.serverUrl");
                /*
                */
                Core.pathSet(schema.properties, ['tryLogin', 'type'], 'string');
                Core.pathSet(schema.properties, ['tryLogin', 'input-attributes', "ng-model"], "openShift.tryLogin");
                Core.pathSet(schema.properties, ['tryLogin', 'label'], 'Authenticate');
                Core.pathSet(schema.properties, ['tryLogin', 'tooltip'], 'Authenticate with the OpenShift Broker using your login and password');
                Core.pathSet(schema.properties, ['tryLogin', 'formTemplate'], '<a ng-click="openShift.login()" ng-disabled="!entity.login || !entity.password || !entity.serverUrl" ' + 'title="Test you entered the correct OpenShift Broker, login and password" class="btn btn-primary">Login to OpenShift</a>' + '<div class="alert" ng-show="openShift.loginFailed" ' + 'title="Are you sure you correctly entered the OpenShift Broker, login and password correctly?">Login failed</div>');

                /*
                Core.pathSet(schema.properties, ['tryLogin', 'formTemplate'], '<button ng-click="openShift.login()" title="Test you entered the correct OpenShift Broker, login and password">Try Login</button>');
                */
                Core.pathSet(schema.properties, ['domain', 'label'], 'OpenShift Domain');
                Core.pathSet(schema.properties, ['domain', 'tooltip'], 'What is your unique domain name used for applications you create on OpenShift. Often this is your own user name or group name');
                Core.pathSet(schema.properties, ['domain', 'input-element'], "select");
                Core.pathSet(schema.properties, ['domain', 'input-attributes', "ng-options"], "c for c in openShift.domains");

                Core.pathSet(schema.properties, ['gearProfile', 'tooltip'], 'Which kind of gear to create');
                Core.pathSet(schema.properties, ['gearProfile', 'input-element'], "select");
                Core.pathSet(schema.properties, ['gearProfile', 'input-attributes', "ng-options"], "c for c in openShift.gearProfiles");

                bulkSet(schema, ['serverUrl', 'login', 'password', 'domain'], 'required', true);
                schema['tabs'] = {
                    'Common': ['name', 'serverUrl', 'login', 'password', 'tryLogin', 'domain', 'gearProfile', 'number'],
                    'Advanced': ['environmentalVariables', 'jvmOpts', '*']
                };
                break;

            case 'docker':
                delete schema.properties['jmxUser'];
                delete schema.properties['jmxPassword'];
                delete schema.properties['parent'];
                delete schema.properties['manualIp'];
                delete schema.properties['preferredAddress'];
                delete schema.properties['resolver'];
                delete schema.properties['ensembleServer'];
                delete schema.properties['proxyUri'];
                delete schema.properties['adminAccess'];
                delete schema.properties['path'];
                delete schema.properties['bindAddress'];
                delete schema.properties['hostNameContext'];

                schema['tabs'] = {
                    'Common': ['name', 'number'],
                    'Advanced': ['environmentalVariables', 'jvmOpts', '*']
                };
                break;

            default:
        }

        return schema;
    }
    Fabric.customizeSchema = customizeSchema;

    function bulkSet(schema, properties, field, value) {
        properties.each(function (name) {
            Core.pathSet(schema, ['properties', name, field], value);
        });
    }

    function setGlobalResolverEnum(schema) {
        var globalResolverEnum = ['localip', 'localhostname', 'publicip', 'publichostname'];
        Core.pathSet(schema, ['properties', 'globalResolver', 'enum'], globalResolverEnum);
    }
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ContainerController($scope, $routeParams, $location, jolokia, workspace, userDetails) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        if ($scope.inDashboard) {
            $scope.operation = 'getContainer(java.lang.String, java.util.List)';
        } else {
            $scope.operation = 'getContainer(java.lang.String)';
        }

        $scope.mavenRepoUploadUri = "upload";
        $scope.mavenRepoDownloadUri = "download";

        $scope.username = userDetails.username;
        $scope.password = userDetails.password;

        $scope.tab = $routeParams['tab'];
        if (!$scope.tab) {
            $scope.tab = 'Status';
        }

        $scope.$watch('tab', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search({ tab: newValue }).replace();
            }
        });

        /*
        // handy for working around any randomly added fields that won't marshal
        $scope.fields = jolokia.execute(Fabric.managerMBean, 'getFields(java.lang.String)', 'io.fabric8.api.Container');
        $scope.fields.remove('fabricService');
        $scope.operation = 'getContainer(java.lang.String, java.util.List)'
        */
        $scope.loading = true;

        $scope.containerId = $routeParams.containerId;

        $scope.addToDashboardLink = function () {
            var href = "#/fabric/container/:containerId";
            var routeParams = angular.toJson($routeParams);
            var title = $scope.containerId;
            return "#/dashboard/add?tab=dashboard&href=" + encodeURIComponent(href) + "&routeParams=" + encodeURIComponent(routeParams) + "&title=" + encodeURIComponent(title);
        };

        $scope.versionTitle = "Migrate to:";

        $scope.selectedProfiles = [];
        $scope.selectedProfilesDialog = [];
        $scope.selectedProfilesString = '';

        $scope.addProfileDialog = new UI.Dialog();
        $scope.deleteProfileDialog = new UI.Dialog();
        $scope.deleteContainerDialog = new UI.Dialog();

        $scope.$watch('selectedProfiles', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.selectedProfilesString = '';
                $scope.selectedProfiles.each(function (p) {
                    $scope.selectedProfilesString += '<li>' + p.id + '</li>\n';
                });
            }
        }, true);

        $scope.stop = function () {
            Fabric.doStopContainer($scope, jolokia, $scope.containerId);
        };

        $scope.start = function () {
            Fabric.doStartContainer($scope, jolokia, $scope.containerId);
        };

        $scope.statusIcon = function () {
            return Fabric.statusIcon($scope.row);
        };

        $scope.getGitURL = function (jolokiaUrl) {
            var answer = jolokiaUrl ? jolokiaUrl.replace("jolokia", "git/fabric") : null;
            if (answer !== null) {
                var command = "git clone -b 1.0 " + answer;
                if ($scope.username !== null && $scope.password !== null) {
                    command = command.replace("://", "://" + $scope.username + ":" + $scope.password + "@");
                }
                answer = command;
            }
            return answer;
        };

        $scope.getSshURL = function (sshUrl) {
            if (!sshUrl) {
                return '';
            }
            var answer = sshUrl;
            if ($scope.username !== null && $scope.password !== null) {
                var parts = sshUrl.split(":");
                if (parts.length === 2) {
                    answer = "ssh -p " + parts[1] + " " + $scope.username + "@" + parts[0];
                }
            }
            return answer;
        };

        $scope.getJmxURL = function (jmxUrl) {
            return jmxUrl;
        };

        $scope.getType = function () {
            if ($scope.row) {
                if ($scope.row.ensembleServer) {
                    return "Fabric Server";
                } else if ($scope.row.managed) {
                    return "Managed Container";
                } else {
                    return "Unmanaged Container";
                }
            }
            return "";
        };

        $scope.updateContainerProperty = function (propertyName, row) {
            Fabric.setContainerProperty(jolokia, row.id, propertyName, row[propertyName], function () {
                Core.$apply($scope);
            }, function (response) {
                notification('error', 'Failed to set container property due to : ' + response.error);
                Core.$apply($scope);
            });
        };

        $scope.getClass = function (item) {
            if (!$scope.provisionListFilter) {
                return 'no-filter';
            } else if (item.has($scope.provisionListFilter)) {
                return 'match-filter';
            } else {
                return 'no-match-filter';
            }
        };

        /*
        $scope.$watch('selectedProfilesDialog', (newValue, oldValue) => {
        if (newValue !== oldValue) {
        console.log("Selected profiles: ", $scope.selectedProfilesDialog);
        }
        }, true);
        */
        $scope.addProfiles = function () {
            $scope.addProfileDialog.close();
            var addedProfiles = $scope.selectedProfilesDialog.map(function (p) {
                return p.id;
            });
            var text = Core.maybePlural(addedProfiles.length, "profile");
            Fabric.addProfilesToContainer(jolokia, $scope.row.id, addedProfiles, function () {
                notification('success', "Successfully added " + text);
                $scope.selectedProfilesDialog = [];
                $scope.$broadcast('fabricProfileRefresh');
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to add " + text + " due to " + response.error);
                $scope.selectedProfilesDialog = [];
                Core.$apply($scope);
            });
        };

        $scope.getArguments = function () {
            if ($scope.inDashboard) {
                return [$scope.containerId, ['id', 'versionId', 'profileIds', 'provisionResult', 'jolokiaUrl', 'alive', 'jmxDomains', 'ensembleServer', 'debugPort']];
            }
            return [$scope.containerId];
        };

        $scope.deleteProfiles = function () {
            $scope.deleteProfileDialog.close();
            var removedProfiles = $scope.selectedProfiles.map(function (p) {
                return p.id;
            });
            var text = Core.maybePlural(removedProfiles.length, "profile");
            Fabric.removeProfilesFromContainer(jolokia, $scope.row.id, removedProfiles, function () {
                notification('success', "Successfully removed " + text);
                $scope.selectedProfiles = [];
                $scope.$broadcast('fabricProfileRefresh');
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to remove " + text + " due to " + response.error);
                $scope.selectedProfiles = [];
                Core.$apply($scope);
            });
        };

        $scope.$on("fabricProfileRefresh", function () {
            setTimeout(function () {
                jolokia.request({
                    type: 'exec', mbean: Fabric.managerMBean,
                    operation: $scope.operation,
                    arguments: $scope.getArguments()
                }, {
                    method: 'POST',
                    success: function (response) {
                        render(response);
                    }
                });
            }, 500);
        });

        if (angular.isDefined($scope.containerId)) {
            Core.register(jolokia, $scope, {
                type: 'read', mbean: Fabric.managerMBean,
                attribute: ["MavenRepoURI", "MavenRepoUploadURI"]
            }, onSuccess(mavenUris));

            Core.register(jolokia, $scope, {
                type: 'exec', mbean: Fabric.managerMBean,
                operation: $scope.operation,
                arguments: $scope.getArguments()
            }, onSuccess(render));
        }

        $scope.formatStackTrace = function (exception) {
            return Log.formatStackTrace(exception);
        };

        function mavenUris(response) {
            var obj = response.value;
            if (obj) {
                $scope.mavenRepoUploadUri = obj.MavenRepoUploadURI;
                $scope.mavenRepoDownloadUri = obj.MavenRepoURI;
            }
        }

        function render(response) {
            if (!angular.isDefined($scope.responseJson)) {
                $scope.loading = false;
            }
            var responseJson = angular.toJson(response.value);
            if ($scope.responseJson !== responseJson) {
                $scope.responseJson = responseJson;
                $scope.row = response.value;
                $scope.container = $scope.row;
                if ($scope.row) {
                    var row = $scope.row;
                    row.debugHost = row.publicIp || row.localHostname || row.localIp || row.ip || row.manualIp;
                    if (angular.isDefined($scope.row.provisionException) && angular.isString($scope.row.provisionException)) {
                        $scope.row.provisionExceptionArray = $scope.row.provisionException.lines();
                    }
                    $scope.services = Fabric.getServiceList($scope.row);
                    if (angular.isDefined($scope.resolverWatch) && angular.isFunction($scope.resolverWatch)) {
                        $scope.resolverWatch();
                    }
                    $scope.resolverWatch = $scope.$watch('row.resolver', function (newValue, oldValue) {
                        if (newValue !== oldValue) {
                            $scope.updateContainerProperty('resolver', $scope.row);
                        }
                    });
                }
                Core.$apply($scope);
            }
        }
    }
    Fabric.ContainerController = ContainerController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function FeatureEditController($scope, $routeParams, $location, jolokia, xml2json, workspace) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.getProfileFeaturesOp = "getProfileFeatures(java.lang.String, java.lang.String)";
        $scope.versionId = $routeParams.versionId;
        $scope.profileId = $routeParams.profileId;

        $scope.response = {};

        $scope.features = [];

        $scope.selectedRepoFeatures = [];

        $scope.deletingFeatures = [];
        $scope.addingFeatures = [];

        $scope.selectedRepoSelectedFeatures = [];

        $scope.featureGridOptions = {
            data: 'selectedRepoFeatures',
            selectedItems: $scope.selectedRepoSelectedFeatures,
            displayFooter: false,
            showFilter: false,
            keepLastSelected: true,
            showSelectionCheckbox: true,
            filterOptions: {
                filterText: ''
            },
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Name'
                },
                {
                    field: 'version',
                    displayName: 'Version'
                }
            ]
        };

        $scope.$watch('features', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.parentFeatures = $scope.features.filter(function (f) {
                    return f.isParentFeature;
                });
                $scope.profileFeatures = $scope.features.filter(function (f) {
                    return !f.isParentFeature;
                });
                $scope.addingFeatures = $scope.features.filter(function (f) {
                    return f.adding;
                });
                $scope.deletingFeatures = $scope.features.filter(function (f) {
                    return f.deleting;
                });
            }
        }, true);

        $scope.$watch('addingFeatures', function (newValue, oldValue) {
            if (newValue !== oldValue) {
            }
        }, true);

        $scope.dispatch = function (response) {
            var responseJson = angular.toJson(response.value);
            if (responseJson !== $scope.responseJson) {
                if (angular.isDefined($scope.responseJson)) {
                    notification('info', "Profile feature definitions updated");
                }
                $scope.responseJson = responseJson;
                $scope.features = response.value.featureDefinitions;
                var repositories = response.value.repositoryDefinitions;

                $scope.selectedRepoFeatures = [];

                repositories.forEach(function (repo) {
                    var repoJson = xml2json(repo['data']);
                    if ('feature' in repoJson) {
                        var features = repoJson['feature'];
                        if (!angular.isArray(features)) {
                            features = [features];
                        }
                        $scope.selectedRepoFeatures.add(features);
                    }
                });

                $scope.selectedRepoFeatures = $scope.selectedRepoFeatures.sortBy('name');

                Core.$apply($scope);
            }
        };

        $scope.getClass = function (feature) {
            if (feature.adding) {
                return "adding";
            }
            if (feature.deleting) {
                return "deleting";
            }
            return "";
        };

        $scope.removeFeature = function (feature) {
            if (feature.adding) {
                $scope.features.remove(function (f) {
                    return f.id === feature.id;
                });
            } else {
                feature.deleting = !feature.deleting;
            }
        };

        $scope.addSelectedFeatures = function (withVersion) {
            $scope.selectedRepoSelectedFeatures.each(function (feature) {
                var id = feature.name;

                if (withVersion) {
                    id = id + "/" + feature.version;
                }

                $scope.features.push({
                    id: id,
                    adding: true
                });
            });
        };

        $scope.save = function () {
            jolokia.request({
                type: 'exec', mbean: Fabric.managerMBean,
                operation: 'getConfigurationFile(java.lang.String, java.lang.String, java.lang.String)',
                arguments: [$scope.versionId, $scope.profileId, 'io.fabric8.agent.properties']
            }, onSuccess($scope.doSave));
        };

        $scope.doSave = function (response) {
            var configFile = response.value.decodeBase64();
            var lines = configFile.lines();

            if ($scope.deletingFeatures.length > 0) {
                $scope.deletingFeatures.each(function (feature) {
                    lines.remove(function (line) {
                        return line.startsWith("feature." + feature.id);
                    });
                });
            }

            if ($scope.addingFeatures.length > 0) {
                $scope.addingFeatures.each(function (feature) {
                    lines.add("feature." + feature.id + " = " + feature.id);
                });
            }

            configFile = lines.join('\n');

            Fabric.saveConfigFile(jolokia, $scope.versionId, $scope.profileId, 'io.fabric8.agent.properties', configFile.encodeBase64(), function () {
                notification('success', "Updated feature definitions...");
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to save feature definitions due to " + response.error);
                Core.$apply($scope);
            });
        };

        Core.register(jolokia, $scope, [{
                type: 'exec', mbean: Fabric.managerMBean, operation: $scope.getProfileFeaturesOp,
                arguments: [$scope.versionId, $scope.profileId]
            }], onSuccess($scope.dispatch));
    }
    Fabric.FeatureEditController = FeatureEditController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function NavBarController($scope, $location, jolokia, workspace, localStorage) {
        $scope.activeVersion = "1.0";

        $scope.mapsEnabled = localStorage['fabricEnableMaps'];

        // update the active version whenever query parameters change
        $scope.$on('$routeUpdate', reloadVersion);

        $scope.isActive = function (href) {
            return workspace.isLinkActive(href);
        };

        $scope.clusterLink = function () {
            // TODO move to use /fabric/clusters by default maybe?
            return Core.createHref($location, "#/fabric/clusters/fabric/registry/clusters", ["cv", "cp", "pv"]);
        };

        function reloadVersion() {
            $scope.activeVersion = Fabric.getActiveVersion($location);
        }

        function reloadData() {
            // TODO should probably load the default version here
            reloadVersion();
            $scope.hasFabric = Fabric.hasFabric(workspace);
            $scope.hasMQManager = Fabric.hasMQManager(workspace);
            if ($scope.hasFabric) {
                var containerId = null;
                Fabric.containerWebAppURL(jolokia, "drools-wb-distribution-wars", containerId, onDroolsUrl, onDroolsUrl);
                $scope.canUpload = workspace.treeContainsDomainAndProperties('hawtio', { type: 'UploadManager' });
            }
        }

        reloadData();

        function onDroolsUrl(response) {
            var url = response ? response.value : null;
            console.log("========== onDroolsUrl: " + url);
            $scope.droolsHref = url;
            Core.$apply($scope);
        }
    }
    Fabric.NavBarController = NavBarController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function AssignProfileController($scope, jolokia, $location, $routeParams, workspace) {
        $scope.profileId = $routeParams['pid'];
        $scope.versionId = $routeParams['vid'];

        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.containerIdFilter = '';

        var valid = true;

        if (Core.isBlank($scope.profileId)) {
            Fabric.log.warn("No profile ID specified, redirecting to Fabric management view");
            valid = false;
        }

        if (Core.isBlank($scope.versionId)) {
            Fabric.log.warn("No version ID specified, redirecting to Fabric management view");
            valid = false;
        }

        if (!valid) {
            $location.path("/fabric/view");
        }

        $scope.gotoCreate = function () {
            $location.path('/fabric/containers/createContainer').search({
                versionId: $scope.versionId,
                profileIds: $scope.profileId
            });
        };

        $scope.$on('$routeChangeSuccess', function () {
            Fabric.log.debug("RouteParams: ", $routeParams);
            Fabric.log.debug("Scope: ", $scope);
        });

        $scope.$watch('containers', function (newValue, oldValue) {
            if (newValue !== oldValue && newValue) {
                $scope.selected = newValue.filter(function (c) {
                    return c['selected'];
                });
            }
        }, true);

        $scope.assignProfiles = function () {
            var requests = [];
            $scope.selected.forEach(function (c) {
                requests.push({
                    type: 'exec', mbean: Fabric.managerMBean,
                    operation: 'addProfilesToContainer',
                    arguments: [c.id, [$scope.profileId]]
                });
            });
            notification('info', "Applying " + $scope.profileId + " to the selected containers");
            var outstanding = requests.length;
            jolokia.request(requests, onSuccess(function () {
                outstanding = outstanding - 1;
                if (outstanding === 0) {
                    notification('success', "Applied " + $scope.profileId);
                    Core.$apply($scope);
                }
            }));
            setTimeout(function () {
                $location.path("/fabric/activeProfiles");
                Core.$apply($scope);
            }, 30);
        };
    }
    Fabric.AssignProfileController = AssignProfileController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    var ProfileDetails = (function () {
        function ProfileDetails() {
            this.restrict = 'A';
            this.replace = true;
            this.templateUrl = Fabric.templatePath + "profileDetailsDirective.html";
            this.scope = {
                versionId: '=',
                profileId: '='
            };
            this.controller = function ($scope, $element, $attrs, $routeParams, jolokia, $location, workspace, $q) {
                $scope.inDirective = true;

                Fabric.ProfileController($scope, $routeParams, jolokia, $location, workspace, $q);
            };
        }
        return ProfileDetails;
    })();
    Fabric.ProfileDetails = ProfileDetails;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ClusterController($scope, $location, $routeParams, workspace, jolokia) {
        $scope.path = $routeParams["page"] || "/";
        if (!$scope.path.startsWith("/")) {
            $scope.path = "/" + $scope.path;
        }

        $scope.gridOptions = {
            data: 'children',
            displayFooter: false,
            sortInfo: { fields: ['name'], directions: ['asc'] },
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText"><a href="{{childLink(row.entity)}}"><i class="{{row | fileIconClass}}"></i> {{row.getProperty(col.field)}}</a></div>',
                    cellFilter: ""
                }
            ]
        };

        $scope.isTabActive = function (href) {
            var tidy = Core.trimLeading(href, "#");
            var loc = $location.path();
            return loc === tidy;
        };

        $scope.childLink = function (child) {
            var prefix = "#/fabric/clusters/" + Core.trimLeading($scope.path, "/") + "/";
            var postFix = "";
            var path = child.name;
            return Core.createHref($location, prefix + path + postFix);
        };

        $scope.$watch('workspace.tree', function () {
            setTimeout(updateView, 50);
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        updateView();

        function updateView() {
            loadBreadcrumbs();

            var mbean = Fabric.getZooKeeperFacadeMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "read", $scope.path, onSuccess(onContents));
            }
        }

        function onContents(contents) {
            // for now it returns just lists of names
            $scope.children = [];
            $scope.stringData = null;
            $scope.html = null;
            if (contents) {
                angular.forEach(contents.children, function (childName) {
                    $scope.children.push({ name: childName });
                });
                if (!$scope.children.length) {
                    var stringData = contents.stringData;
                    if (stringData) {
                        $scope.stringData = stringData;
                        var json = Core.tryParseJson(stringData);
                        if (json) {
                            $scope.html = Core.valueToHtml(json);
                        } else {
                            // TODO detect properties files
                            $scope.html = stringData;
                        }
                    }
                }
            }
            Core.$apply($scope);
        }

        function loadBreadcrumbs() {
            var href = "#/fabric/clusters";
            $scope.breadcrumbs = [
                { href: href + "/", name: "/" }
            ];
            var path = $scope.path;
            var array = path ? path.split("/") : [];
            angular.forEach(array, function (name) {
                if (name) {
                    if (!name.startsWith("/") && !href.endsWith("/")) {
                        href += "/";
                    }
                    href += name;
                    $scope.breadcrumbs.push({ href: href, name: name });
                }
            });
        }
    }
    Fabric.ClusterController = ClusterController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function PIDController($scope, $routeParams, jolokia, $location) {
        $scope.versionId = $routeParams.versionId;
        $scope.profileId = $routeParams.profileId;
        $scope.fname = $routeParams.fname;
        $scope.response = undefined;
        $scope.data = "";
        $scope.dirty = false;

        $scope.getMode = function () {
            var parts = $scope.fname.split('.');
            var mode = parts[parts.length - 1];
            if (!mode) {
                return 'text';
            }
            switch (mode) {
                case 'cfg':
                    mode = "properties";
                    break;
            }
            return mode;
        };

        $scope.mode = $scope.getMode();

        if (angular.isDefined($scope.versionId) && angular.isDefined($scope.profileId) && angular.isDefined($scope.fname)) {
            Core.register(jolokia, $scope, {
                type: 'exec', mbean: Fabric.managerMBean,
                operation: 'getConfigurationFile(java.lang.String,java.lang.String,java.lang.String)',
                arguments: [$scope.versionId, $scope.profileId, $scope.fname]
            }, onSuccess(render));
        }

        $scope.save = function () {
            Fabric.saveConfigFile(jolokia, $scope.versionId, $scope.profileId, $scope.fname, $scope.data.encodeBase64(), function () {
                $scope.dirty = false;
                notification('success', "Saved " + $scope.fname);
                $location.path("/fabric/profile/" + $scope.versionId + "/" + $scope.profileId);
            }, function (response) {
                notification('error', "Failed to save " + $scope.fname + " due to " + response.error);
            });
        };

        function stringToBytes(s) {
            return s.codes();
        }

        function bytesToString(b) {
            var answer = [];
            b.forEach(function (b) {
                answer.push(String.fromCharCode(b));
            });
            return answer.join('');
        }

        function render(response) {
            if (!Object.equal($scope.response, response.value)) {
                $scope.response = response.value;
                $scope.data = $scope.response.decodeBase64();
                $scope.mode = $scope.getMode();
                Core.$apply($scope);
            }
        }
    }
    Fabric.PIDController = PIDController;
})(Fabric || (Fabric = {}));
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Fabric;
(function (Fabric) {
    var ContainerList = (function () {
        function ContainerList() {
            this.restrict = 'A';
            this.replace = true;
            this.templateUrl = Fabric.templatePath + "containerList.html";
            this.scope = false;
            this.link = function ($scope, $element, $attrs) {
                $scope.showSelect = Core.parseBooleanValue(UI.getIfSet('showSelect', $attrs, 'true'));

                var atVersion = UI.getIfSet('atVersion', $attrs, null);
                var withoutProfile = UI.getIfSet('withoutProfile', $attrs, null);

                if (atVersion !== null) {
                    $scope.atVersion = $scope.$eval(atVersion);
                }

                if (withoutProfile !== null) {
                    $scope.withoutProfile = $scope.$eval(withoutProfile);
                }

                Fabric.log.debug("atVersion: ", $scope.atVersion);
                Fabric.log.debug("withoutProfile: ", $scope.withoutProfile);

                Fabric.log.debug("container list attributes: ", $attrs);
            };
        }
        ContainerList.prototype.controller = function ($scope, $element, $attrs, jolokia, $location, workspace, $templateCache) {
            $scope.containerArgs = ["id", "alive", "parentId", "profileIds", "versionId", "provisionResult", "jolokiaUrl", "root", 'jmxDomains', "type", "metadata", "location"];
            $scope.profileFields = ["id", "hidden"];
            $scope.containersOp = 'containers(java.util.List, java.util.List)';
            $scope.ensembleContainerIdListOp = 'EnsembleContainers';

            $scope.containers = [];
            $scope.activeProfiles = [];
            $scope.selectedContainers = [];
            $scope.selectedContainerIds = [];
            $scope.showSelect = true;
            $scope.requirements = null;

            Fabric.initScope($scope, $location, jolokia, workspace);

            $scope.currentPage = $templateCache.get("addProfileRequirements");

            // for editing container requirements
            $scope.editRequirements = {
                dialog: new UI.Dialog(),
                excludeProfiles: [],
                selectedProfiles: [],
                excludeDependentProfiles: [],
                selectedDependentProfiles: [],
                addDependentProfileDialog: new UI.Dialog(),
                versionId: null,
                addProfileSelectShow: false,
                dialogOpen: function (profile) {
                    // lets make sure the requirements are pre-populated with values
                    var editRequirementsEntity = {
                        profileRequirements: []
                    };

                    // initially the requirements stored in ZK look like this:
                    // > zk:get /fabric/configs/io.fabric8.requirements.json
                    // {"profileRequirements":[],"version":"1.0"}
                    if ($scope.requirements) {
                        angular.copy($scope.requirements, editRequirementsEntity);
                    }
                    var profileRequirements = editRequirementsEntity.profileRequirements;
                    if (profileRequirements) {
                        angular.forEach($scope.activeProfiles, function (profile) {
                            var currentRequirements = profile.requirements;
                            if (!currentRequirements) {
                                currentRequirements = {
                                    profile: profile.id
                                };
                                profile.requirements = currentRequirements;
                            }
                            if (!profileRequirements.find(function (p) {
                                return p.profile === currentRequirements.profile;
                            })) {
                                profileRequirements.push(currentRequirements);
                            }
                        });
                    }
                    if (!profile && $scope.activeProfiles.length) {
                        // lets pick the first one - its just to default a version
                        profile = $scope.activeProfiles[0];
                    }
                    if (profile) {
                        $scope.editRequirements.versionId = profile.versionId;
                    }
                    $scope.editRequirements.entity = editRequirementsEntity;
                    $scope.editRequirements.dialog.open();
                },
                // show / hide the new dependent profiles on a profile requirement
                addDependentProfileDialogOpen: function (requirement) {
                    $scope.editRequirements.addDependentProfileDialogProfile = requirement.profile;
                    $scope.editRequirements.selectedDependentProfiles.splice(0, $scope.editRequirements.selectedDependentProfiles.length);
                    $scope.editRequirements.excludeDependentProfiles = [requirement.profile].concat(requirement.dependentProfiles || []);
                    $scope.editRequirements.addDependentProfilesToRequirement = requirement;
                    $scope.editRequirements.addDependentProfileDialogShow = true;
                },
                addDependentProfileDialogHide: function () {
                    $scope.editRequirements.addDependentProfileDialogShow = false;
                },
                addDependentProfileDialogApply: function () {
                    var requirement = $scope.editRequirements.addDependentProfilesToRequirement;
                    angular.forEach($scope.editRequirements.selectedDependentProfiles, function (profile) {
                        var id = profile.id;
                        if (id && requirement) {
                            if (!requirement.dependentProfiles)
                                requirement.dependentProfiles = [];
                            if (!requirement.dependentProfiles.find(function (el) {
                                return el === id;
                            })) {
                                requirement.dependentProfiles.push(id);
                            }
                        }
                    });
                    $scope.editRequirements.addDependentProfileDialogHide();
                },
                // how / hide / add a requirement on new profile
                addProfileRequirementOpen: function () {
                    $scope.editRequirements.selectedProfiles.splice(0, $scope.editRequirements.selectedProfiles.length);
                    $scope.editRequirements.excludeProfiles = $scope.activeProfiles.map(function (p) {
                        return p.id;
                    });
                    $scope.editRequirements.addProfileRequirementShow = true;
                },
                addProfileRequirementHide: function () {
                    $scope.editRequirements.addProfileRequirementShow = false;
                },
                addProfileRequirementApply: function () {
                    var entity = $scope.editRequirements.entity;
                    var profileRequirements = entity.profileRequirements;
                    if (!profileRequirements) {
                        profileRequirements = [];
                        entity.profileRequirements = profileRequirements;
                    }
                    angular.forEach($scope.editRequirements.selectedProfiles, function (profile) {
                        var id = profile.id;
                        if (id) {
                            profileRequirements.push({ profile: id });
                        }
                    });
                    $scope.editRequirements.addProfileRequirementHide();
                }
            };

            $scope.getFilteredName = function (item) {
                return item.versionId + " / " + item.id;
            };

            $scope.filterContainer = function (container) {
                var filterText = $scope.containerIdFilter;
                var filterName = $scope.getFilteredName(container);

                if (!Core.matchFilterIgnoreCase(filterName, filterText)) {
                    // we did not match the container name, then try to see if we match any of its profiles
                    var profileIds = container.profileIds;
                    if (profileIds) {
                        return profileIds.any(function (id) {
                            return Core.matchFilterIgnoreCase(id, filterText);
                        });
                    }
                    return false;
                }
                return true;
            };

            $scope.$watch('editRequirements.addDependentProfileDialogShow', function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    if (newValue) {
                        $scope.currentPage = $templateCache.get("addDependentProfile");
                    } else {
                        $scope.currentPage = $templateCache.get("addProfileRequirements");
                    }
                }
            });

            $scope.$watch('editRequirements.addProfileRequirementShow', function (newValue, oldValue) {
                if (newValue !== oldValue) {
                    if (newValue) {
                        $scope.currentPage = $templateCache.get("addProfileRequirement");
                    } else {
                        $scope.currentPage = $templateCache.get("addProfileRequirements");
                    }
                }
            });

            // invoked regularly by Jolokia after detecting new response from requirements()
            // from object io.fabric:type=Fabric
            $scope.updateActiveContainers = function () {
                var activeProfiles = $scope.activeProfiles;
                $scope.activeProfiles = $scope.currentActiveProfiles();
                $scope.activeProfiles.each(function (activeProfile) {
                    var ap = activeProfiles.find(function (ap) {
                        return ap.id === activeProfile.id && ap.versionId === activeProfile.versionId;
                    });
                    if (ap) {
                        activeProfile['selected'] = ap.selected;
                        activeProfile['expanded'] = ap.expanded;
                    } else {
                        activeProfile['selected'] = false;
                        activeProfile['expanded'] = false;
                    }
                });
            };

            // invoked regularly by Jolokia wth the result of containers(List, List)
            // from object io.fabric:type=Fabric
            $scope.updateContainers = function (newContainers) {
                var response = angular.toJson(newContainers);
                if ($scope.containersResponse !== response) {
                    $scope.containersResponse = response;

                    newContainers = newContainers.sortBy('id');

                    var rootContainers = newContainers.exclude(function (c) {
                        return !c.root;
                    });
                    var childContainers = newContainers.exclude(function (c) {
                        return c.root;
                    });

                    if (childContainers.length > 0) {
                        var tmp = [];
                        rootContainers.each(function (c) {
                            tmp.add(c);
                            var children = childContainers.exclude(function (child) {
                                return child.parentId !== c.id;
                            });
                            tmp.add(children);
                        });
                        newContainers = tmp;
                    }

                    if (angular.isDefined($scope.atVersion)) {
                        newContainers = newContainers.filter(function (c) {
                            return c.versionId === $scope.atVersion;
                        });
                    }

                    if (angular.isDefined($scope.withoutProfile)) {
                        newContainers = newContainers.filter(function (c) {
                            return !c.profileIds.any(function (p) {
                                return p === $scope.withoutProfile;
                            });
                        });
                    }

                    newContainers.each(function (container) {
                        container.services = Fabric.getServiceList(container);
                        container.icon = Fabric.getTypeIcon(container);
                        var c = $scope.containers.find(function (c) {
                            return c.id === container.id;
                        });
                        if (c) {
                            container['selected'] = c.selected;
                        } else {
                            container['selected'] = false;
                        }
                        if ($scope.selectedContainerIds.any(container.id)) {
                            container.selected = true;
                        }
                    });

                    $scope.containers = newContainers;
                    $scope.updateActiveContainers();
                    Core.$apply($scope);
                }
            };

            $scope.currentActiveProfiles = function () {
                var answer = [];

                $scope.containers.each(function (container) {
                    container.profileIds.each(function (profile) {
                        var p = container.profiles.find(function (p) {
                            return p.id === profile;
                        });
                        if (p && p.hidden) {
                            return;
                        }

                        var activeProfile = answer.find(function (o) {
                            return o.versionId === container.versionId && o.id === profile;
                        });

                        if (activeProfile) {
                            activeProfile['containers'] = activeProfile['containers'].include(container.id).unique();

                            activeProfile.count = activeProfile['containers'].length;
                        } else {
                            answer.push({
                                id: profile,
                                count: 1,
                                versionId: container.versionId,
                                containers: [container.id],
                                selected: false,
                                requirements: null,
                                requireStyle: null
                            });
                        }
                    });
                });

                if ($scope.requirements) {
                    angular.forEach($scope.requirements.profileRequirements, function (profileRequirement) {
                        var id = profileRequirement.profile;
                        var min = profileRequirement.minimumInstances;
                        if (id) {
                            var profile = answer.find(function (p) {
                                return (p.id === id);
                            });

                            function requireStyle() {
                                var count = 0;
                                if (profile) {
                                    count = profile['count'];
                                }
                                return Fabric.containerCountBadgeStyle(min, count);
                            }

                            if (profile) {
                                profile["requirements"] = profileRequirement;
                                profile["requireStyle"] = requireStyle();
                            } else {
                                // lets add the profile with no containers
                                answer.push({
                                    id: id,
                                    count: 0,
                                    versionId: $scope.requirements.version || "1.0",
                                    containers: [],
                                    selected: false,
                                    requirements: profileRequirement,
                                    requireStyle: requireStyle()
                                });
                            }
                        }
                    });
                }

                return answer;
            };

            $scope.updateEnsembleContainerIdList = function (ids) {
                var response = angular.toJson(ids);
                if ($scope.ensembleContainerIdsResponse !== response) {
                    $scope.ensembleContainerIdsResponse = response;
                    $scope.ensembleContainerIds = ids;
                    Core.$apply($scope);
                }
            };

            $scope.dispatch = function (response) {
                switch (response.request.operation) {
                    case ($scope.containersOp):
                        $scope.updateContainers(response.value);
                        return;
                }
                switch (response.request.attribute) {
                    case ($scope.ensembleContainerIdListOp):
                        $scope.updateEnsembleContainerIdList(response.value);
                        return;
                }
            };

            $scope.clearSelection = function (group) {
                group.each(function (item) {
                    item.selected = false;
                });
            };

            $scope.setActiveProfile = function (profile) {
                $scope.clearSelection($scope.activeProfiles);
                if (!profile || profile === null) {
                    return;
                }
                profile.selected = true;
            };

            $scope.selectAllContainers = function () {
                $scope.containers.each(function (container) {
                    if ($scope.filterContainer(container)) {
                        container.selected = true;
                    }
                });
            };

            $scope.setActiveContainer = function (container) {
                $scope.clearSelection($scope.containers);
                if (!container || container === null) {
                    return;
                }
                container.selected = true;
            };

            $scope.startSelectedContainers = function () {
                $scope.selectedContainers.each(function (c) {
                    $scope.startContainer(c.id);
                });
            };

            $scope.stopSelectedContainers = function () {
                $scope.selectedContainers.each(function (c) {
                    $scope.stopContainer(c.id);
                });
            };

            $scope.startContainer = function (name) {
                Fabric.doStartContainer($scope, jolokia, name);
            };

            $scope.stopContainer = function (name) {
                Fabric.doStopContainer($scope, jolokia, name);
            };

            $scope.anySelectionAlive = function (state) {
                var selected = $scope.selectedContainers;
                return selected.length > 0 && selected.any(function (s) {
                    return s.alive === state;
                });
            };

            $scope.everySelectionAlive = function (state) {
                var selected = $scope.selectedContainers;
                return selected.length > 0 && selected.every(function (s) {
                    return s.alive === state;
                });
            };

            Core.register(jolokia, $scope, [
                { type: 'exec', mbean: Fabric.managerMBean, operation: $scope.containersOp, arguments: [$scope.containerArgs, $scope.profileFields] },
                { type: 'read', mbean: Fabric.clusterManagerMBean, attribute: $scope.ensembleContainerIdListOp }
            ], onSuccess($scope.dispatch, { silent: true }));
        };
        return ContainerList;
    })();
    Fabric.ContainerList = ContainerList;

    var ActiveProfileList = (function (_super) {
        __extends(ActiveProfileList, _super);
        function ActiveProfileList() {
            _super.apply(this, arguments);
            this.templateUrl = Fabric.templatePath + "activeProfileList.html";
        }
        ActiveProfileList.prototype.controller = function ($scope, $element, $attrs, jolokia, $location, workspace, $templateCache) {
            _super.prototype.controller.call(this, $scope, $element, $attrs, jolokia, $location, workspace, $templateCache);

            $scope.searchFilter = '';

            $scope.isOpen = function (profile) {
                if ($scope.searchFilter !== '') {
                    return "opened";
                }
                return "closed";
            };

            $scope.containersForProfile = function (id) {
                return $scope.containers.filter(function (container) {
                    return container.profileIds.some(id);
                });
            };

            $scope.profileMatchesFilter = function (profile) {
                var filterText = $scope.searchFilter;

                return Core.matchFilterIgnoreCase(profile.id, filterText) || !profile.containers.filter(function (id) {
                    return Core.matchFilterIgnoreCase(id, filterText);
                }).isEmpty();
            };

            $scope.containerMatchesFilter = function (container) {
                var filterText = $scope.searchFilter;
                return Core.matchFilterIgnoreCase(container.id, filterText) || !container.profileIds.filter(function (id) {
                    return Core.matchFilterIgnoreCase(id, filterText);
                }).isEmpty;
            };

            $scope.updateRequirements = function (requirements) {
                function onRequirementsSaved(response) {
                    $scope.requirements = requirements;
                    notification("success", "Updated the requirements");
                    $scope.updateActiveContainers();
                    Core.$apply($scope);
                }
                ;

                if (requirements) {
                    $scope.editRequirements.dialog.close();

                    var json = JSON.stringify(requirements);
                    jolokia.execute(Fabric.managerMBean, "requirementsJson", json, onSuccess(onRequirementsSaved));
                }
            };

            function onRequirements(response) {
                var responseJson = angular.toJson(response.value);

                if (responseJson !== $scope.requirementsResponse) {
                    $scope.requirementsResponse = responseJson;
                    $scope.requirements = response.value;
                    $scope.updateActiveContainers();
                    Core.$apply($scope);
                }
            }

            Core.register(jolokia, $scope, { type: 'exec', mbean: Fabric.managerMBean, operation: "requirements()" }, onSuccess(onRequirements));
        };
        return ActiveProfileList;
    })(Fabric.ContainerList);
    Fabric.ActiveProfileList = ActiveProfileList;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    Fabric.startMaps = function () {
    };

    function MapController($scope, $templateCache, jolokia) {
        $scope.myMarkers = [];
        $scope.containers = {};
        $scope.template = "";
        $scope.first = true;
        $scope.myMap = null;

        $scope.start = function () {
            // must have initial map options
            $scope.mapOptions = {
                center: new google.maps.LatLng(35.784, -78.670),
                zoom: 15,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };

            Core.register(jolokia, $scope, {
                type: 'exec', mbean: Fabric.managerMBean,
                operation: 'containers()',
                arguments: []
            }, onSuccess(render));
        };

        Fabric.startMaps = $scope.start;

        $('body').append('<script type="text/javascript" src="//maps.google.com/maps/api/js?sensor=false&async=2&callback=Fabric.startMaps"></script>');

        // TODO: adding markers on map to place containers is not (yet) supported
        /*
        $scope.addMarker = function ($event) {
        $scope.myMarkers.push(new google.maps.Marker({
        map: $scope.myMap,
        position: $event.latLng
        }));
        };
        
        $scope.setZoomMessage = function (zoom) {
        //$scope.zoomMessage = 'You just zoomed to ' + zoom + '!';
        console.log(zoom, 'zoomed')
        };
        
        $scope.openMarkerInfo = function (marker) {
        $scope.currentMarker = marker;
        $scope.currentMarkerLat = marker.getPosition().lat();
        $scope.currentMarkerLng = marker.getPosition().lng();
        $scope.myInfoWindow.open($scope.myMap, marker);
        };
        
        $scope.setMarkerPosition = function (marker, lat, lng) {
        marker.setPosition(new google.maps.LatLng(lat, lng));
        };
        */
        function render(response) {
            if (response && response.value) {
                response.value.forEach(function (container) {
                    var addMarker = false;
                    var id = container.id;
                    var containerData = $scope.containers[id];
                    if (!containerData) {
                        containerData = {
                            name: id
                        };

                        $scope.containers[id] = containerData;
                        addMarker = true;
                    }
                    containerData.alive = container.alive;
                    containerData.version = container.versionId;
                    containerData.profileIds = container.profileIds;

                    var geoLocation = container["geoLocation"];
                    if (geoLocation) {
                        var values = geoLocation.split(",");
                        if (values.length >= 2) {
                            var lattitude = Core.parseFloatValue(values[0], "lattitude");
                            var longitude = Core.parseFloatValue(values[1], "longitude");
                            if (lattitude && longitude) {
                                // only add marker if we got the map initialized
                                if ($scope.myMap) {
                                    var marker = containerData.marker;
                                    if (addMarker || !marker) {
                                        Fabric.log.info("Adding marker as we have map " + $scope.myMap);
                                        marker = new google.maps.Marker({
                                            position: new google.maps.LatLng(lattitude, longitude),
                                            map: $scope.myMap,
                                            title: container.id,
                                            tooltip: "(lattitude: " + lattitude + ", longitude: " + longitude + ")"
                                        });
                                        containerData.marker = marker;
                                        $scope.myMarkers.push(marker);
                                    }
                                } else {
                                    // lets update the marker in case the container moved ;)
                                    if (containerData.marker) {
                                        containerData.marker.position = new google.maps.LatLng(lattitude, longitude);
                                    }
                                }

                                // lets update the container data
                                containerData.lattitude = lattitude;
                                containerData.longitude = longitude;
                            }
                        }
                    }
                });

                // if there is only 1 container then jump to it asap
                if ($scope.myMarkers.length > 0 && $scope.first) {
                    // lets jump to this as the centre
                    if ($scope.myMap) {
                        var marker = $scope.myMarkers[0];
                        Fabric.log.info("Auto selecting first container on map: " + marker.title);
                        $scope.myMap.panTo(marker.getPosition());
                        $scope.first = false;
                    }
                }

                // only assign template to scope so we only draw map when we are ready
                $scope.template = $templateCache.get("pageTemplate");

                Core.$apply($scope);
            }
        }
    }
    Fabric.MapController = MapController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ProfileController($scope, $routeParams, jolokia, $location, workspace, $q) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.loading = true;

        $scope.mavenMBean = Maven.getMavenIndexerMBean(workspace);

        if (!angular.isDefined($scope.versionId)) {
            $scope.versionId = $routeParams.versionId;
        }
        if (!angular.isDefined($scope.profileId)) {
            $scope.profileId = $routeParams.profileId;
        }

        $scope.newFileDialog = false;
        $scope.deleteFileDialog = false;
        $scope.newFileName = '';
        $scope.markedForDeletion = '';

        $scope.newProfileName = '';
        $scope.deleteThingDialog = false;
        $scope.changeParentsDialog = false;
        $scope.removeParentDialog = false;
        $scope.newThingName = '';
        $scope.selectedParents = [];

        $scope.profilePath = Fabric.profilePath;
        $scope.pageId = Fabric.fabricTopLevel + Fabric.profilePath($scope.profileId);

        var versionId = $scope.versionId;
        var profileId = $scope.profileId;
        if (versionId && versionId) {
            Fabric.profileJolokia(jolokia, profileId, versionId, function (profileJolokia) {
                $scope.profileJolokia = profileJolokia;
            });
        }

        if ($scope.inDirective && angular.isDefined($scope.$parent.childActions) && $scope.versionId) {
            var actions = $scope.$parent.childActions;

            if ($scope.profileId) {
                actions.push({
                    doAction: function () {
                        $scope.showChangeParentsDialog();
                    },
                    title: "Edit parent profiles",
                    icon: "icon-edit",
                    name: "Change Parents"
                });
                actions.push({
                    doAction: function () {
                        $scope.copyProfileDialog = true;
                    },
                    title: "Copy Profile",
                    icon: "icon-copy",
                    name: "Copy Profile"
                });
                actions.push({
                    doAction: function () {
                        $scope.goto('/wiki/profile/' + $scope.versionId + '/' + $scope.profileId + '/editFeatures');
                    },
                    title: "Edit the features defined in this profile",
                    icon: "icon-edit",
                    name: "Edit Features"
                });
                actions.push({
                    doAction: function () {
                        $location.url('/fabric/assignProfile').search({
                            vid: $scope.versionId,
                            pid: $scope.profileId
                        });
                    },
                    title: "Assign profile to existing containers",
                    icon: "icon-truck",
                    name: "Assign to Container"
                });
                actions.push({
                    doAction: function () {
                        $location.url('/fabric/containers/createContainer').search({
                            versionId: $scope.versionId,
                            profileIds: $scope.profileId
                        });
                    },
                    title: "Create a new container with this profile",
                    icon: "icon-truck",
                    name: "New Container"
                });
            }
            /*
            var createVersionDialog = $scope.createVersionDialog;
            if (createVersionDialog) {
            actions.push({
            doAction: () => {
            $scope.createVersionDialog.open();
            },
            title: "Create a new version of this configuration so you can edit it and then perform rolling upgrades",
            icon: "icon-plus",
            name: "New Version"
            });
            }
            */
        }

        $scope.$watch('activeTab', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.newThingName = '';
            }
        });

        $scope.$watch('versionId', function (newValue, oldValue) {
            if (angular.isDefined($scope.versionId) && angular.isDefined($scope.profileId)) {
                $scope.doRegister();
            }
        });

        $scope.$watch('profileId', function (newValue, oldValue) {
            if (angular.isDefined($scope.versionId) && angular.isDefined($scope.profileId)) {
                $scope.doRegister();
            }
        });

        // TODO, should complete URL handlers too
        $scope.doCompletionFabric = function (something) {
            if (something.startsWith("mvn:")) {
                $scope.prefix = "mvn:";
                return Maven.completeMavenUri($q, $scope, workspace, jolokia, something.from(4));
            }
            $scope.prefix = "";
            return $q.when([]);
        };

        $scope.uriParts = [];

        $scope.$watch('newThingName', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.uriParts = newValue.split("/");
            }
        });

        $scope.$watch('uriParts', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (!$scope.prefix || $scope.prefix === '') {
                    return;
                }
                if (newValue && newValue.length > 0 && !newValue.first().startsWith($scope.prefix)) {
                    /*
                    console.log("newValue: ", newValue);
                    console.log("oldValue: ", oldValue);
                    console.log("prefix: ", $scope.prefix);
                    */
                    if (newValue.first() === "" || newValue.first().length < $scope.prefix.length) {
                        return;
                    }
                    if (oldValue.length === 0) {
                        return;
                    }

                    // a completion occurred...
                    if (oldValue.length === 1) {
                        $scope.newThingName = $scope.prefix + newValue.first();
                    } else {
                        var merged = oldValue.first(oldValue.length - 1).include(newValue.first());
                        $scope.newThingName = merged.join('/');
                    }
                }
            }
        }, true);

        $scope.doRegister = function () {
            Core.unregister(jolokia, $scope);
            if ($scope.versionId && $scope.profileId && !$scope.versionId.isBlank() && !$scope.profileId.isBlank()) {
                Core.register(jolokia, $scope, {
                    type: 'exec', mbean: Fabric.managerMBean,
                    operation: 'getProfile(java.lang.String, java.lang.String)',
                    arguments: [$scope.versionId, $scope.profileId]
                }, onSuccess(render));
            }
        };

        $scope.showChangeParentsDialog = function () {
            $scope.selectedParents = $scope.row.parentIds.map(function (parent) {
                return {
                    id: parent,
                    selected: true
                };
            });
            $scope.changeParentsDialog = true;
        };

        $scope.removeParentProfile = function (parent) {
            $scope.markedForDeletion = parent;
            $scope.removeParentDialog = true;
        };

        $scope.doRemoveParentProfile = function () {
            var parents = $scope.row.parentIds.exclude($scope.markedForDeletion);
            Fabric.changeProfileParents(jolokia, $scope.versionId, $scope.profileId, parents, function () {
                notification('success', 'Removed parent profile ' + $scope.markedForDeletion + ' from ' + $scope.profileId);
                Core.$apply($scope);
            }, function (response) {
                notification('error', 'Failed to change parent profiles of ' + $scope.profileId + ' due to ' + response.error);
                Core.$apply($scope);
            });
        };

        $scope.changeAttribute = function (attribute, value) {
            jolokia.request({
                type: 'exec',
                method: 'post',
                mbean: Fabric.managerMBean,
                operation: 'setProfileAttribute',
                arguments: [$scope.versionId, $scope.profileId, attribute, value]
            }, {
                success: function () {
                    // TODO - we're secretly hiding that the ng-click event is firing twice...
                    // notification('success', "Set attribute " + attribute + " to " + value);
                    Core.$apply($scope);
                },
                error: function (response) {
                    console.log("Failed to set attribute " + attribute + " to " + value + " due to " + response.error);

                    // notification('error', "Failed to set attribute " + attribute + " to " + value + " due to " + response.error);
                    Core.$apply($scope);
                }
            });
        };

        $scope.doChangeParents = function () {
            $scope.changeParentsDialog = false;
            var parents = $scope.selectedParents.map(function (parent) {
                return parent.id;
            });
            Fabric.changeProfileParents(jolokia, $scope.versionId, $scope.profileId, parents, function () {
                notification('success', 'Successfully changed parent profiles of ' + $scope.profileId);
                Core.$apply($scope);
            }, function (response) {
                notification('error', 'Failed to change parent profiles of ' + $scope.profileId + ' due to ' + response.error);
                Core.$apply($scope);
            });
        };

        $scope.goto = function (location) {
            $location.url(location);
        };

        $scope.addNewThing = function (title, type, current) {
            if (Core.isBlank($scope.newThingName)) {
                return;
            }
            $scope.thingName = title;
            $scope.currentThing = current;
            $scope.currentThingType = type;
            $scope.doAddThing();
        };

        $scope.deleteThing = function (title, type, current, item) {
            $scope.thingName = title;
            $scope.currentThing = current;
            $scope.currentThingType = type;
            $scope.currentThingItem = item;
            $scope.deleteThingDialog = true;
        };

        $scope.updateThing = function (title, type, current) {
            $scope.thingName = title;
            $scope.currentThing = current;
            $scope.currentThingType = type;
            $scope.callSetProfileThing("Changed", "change", title);
        };

        $scope.mavenLink = function (url) {
            return Maven.mavenLink(url);
        };

        $scope.callSetProfileThing = function (success, error, thing) {
            jolokia.request({
                type: 'exec',
                mbean: Fabric.managerMBean,
                operation: "setProfile" + $scope.currentThingType + "(java.lang.String, java.lang.String, java.util.List)",
                arguments: [$scope.versionId, $scope.profileId, $scope.currentThing]
            }, {
                method: 'POST',
                success: function () {
                    notification('success', success + ' ' + thing);
                    $scope.newThingName = '';
                    Core.$apply($scope);
                },
                error: function (response) {
                    notification('error', 'Failed to ' + error + ' ' + thing + ' due to ' + response.error);
                    Core.$apply($scope);
                }
            });
        };

        $scope.doDeleteThing = function () {
            $scope.currentThing.remove($scope.currentThingItem);
            $scope.callSetProfileThing('Deleted', 'delete', $scope.currentThingItem);
        };

        $scope.doAddThing = function () {
            if (!$scope.currentThing.any($scope.newThingName)) {
                $scope.currentThing.push($scope.newThingName);
                $scope.addThingDialog = false;
                $scope.callSetProfileThing('Added', 'add', $scope.newThingName);
            } else {
                notification('error', 'There is already a ' + $scope.thingName + ' with the name ' + $scope.newThingName);
            }
        };

        $scope.deleteFile = function (file) {
            $scope.markedForDeletion = file;
            $scope.deleteFileDialog = true;
        };

        $scope.doDeleteFile = function () {
            $scope.deleteFileDialog = false;
            Fabric.deleteConfigFile(jolokia, $scope.versionId, $scope.profileId, $scope.markedForDeletion, function () {
                notification('success', 'Deleted file ' + $scope.markedForDeletion);
                $scope.markedForDeletion = '';
                Core.$apply($scope);
            }, function (response) {
                notification('error', 'Failed to delete file ' + $scope.markedForDeletion + ' due to ' + response.error);
                $scope.markedForDeletion = '';
                Core.$apply($scope);
            });
        };

        $scope.doCreateFile = function () {
            $scope.newFileDialog = false;
            Fabric.newConfigFile(jolokia, $scope.versionId, $scope.profileId, $scope.newFileName, function () {
                notification('success', 'Created new configuration file ' + $scope.newFileName);
                $location.path("/fabric/profile/" + $scope.versionId + "/" + $scope.profileId + "/" + $scope.newFileName);
            }, function (response) {
                notification('error', 'Failed to create ' + $scope.newFileName + ' due to ' + response.error);
            });
        };

        $scope.copyProfile = function () {
            $scope.copyProfileDialog = false;

            if ($scope.profileId.has('-') && !$scope.newProfileName.has('-')) {
                var parts = $scope.profileId.split('-');
                parts.pop();
                parts.push($scope.newProfileName);
                $scope.newProfileName = parts.join('-');
            }

            notification('info', 'Copying ' + $scope.profileId + ' to ' + $scope.newProfileName);

            Fabric.copyProfile(jolokia, $scope.versionId, $scope.profileId, $scope.newProfileName, true, function () {
                notification('success', 'Created new profile ' + $scope.newProfileName);
                Fabric.gotoProfile(workspace, jolokia, localStorage, $location, $scope.versionId, { id: $scope.newProfileName });
                Core.$apply($scope);
            }, function (response) {
                notification('error', 'Failed to create new profile ' + $scope.newProfileName + ' due to ' + response.error);
                Core.$apply($scope);
            });
        };

        function render(response) {
            if (!angular.isDefined($scope.row)) {
                $scope.loading = false;
            }
            var responseJson = angular.toJson(response.value);

            if ($scope.profileResponseJson !== responseJson) {
                if (!$scope.activeTab) {
                    $scope.activeTab = "features";
                }
                $scope.profileResponseJson = responseJson;
                $scope.row = response.value;
                var id = $scope.row.id;
                var version = $scope.row.version;
                $scope.configFolderLink = null;
                if ($scope.hasFabricWiki() && id && version) {
                    $scope.configFolderLink = "#/wiki/branch/" + version + "/view/fabric/profiles/" + Fabric.profilePath(id);
                }
                Core.$apply($scope);
            }
        }
    }
    Fabric.ProfileController = ProfileController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function TestController($scope, jolokia, $q, workspace, $templateCache) {
        $scope.mavenMBean = Maven.getMavenIndexerMBean(workspace);

        $scope.html = "text/html";
        $scope.versionSelector = $templateCache.get("versionSelectorTemplate");
        $scope.profileIncludes = $templateCache.get("profile-includes");
        $scope.profileExcludes = $templateCache.get("profile-excludes");
        $scope.containerList = $templateCache.get("containerListTemplate");
        $scope.profileLink = $templateCache.get("profileLinkTemplate");

        $scope.version = {};
        $scope.versionId = '';
        $scope.someUri = '';
        $scope.uriParts = [];

        $scope.version = {};

        $scope.osp = [];
        $scope.vid = '1.0';
        $scope.someProfiles = ['a-mq', 'aws-ec2'];

        $scope.selectedProfiles = [
            {
                id: '1-dot-0',
                selected: true
            }, {
                id: 'a-mq',
                selected: true
            }];

        $scope.selectedProfilesString = "";

        $scope.$watch('version', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if ($scope.version && !Object.equal($scope.version, {})) {
                    $scope.versionId = $scope.version.id;
                }
            }
        });

        $scope.$watch('osp', function (newValue, oldValue) {
            $scope.selectedProfilesString = angular.toJson($scope.osp);
        });

        $scope.$watch('someUri', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.uriParts = newValue.split("/");
            }
        });

        $scope.$watch('uriParts', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (newValue.length === 1 && newValue.length < oldValue.length) {
                    if (oldValue.last() !== '' && newValue.first().has(oldValue.last())) {
                        var merged = oldValue.first(oldValue.length - 1).include(newValue.first());
                        $scope.someUri = merged.join('/');
                    }
                }
            }
        }, true);

        $scope.doCompletionMaven = function (something) {
            return Maven.completeMavenUri($q, $scope, workspace, jolokia, something);
        };

        $scope.doCompletionFabric = function (something) {
            return Fabric.completeUri($q, $scope, workspace, jolokia, something);
        };
    }
    Fabric.TestController = TestController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function PatchingController($scope, jolokia, localStorage, $location) {
        $scope.files = [];
        $scope.targetVersion = $location.search()['versionId'];
        $scope.newVersionName = '';
        $scope.proxyUser = localStorage['fabric.userName'];
        $scope.proxyPassword = localStorage['fabric.password'];
        $scope.saveJmxCredentials = false;

        $scope.cancel = function () {
            $location.url('/fabric/view').search({ cv: $scope.targetVersion });
        };

        $scope.valid = function () {
            return $scope.files && $scope.files.length > 0 && $scope.targetVersion !== null && $scope.proxyUser && $scope.proxyPassword;
        };

        $scope.go = function () {
            var message = $scope.files.length + ' patches';

            if ($scope.files.length === 1) {
                message = "patch: " + $scope.files[0].fileName;
            }

            notification('info', "Applying " + message);

            if ($scope.saveJmxCredentials) {
                localStorage['fabric.userName'] = $scope.proxyUser;
                localStorage['fabric.password'] = $scope.proxyPassword;
            }

            var files = $scope.files.map(function (file) {
                return file.absolutePath;
            });

            Fabric.applyPatches(jolokia, files, $scope.targetVersion, $scope.newVersionName, $scope.proxyUser, $scope.proxyPassword, function () {
                notification('success', "Successfully applied " + message);
                $location.url("/fabric/view");
                Core.$apply($scope);
            }, function (response) {
                Fabric.log.error("Failed to apply ", message, " due to ", response.error);
                Fabric.log.info("Stack trace: ", response.stacktrace);
                Core.$apply($scope);
            });
        };
    }
    Fabric.PatchingController = PatchingController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ProfilesController($scope, $location, workspace, jolokia) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.defaultVersion = Fabric.getDefaultVersion(jolokia);
        $scope.version = { id: $scope.defaultVersion.id };

        $scope.selected = [];
        $scope.selectedParents = [];
        $scope.selectedParentVersion = [];

        $scope.deleteVersionDialog = false;
        $scope.deleteProfileDialog = false;

        $scope.createProfileDialog = false;
        $scope.createVersionDialog = false;

        $scope.triggerResize = function () {
            setTimeout(function () {
                $('.dialogGrid').trigger('resize');
            }, 10);
        };

        $scope.$watch('createProfileDialog', function () {
            if ($scope.createProfileDialog) {
                $scope.triggerResize();
            }
        });

        $scope.$watch('createVersionDialog', function () {
            if ($scope.createVersionDialog) {
                $scope.triggerResize();
            }
        });

        $scope.newProfileName = '';
        $scope.newVersionName = '';

        var key = $location.search()['pv'];
        if (key) {
            $scope.version = { id: key };
        }

        key = $location.search()['ao'];

        // lets default to activeOnly if no query parameter used
        $scope.activeOnly = !angular.isDefined(key) || key === 'true';

        $scope.versions = [];
        $scope.profiles = [];

        $scope.versionResponse = [];
        $scope.profilesResponse = [];

        $scope.$watch('activeOnly', function (oldValue, newValue) {
            if (oldValue === newValue) {
                return;
            }
            var q = $location.search();
            q['ao'] = "" + $scope.activeOnly;
            $location.search(q);
        });

        $scope.$watch('version', function (oldValue, newValue) {
            var q = $location.search();
            q['pv'] = $scope.version.id;
            $location.search(q);

            if (oldValue === newValue) {
                notification('info', "Please wait, fetching profile data for version " + $scope.version.id);
            }

            Core.unregister(jolokia, $scope);
            var versionId = $scope.version.id;
            if (versionId) {
                Core.register(jolokia, $scope, [
                    { type: 'exec', mbean: Fabric.managerMBean, operation: 'versions()' },
                    { type: 'exec', mbean: Fabric.managerMBean, operation: 'getProfiles(java.lang.String, java.util.List)', arguments: [versionId, ["id", "parentIds", "childIds", "containerCount", "locked", "abstract"]] }], onSuccess(render));
            }
        });

        $scope.selectedHasContainers = function () {
            return $scope.selected.findAll(function (item) {
                return item.containerCount > 0;
            }).length > 0;
        };

        $scope.versionCanBeDeleted = function () {
            if ($scope.version.id === $scope.defaultVersion.id) {
                return true;
            }
            if ($scope.versions.length === 0) {
                return true;
            }
            return $scope.profiles.findAll(function (item) {
                return item.containerCount > 0;
            }).length > 0;
        };

        $scope.createProfileGridOptions = {
            data: 'profiles',
            selectedItems: $scope.selectedParents,
            showSelectionCheckbox: true,
            multiSelect: true,
            selectWithCheckboxOnly: false,
            keepLastSelected: false,
            columnDefs: [{
                    field: 'id',
                    displayName: 'Name'
                }]
        };

        $scope.createVersionGridOptions = {
            data: 'versions',
            selectedItems: $scope.selectedParentVersion,
            showSelectionCheckbox: true,
            multiSelect: false,
            selectWithCheckboxOnly: false,
            keepLastSelected: false,
            columnDefs: [{
                    field: 'id',
                    displayName: 'Name'
                }]
        };

        $scope.gridOptions = {
            data: 'profiles',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                filterText: ''
            },
            selectedItems: $scope.selected,
            showSelectionCheckbox: true,
            multiSelect: true,
            selectWithCheckboxOnly: true,
            keepLastSelected: false,
            checkboxCellTemplate: '<div class="ngSelectionCell"><input tabindex="-1" class="ngSelectionCheckbox" type="checkbox" ng-checked="row.selected" ng-disabled="row.entity.containerCount > 0 || row.entity.childIds.length > 0"/></div>',
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText"><a ng-href="#/fabric/profile/{{$parent.version.id}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 300
                },
                {
                    field: 'attributes',
                    displayName: 'A',
                    headerCellTemplate: '<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}} pagination-centered" title="Attributes"><i class="icon-cogs"></i></div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div>',
                    cellTemplate: '<div class="ngCellText"><ul class="unstyled inline"><li class="attr-column"><i ng-show="row.entity.locked" title="Locked" class="icon-lock"></i></li><li class="attr-column"><i ng-show="row.entity.abstract" title="Abstract" class="icon-font"></i></li></ul></div>',
                    width: 52
                },
                {
                    field: 'containerCount',
                    displayName: 'C',
                    headerCellTemplate: '<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}} pagination-centered" title="Containers"><i class="icon-truck"></i></div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div>',
                    cellTemplate: '<div class="ngCellText pagination-centered"><a ng-show="row.getProperty(col.field) > 0" title="{{row.entity.containers.sortBy().join(\'\n\')}}" href="#/fabric/containers?cv={{$parent.version.id}}&cp={{row.entity.id}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 28
                },
                {
                    field: 'parentIds',
                    displayName: 'Parent Profiles',
                    cellTemplate: '<div class="ngCellText"><ul class="unstyled inline"><li ng-repeat="profile in row.entity.parentIds.sortBy()"><a href="#/fabric/profile/{{$parent.version.id}}/{{profile}}">{{profile}}</a></li></ul></div>',
                    width: 400
                },
                {
                    field: 'childIds',
                    displayName: 'Child Profiles',
                    cellTemplate: '<div class="ngCellText"><ul class="unstyled inline"><li ng-repeat="profile in row.entity.childIds.sortBy()"><a href="#/fabric/profile/{{$parent.version.id}}/{{profile}}">{{profile}}</a></li></ul></div>',
                    width: 800
                }
            ]
        };

        $scope.doCreateProfile = function () {
            $scope.createProfileDialog = false;
            var parents = $scope.selectedParents.map(function (profile) {
                return profile.id;
            });
            Fabric.createProfile(jolokia, $scope.version.id, $scope.newProfileName, parents, function () {
                notification('success', "Created profile " + $scope.newProfileName);
                $scope.newProfileName = "";
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to create profile " + $scope.newProfileName + " due to " + response.error);
            });
        };

        $scope.doCreateVersion = function () {
            $scope.createVersionDialog = false;

            var success = function (response) {
                notification('success', "Created version " + response.value.id);
                $scope.newVersionName = '';
                $scope.version = response.value;
                Core.$apply($scope);
            };

            var error = function (response) {
                var msg = "Error creating new version: " + response.error;
                if ($scope.newVersionName !== '') {
                    msg = "Error creating " + $scope.newVersionName + " : " + response.error;
                }
                notification('error', msg);
            };

            if ($scope.selectedParentVersion.length > 0 && $scope.newVersionName !== '') {
                Fabric.createVersionWithParentAndId(jolokia, $scope.selectedParentVersion[0].id, $scope.newVersionName, success, error);
            } else if ($scope.newVersionName !== '') {
                Fabric.createVersionWithId(jolokia, $scope.newVersionName, success, error);
            } else {
                Fabric.createVersion(jolokia, success, error);
            }
        };

        $scope.deleteVersion = function () {
            // avoid getting any not found errors while deleting the version
            Core.unregister(jolokia, $scope);

            Fabric.deleteVersion(jolokia, $scope.version.id, function () {
                notification('success', "Deleted version " + $scope.version.id);
                $scope.version = $scope.defaultVersion;
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to delete version " + $scope.version.id + " due to " + response.error);
                $scope.version = $scope.defaultVersion;
                Core.$apply($scope);
            });
        };

        $scope.deleteSelected = function () {
            $scope.selected.each(function (profile) {
                Fabric.deleteProfile(jolokia, $scope.version.id, profile.id, function () {
                    notification('success', "Deleted profile " + profile.id);
                }, function (response) {
                    notification('error', "Failed to delete profile " + profile.id + ' due to ' + response.error);
                });
            });
        };

        function filterActive(data) {
            var rc = data;
            if ($scope.activeOnly) {
                rc = data.filter(function (item) {
                    return item.containerCount > 0;
                });
            }
            return rc;
        }

        function render(response) {
            clearNotifications();

            if (response.request.operation === 'versions()') {
                if (!Object.equal($scope.versionResponse, response.value)) {
                    $scope.versionResponse = response.value;
                    $scope.versions = response.value.map(function (version) {
                        var v = {
                            id: version.id,
                            'defaultVersion': version.defaultVersion
                        };

                        if (v['defaultVersion']) {
                            $scope.defaultVersion = v;
                        }

                        return v;
                    });
                    $scope.version = Fabric.setSelect($scope.version, $scope.versions);

                    Core.$apply($scope);
                }
            } else {
                if (!Object.equal($scope.profilesResponse, response.value)) {
                    $scope.profilesResponse = response.value;
                    $scope.profiles = [];

                    $scope.profilesResponse.forEach(function (profile) {
                        $scope.profiles.push({
                            id: profile.id,
                            parentIds: profile.parentIds,
                            childIds: profile.childIds,
                            containerCount: profile.containerCount,
                            containers: profile.containers,
                            locked: profile.locked,
                            abstract: profile['abstract']
                        });
                    });

                    $scope.profiles = filterActive($scope.profiles);
                    Core.$apply($scope);
                }
            }
        }
    }
    Fabric.ProfilesController = ProfilesController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function CreateContainerController($scope, $element, $compile, $location, workspace, jolokia, localStorage, userDetails) {
        var log = Logger.get("Fabric");

        if (!('fabric.userName' in localStorage)) {
            localStorage['fabric.userName'] = userDetails.username;
            localStorage['fabric.password'] = userDetails.password;
        }

        $scope.versionsOp = 'versions()';

        $scope.entity = {
            // default options
            number: 1,
            saveJmxCredentials: true
        };

        // the form properties stored in local storage
        // which we then default when creating a new container
        var localStorageProperties = {
            child: {
                jmxUser: 'fabric.userName',
                jmxPassword: 'fabric.password'
            },
            openshift: {
                serverUrl: 'openshift.serverUrl',
                login: 'openshift.login',
                password: 'openshift.password',
                domain: 'openshift.domain',
                gearProfile: 'openshift.gearProfile'
            },
            jclouds: {
                owner: 'jclouds.owner',
                credential: 'jclouds.credential',
                providerName: 'jclouds.providerName',
                imageId: 'jclouds.imageId',
                hardwareId: 'jclouds.hardwareId',
                locationId: 'jclouds.locationId',
                group: 'jclouds.group',
                instanceType: 'jclouds.instanceType'
            }
        };

        $scope.providers = Fabric.registeredProviders(jolokia);

        //console.log("providers: ", $scope.providers);
        $scope.selectedProvider = $scope.providers[Object.extended($scope.providers).keys().first()];
        $scope.resolvers = [];
        $scope.schema = {};

        $scope.response = {};

        $scope.versions = [];
        $scope.profiles = [];

        $scope.selectedVersion = {};

        $scope.selectedProfiles = [];
        $scope.selectedProfileIds = '';
        $scope.selectedVersionId = '';
        $scope.profileIdFilter = '';

        // referenced static data for child
        $scope.child = {
            rootContainers: []
        };

        // referenced static data for openshift
        $scope.openShift = {
            loginDataKey: "openshift.loginData",
            params: null,
            domains: [],
            gearProfiles: [],
            tryLogin: "",
            login: function () {
                var entity = $scope.entity;
                var serverUrl = Core.pathGet(entity, ["serverUrl"]) || "openshift.redhat.com";
                var login = Core.pathGet(entity, ["login"]);
                var password = Core.pathGet(entity, ["password"]);

                log.debug("Invoking login to server " + serverUrl + " user " + login);
                $scope.openShift.loginFailed = false;
                if (serverUrl && login && password) {
                    $scope.openShift.domains = [];
                    Fabric.getOpenShiftDomains(workspace, jolokia, serverUrl, login, password, function (results) {
                        $scope.openShift.domains = results;
                        log.debug("found openshift domains: " + results);

                        // lets default the value if there's only 1
                        if (results.length === 1) {
                            $scope.entity.domain = results[0];
                        }
                        Core.$apply($scope);

                        Fabric.getOpenShiftGearProfiles(workspace, jolokia, serverUrl, login, password, function (results) {
                            $scope.openShift.gearProfiles = results;
                            log.debug("found openshift gears: " + $scope.openShift.gearProfiles);

                            // now lets store the current settings so they can be defaulted next time without a login
                            savePropertiesInLocalStorage();
                            var loginData = {
                                domains: $scope.openShift.domains,
                                gearProfiles: $scope.openShift.gearProfiles
                            };
                            localStorage[$scope.openShift.loginDataKey] = angular.toJson(loginData);
                            Core.$apply($scope);
                        });
                    }, function (error) {
                        $scope.openShift.loginFailed = true;
                        Core.$apply($scope);
                    });
                }
            }
        };

        // referenced static data for jclouds
        $scope.jclouds = {};

        // holds all the form objects from nested child scopes
        $scope.forms = {};

        $scope.showAddProfileDialog = false;

        $scope.$watch('selectedProvider', function (newValue, oldValue) {
            if ($scope.selectedProvider) {
                Fabric.getSchema($scope.selectedProvider.id, $scope.selectedProvider.className, jolokia, function (schema) {
                    $scope.schema = schema;
                    $scope.resolvers = Fabric.getResolvers($scope.selectedProvider.id);
                    Core.$apply($scope);
                });
            }
        }, true);

        $scope.$watch('schema', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.entity['providerType'] = $scope.selectedProvider.id;
                $location.search('tab', $scope.selectedProvider.id);

                var providerId = $scope.entity['providerType'];
                var properties = localStorageProperties[providerId];

                // e.g. key = jmxUser, value = fabric.userName
                //
                //    $scope.entity['jmxUser'] = localStorage['fabric.userName'];
                //    $scope.entity['jmxPassword'] = localStorage['fabric.password'];
                angular.forEach(properties, function (value, key) {
                    var localValue = localStorage[value];
                    if (localValue) {
                        $scope.entity[key] = localValue;
                        log.debug("Defaulted entity " + key + " to " + localValue + " from localStorage");
                    }
                });

                if (providerId === "openshift") {
                    var loginDataText = localStorage[$scope.openShift.loginDataKey];
                    if (loginDataText) {
                        log.debug("Loaded openshift login details: " + loginDataText);
                        var loginData = Wiki.parseJson(loginDataText);
                        if (loginData) {
                            angular.forEach(["domains", "gearProfiles"], function (key) {
                                var value = loginData[key];

                                // assume all non-empty arrays for n ow
                                if (value && angular.isArray(value) && value.length) {
                                    $scope.openShift[key] = value;
                                }
                            });
                        }
                    }
                }

                Forms.defaultValues($scope.entity, $scope.schema);

                if ($scope.selectedProvider.id === 'child') {
                    // load the root containers and default the parent if its not set
                    var rootContainers = Fabric.getRootContainers(jolokia);
                    $scope.child.rootContainers = rootContainers;
                    if (rootContainers && rootContainers.length === 1 && !$scope.entity["parent"]) {
                        $scope.entity["parent"] = rootContainers[0];
                    }
                } else {
                    if ('parent' in $scope.entity) {
                        delete $scope.entity["parent"];
                    }
                }

                // updates autofilled fields
                window.setTimeout(function () {
                    $('input[ng-model]').trigger('input');
                }, 100);
            }
        }, true);

        $scope.$watch('versions', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (!$scope.selectedVersion) {
                    if ($scope.selectedVersionId !== '') {
                        $scope.selectedVersion = $scope.versions.find(function (v) {
                            return v.id === $scope.selectedVersionId;
                        });
                    } else {
                        $scope.selectedVersion = $scope.versions.find(function (v) {
                            return v.defaultVersion;
                        });
                    }
                }
            }
        });

        $scope.$watch('selectedVersion', function (newValue, oldValue) {
            if (oldValue !== newValue) {
                if (newValue && 'id' in newValue) {
                    $scope.selectedVersionId = newValue['id'];
                    $location.search('versionId', $scope.selectedVersionId);
                }
            }
        }, true);

        $scope.deselect = function (profile) {
            profile.selected = false;
            $scope.selectedProfiles.remove(function (p) {
                return p.id === profile.id;
            });
        };

        $scope.$watch('selectedProfiles', function (newValue, oldValue) {
            if (oldValue !== newValue) {
                log.debug("selectedProfiles: ", $scope.selectedProfiles);
                $scope.selectedProfileIds = $scope.selectedProfiles.map(function (p) {
                    return p.id;
                }).join(',');
            }
        }, true);

        $scope.$watch('selectedProfileIds', function (newValue, oldValue) {
            var profileIds = $scope.selectedProfileIds.split(',');
            var selected = [];
            profileIds.each(function (id) {
                selected.push({
                    id: id,
                    selected: true
                });
            });
            $scope.selectedProfiles = selected;
            $location.search('profileIds', $scope.selectedProfileIds);
        });

        $scope.massage = function (str) {
            if (str === 'name') {
                return 'containerName';
            }
            return str;
        };

        $scope.rootContainers = function () {
            return Fabric.getRootContainers(jolokia);
        };

        $scope.init = function () {
            var tab = $location.search()['tab'];
            if (tab) {
                $scope.selectedProvider = $scope.providers[tab];
            }

            var parentId = $location.search()['parentId'];
            if (parentId) {
                $scope.entity['parent'] = parentId;
            }

            var versionId = $location.search()['versionId'];
            if (versionId) {
                $scope.selectedVersion = {
                    id: versionId
                };
            }

            var profileIds = $location.search()['profileIds'];
            if (profileIds) {
                $scope.selectedProfileIds = profileIds;
            }

            var count = $location.search()['number'];
            if (count) {
                $scope.entity.number = count;
            }
        };

        $scope.init();

        $scope.$on('$routeUpdate', $scope.init);

        /**
        * Saves the provider specific properties into localStorage; called on a succesful submit
        * or on a Login in the form so we remember the last successful login attempt.
        */
        function savePropertiesInLocalStorage() {
            var providerId = $scope.entity['providerType'];

            // e.g. key = jmxUser, value = fabric.userName
            //    localStorage['fabric.userName'] = $scope.entity.jmxUser;
            //    localStorage['fabric.password'] = $scope.entity.jmxPassword;
            var properties = localStorageProperties[providerId];

            angular.forEach(properties, function (value, key) {
                var entityValue = $scope.entity[key];
                if (entityValue) {
                    localStorage[value] = entityValue;
                }
            });
        }

        $scope.onSubmit = function (json, form) {
            var providerId = $scope.entity['providerType'];
            if (json.saveJmxCredentials || 'child' !== providerId) {
                savePropertiesInLocalStorage();
            }

            // remove possibly dodgy values if they are blank
            json = Fabric.sanitizeJson(json);
            delete json.saveJmxCredentials;

            if (json.number === 1) {
                delete json.number;
            }

            var selectedVersion = $scope.selectedVersion;
            if (selectedVersion) {
                json['version'] = selectedVersion.id;
            }
            if ($scope.selectedProfiles.length > 0) {
                json['profiles'] = $scope.selectedProfiles.map(function (p) {
                    return p.id;
                });
            }

            var createJson = angular.toJson(json);

            log.debug("createContainers json:\n" + createJson);

            setTimeout(function () {
                jolokia.execute(Fabric.managerMBean, 'createContainers(java.util.Map)', createJson, {
                    method: "post",
                    success: function (response) {
                        log.debug("Response from creating container(s): ", response);
                        var error = false;
                        if ('<not available>' in response) {
                            var message = response['<not available>'];
                            if (message.toLowerCase().has('exception')) {
                                error = true;
                                var cont = "container";
                                if (json.number) {
                                    cont = Core.maybePlural(json.number, "container");
                                }
                                notification('error', "Creating " + cont + " failed: " + message);
                            }
                        }

                        // check for error if a container already exists with that name
                        var text = response[json.name];
                        if (text && text.toLowerCase().has('already exists')) {
                            error = true;
                            notification('error', "Creating container " + json.name + " failed as a container with that name already exists.");
                        }

                        angular.forEach(response.value, function (value, key) {
                            error = true;
                            notification('error', "Creating container " + key + " failed: " + value);
                        });
                        if (!error) {
                            notification('success', "Successfully created containers");
                        }
                        Core.$apply($scope);
                    },
                    error: function (response) {
                        notification('error', "Error creating containers: " + response.error);
                        Core.$apply($scope);
                    }
                });
                Core.$apply($scope);
            }, 10);

            //notification('info', "Requesting that new container(s) be created");
            $location.url('/fabric/containers');
        };
    }
    Fabric.CreateContainerController = CreateContainerController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ContainersController($scope, $location, $route, jolokia, workspace) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        // bind model values to search params...
        Core.bindModelToSearchParam($scope, $location, "containerIdFilter", "q", "");

        // only reload the page if certain search parameters change
        Core.reloadWhenParametersChange($route, $scope, $location);

        $scope.locationMenu = {
            icon: 'icon-beer',
            title: 'Set Location',
            items: []
        };

        $scope.noLocation = "(No Location)";
        $scope.newLocationDialog = {
            dialog: new UI.Dialog(),
            onOk: function () {
                $scope.newLocationDialog.close();
                $scope.selectedContainers.each(function (container) {
                    Fabric.setContainerProperty(jolokia, container.id, 'location', $scope.newLocationName, function () {
                        Core.$apply($scope);
                    }, function () {
                        Core.$apply($scope);
                    });
                });
            },
            open: function () {
                $scope.newLocationDialog.dialog.open();
            },
            close: function () {
                $scope.newLocationDialog.dialog.close();
            }
        };
        $scope.newLocationName = "";

        $scope.addToDashboardLink = function () {
            var href = "#/fabric/containers";
            var title = "Containers";
            var size = angular.toJson({ size_y: 1, size_x: 4 });

            return "#/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&size=" + encodeURIComponent(size) + "&title=" + encodeURIComponent(title);
        };

        $scope.$watch('containers', function (oldValue, newValue) {
            if (oldValue !== newValue) {
                $scope.selectedContainers = $scope.containers.filter(function (c) {
                    return c.selected;
                });

                var menuItems = [];

                var locations = $scope.containers.map(function (container) {
                    if (Core.isBlank(container['location'])) {
                        return $scope.noLocation;
                    } else {
                        return container['location'];
                    }
                });
                locations.push($scope.noLocation);
                locations = locations.unique().sortBy();
                locations = locations.exclude(function (location) {
                    return Core.isBlank(location);
                });

                locations.forEach(function (location) {
                    menuItems.push({
                        title: location,
                        action: function () {
                            $scope.selectedContainers.each(function (container) {
                                var arg = location;
                                if (arg === $scope.noLocation) {
                                    arg = "";
                                }
                                Fabric.setContainerProperty(jolokia, container.id, 'location', arg, function () {
                                    Core.$apply($scope);
                                }, function () {
                                    Core.$apply($scope);
                                });
                            });
                        }
                    });
                });

                menuItems.push({
                    title: "New...",
                    action: function () {
                        $scope.newLocationName = "";
                        $scope.newLocationDialog.open();
                    }
                });

                $scope.locationMenu.items = menuItems;

                if ($scope.selectedContainers.length > 0) {
                    $scope.activeContainerId = '';
                }
            }
        }, true);
    }
    Fabric.ContainersController = ContainersController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function ActiveProfileController($scope, jolokia) {
        $scope.addToDashboardLink = function () {
            var href = "#/fabric/activeProfiles";
            var title = "Active Profiles";
            var size = angular.toJson({
                size_y: 1,
                size_x: 5
            });

            return "#/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&size=" + encodeURIComponent(size) + "&title=" + encodeURIComponent(title);
        };
    }
    Fabric.ActiveProfileController = ActiveProfileController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function FabricApisController($scope, localStorage, $routeParams, $location, jolokia, workspace, $compile, $templateCache) {
        $scope.path = "apis";

        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.apis = null;
        $scope.selectedApis = [];

        $scope.versionId = Fabric.getDefaultVersionId(jolokia);

        $scope.apiOptions = {
            //plugins: [searchProvider],
            data: 'apis',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                filterText: "",
                useExternalFilter: false
            },
            selectedItems: $scope.selectedApis,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'serviceName',
                    displayName: 'Service',
                    cellTemplate: '<div class="ngCellText">{{row.entity.serviceName}}</div>',
                    //width: 400
                    width: "***"
                },
                {
                    field: 'wadlHref',
                    displayName: 'APIs',
                    cellTemplate: '<div class="ngCellText">' + '<a ng-show="row.entity.apidocsHref" ng-href="{{row.entity.apidocsHref}}"><i class="icon-puzzle-piece"></i> Swagger</a> ' + '<a ng-show="row.entity.wadlHref" ng-href="{{row.entity.wadlHref}}"><i class="icon-puzzle-piece"></i> WADL</a> ' + '<a ng-show="row.entity.wsdlHref" ng-href="{{row.entity.wsdlHref}}"><i class="icon-puzzle-piece"></i> WSDL</a>' + '</div>',
                    //width: 100
                    width: "*"
                },
                {
                    field: 'container',
                    displayName: 'Container',
                    cellTemplate: '<div class="ngCellText"><span fabric-container-link="{{row.entity.container}}"/></div>',
                    //width: 100
                    width: "*"
                },
                {
                    field: 'version',
                    displayName: 'Version',
                    cellTemplate: '<div class="ngCellText">{{row.entity.version}}</div>',
                    //width: 100
                    width: "*"
                },
                {
                    field: 'endpoint',
                    displayName: 'Location',
                    cellTemplate: '<div class="ngCellText"><a target="endpoint" href="{{row.entity.endpoint}}">{{row.entity.endpoint}}</a></div>',
                    width: "***"
                }
            ]
        };

        function matchesFilter(text) {
            var filter = $scope.searchFilter;
            return !filter || (text && text.has(filter));
        }

        if (Fabric.fabricCreated(workspace)) {
            Core.register(jolokia, $scope, {
                type: 'exec',
                mbean: Fabric.managerMBean,
                operation: "clusterJson",
                arguments: [$scope.path] }, onSuccess(onClusterData, { error: onClusterDataError }));
        }

        /*
        * Pulls all the properties out of the objectName and adds them to the object
        */
        function addObjectNameProperties(object) {
            var objectName = object["objectName"];
            if (objectName) {
                var properties = Core.objectNameProperties(objectName);
                if (properties) {
                    angular.forEach(properties, function (value, key) {
                        if (!object[key]) {
                            object[key] = value;
                        }
                    });
                }
            }
            return null;
        }

        function createFlatList(array, json, path) {
            if (typeof path === "undefined") { path = ""; }
            angular.forEach(json, function (value, key) {
                var childPath = path + "/" + key;

                function addParameters(href) {
                    angular.forEach(["container", "objectName"], function (name) {
                        var param = value[name];
                        if (param) {
                            href += "&" + name + "=" + encodeURIComponent(param);
                        }
                    });
                    return href;
                }

                // lets check if we are a services object or a folder
                var services = value["services"];
                if (services && angular.isArray(services) && value["id"]) {
                    value["path"] = childPath;
                    if (services.length) {
                        var url = services[0];
                        value["endpoint"] = url;
                        addObjectNameProperties(value);

                        // lets use proxy if external URL
                        url = Core.useProxyIfExternal(url);
                        value["serviceName"] = trimQuotes(value["service"]);
                        var apidocs = value["apidocs"];
                        var wadl = value["wadl"];
                        var wsdl = value["wsdl"];
                        if (apidocs) {
                            value["apidocsHref"] = addParameters("/hawtio-swagger/index.html?baseUri=" + url + apidocs);
                        }
                        if (wadl) {
                            value["wadlHref"] = addParameters("#/fabric/api/wadl?wadl=" + encodeURIComponent(url + wadl));
                        }
                        if (wsdl) {
                            value["wsdlHref"] = addParameters("#/fabric/api/wsdl?wsdl=" + encodeURIComponent(url + wsdl));
                        }
                    }
                    array.push(value);
                } else {
                    createFlatList(array, value, childPath);
                }
            });
        }

        function onClusterData(response) {
            var responseJson = null;
            if (response) {
                responseJson = response.value;
            }
            if ($scope.responseJson === responseJson) {
                return;
            }
            $scope.apis = [];
            $scope.responseJson = responseJson;

            try  {
                var json = JSON.parse(responseJson);
                createFlatList($scope.apis, json);
                Core.$apply($scope);
            } catch (e) {
                console.log("Failed to parse JSON " + e);
                console.log("JSON: " + responseJson);
            }
        }

        function onClusterDataError(response) {
            // make sure we initialise the apis so we know to show the warning of no
            // APIs available yet
            $scope.apis = [];
            Core.$apply($scope);
            Core.defaultJolokiaErrorHandler(response);
        }
    }
    Fabric.FabricApisController = FabricApisController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function VersionSelector($templateCache) {
        return {
            restrict: 'A',
            replace: true,
            templateUrl: Fabric.templatePath + "versionSelector.html",
            scope: {
                selectedVersion: '=fabricVersionSelector',
                availableVersions: '=?',
                menuBind: '=?',
                exclude: '@'
            },
            controller: function ($scope, $element, $attrs, jolokia) {
                $scope.versions = [];
                $scope.responseJson = '';

                $scope.$watch('selectedVersion', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if (newValue && 'id' in newValue) {
                            $scope.selectedVersion = $scope.versions.find(function (version) {
                                return version.id === newValue['id'];
                            });
                        } else {
                            $scope.selectedVersion = $scope.versions.find(function (version) {
                                return version.defaultVersion;
                            });
                        }
                    }
                });

                $scope.$watch('versions', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if ($scope.selectedVersion && 'id' in $scope.selectedVersion) {
                            $scope.selectedVersion = $scope.versions.find(function (version) {
                                return version.id === $scope.selectedVersion['id'];
                            });
                        } else {
                            $scope.selectedVersion = $scope.versions.find(function (version) {
                                return version.defaultVersion;
                            });
                        }
                    }
                }, true);

                function excludeVersions(versions, exclude) {
                    if (angular.isString(exclude)) {
                        if (exclude.has("[") && exclude.has("]")) {
                            exclude = angular.fromJson(exclude);
                        } else {
                            exclude = [exclude];
                        }
                    }

                    //log.debug("exclude: ", exclude);
                    if (!exclude || exclude.length === 0) {
                        return versions;
                    }
                    return versions.exclude(function (v) {
                        return exclude.some(function (e) {
                            return e === v.id;
                        });
                    });
                }

                function generateMenu(versions) {
                    return $scope.versions.map(function (v) {
                        return {
                            title: v.id,
                            action: function () {
                                $scope.selectedVersion = v;
                                if (!Core.isBlank($scope.onPick)) {
                                    $scope.$parent.$eval($scope.onPick, {
                                        version: v['id']
                                    });
                                }
                            }
                        };
                    });
                }

                $scope.$watch('exclude', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        // need to rebuild the original version list
                        if ($scope.responseJson) {
                            var versions = angular.fromJson($scope.responseJson);
                            buildArray(versions);
                        }
                    }
                });

                function buildArray(versions) {
                    //log.debug("Building array from: ", versions);
                    $scope.versions = Fabric.sortVersions(versions, $scope.desc);
                    $scope.versions = excludeVersions($scope.versions, $scope.exclude);
                    if ($scope.config) {
                        $scope.config.items = generateMenu($scope.versions);
                    }
                    $scope.availableVersions = $scope.versions;
                }

                $scope.render = function (response) {
                    var responseJson = angular.toJson(response.value);
                    if ($scope.responseJson !== responseJson) {
                        $scope.responseJson = responseJson;
                        buildArray(response.value);
                        Core.$apply($scope);
                    }
                };

                Core.register(jolokia, $scope, {
                    type: 'exec',
                    mbean: Fabric.managerMBean,
                    operation: 'versions(java.util.List)',
                    arguments: [
                        ['id', 'defaultVersion']
                    ]
                }, onSuccess($scope.render));
            },
            link: function ($scope, $element, $attrs) {
                $scope.template = $templateCache.get('withSelect');
                if (Core.parseBooleanValue($attrs['useMenu'])) {
                    $scope.config = {
                        title: 'Version'
                    };
                    if (!Core.isBlank($attrs['menuTitle'])) {
                        $scope.config.title = $attrs['menuTitle'];
                    }
                    if (!Core.isBlank($attrs['menuBind'])) {
                        $scope.$watch('menuBind', function (newValue, oldValue) {
                            if (!Core.isBlank(newValue)) {
                                $scope.config.title = newValue;
                            }
                        });
                    }
                    if (!Core.isBlank($attrs['onPick'])) {
                        $scope.onPick = $attrs['onPick'];
                    }
                    if (!Core.isBlank($attrs['useIcon'])) {
                        $scope.config.icon = $attrs['useIcon'];
                    }
                    $scope.desc = 'desc' in $attrs;
                    $scope.template = $templateCache.get('withMenu');
                }
            }
        };
    }
    Fabric.VersionSelector = VersionSelector;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    function FabricViewController($scope, $location, jolokia, localStorage, workspace) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        $scope.containerArgs = ["id", "alive", "parentId", "profileIds", "versionId", "provisionResult", "jolokiaUrl", "root"];
        $scope.containersOp = 'containers(java.util.List)';
        $scope.ensembleContainerIdListOp = 'EnsembleContainers';

        $scope.init = function () {
            var activeVersionId = $location.search()['cv'];
            if (activeVersionId) {
                $scope.activeVersionId = activeVersionId;
                $scope.activeVersion = {
                    id: $scope.activeVersionId
                };
            }

            var profiles = $location.search()['sp'];
            $scope.selectedProfileIds = [];
            if (profiles) {
                $scope.selectedProfileIds = profiles.split(',');
            }

            var containers = $location.search()['sc'];
            $scope.selectedContainerIds = [];
            if (containers) {
                $scope.selectedContainerIds = containers.split(',');
            }
        };

        $scope.versions = [];
        $scope.profiles = [];
        $scope.containers = [];
        $scope.activeProfiles = [];

        $scope.activeVersion = {};
        $scope.activeVersionId = '';
        $scope.selectedContainers = [];
        $scope.selectedProfiles = [];
        $scope.selectedActiveProfiles = [];

        $scope.dialogProfiles = [];

        $scope.profileIdFilter = '';
        $scope.activeProfileIdFilter = '';
        $scope.containerIdFilter = '';

        $scope.filterActiveVersion = false;
        $scope.filterActiveProfile = false;

        $scope.deleteVersionDialog = new UI.Dialog();
        $scope.deleteProfileDialog = new UI.Dialog();
        $scope.createProfileDialog = new UI.Dialog();

        $scope.ensembleContainerIds = [];
        $scope.profileSelectedAll = false;
        $scope.profileNoneSelected = true;

        $scope.targetContainer = {};

        // Tweaks to ensure ng-grid displays on dialogs
        $scope.triggerResize = function () {
            setTimeout(function () {
                $('.dialogGrid').trigger('resize');
            }, 10);
        };

        /*
        $scope.$watch('createProfileDialog', function() {
        if ($scope.createProfileDialog) {
        $scope.triggerResize();
        }
        });
        
        $scope.$watch('createVersionDialog', function() {
        if ($scope.createVersionDialog) {
        $scope.triggerResize();
        }
        });
        */
        // holders for dialog data
        $scope.newProfileName = '';
        $scope.selectedParents = [];
        $scope.selectedParentVersion = [];

        $scope.$on('$routeUpdate', $scope.init);

        // watchers for selection handling
        $scope.$watch('activeVersionId', function (oldValue, newValue) {
            $location.search('cv', $scope.activeVersionId);
        });

        $scope.$watch('activeVersion', function (newValue, oldValue) {
            if (newValue !== oldValue && $scope.activeVersion && $scope.activeVersion.id !== $scope.activeVersionId) {
                $scope.activeVersionId = $scope.activeVersion.id;
            }
        });

        $scope.$watch('containers', function (oldValue, newValue) {
            if (oldValue !== newValue) {
                $scope.selectedContainers = $scope.containers.filter(function (c) {
                    return c.selected;
                });

                if ($scope.selectedContainers.length > 0) {
                    $scope.activeContainerId = '';
                }
            }
        }, true);

        $scope.$watch('activeProfiles', function (oldValue, newValue) {
            if (oldValue !== newValue) {
                $scope.selectedActiveProfiles = $scope.activeProfiles.filter(function (ap) {
                    return ap.selected;
                });
            }
        }, true);

        $scope.$watch('selectedProfiles', function (oldValue, newValue) {
            if (oldValue !== newValue) {
                var ids = $scope.getSelectedProfileIds().join(',');
                $location.search('sp', ids);
            }
        }, true);

        $scope.$watch('selectedContainers', function (oldValue, newValue) {
            if (oldValue !== newValue) {
                var ids = $scope.getSelectedContainerIds().join(',');
                $location.search('sc', ids);
            }
        }, true);

        // initialize the scope after we set all our watches
        $scope.init();

        // create profile dialog action
        $scope.doCreateProfile = function (newProfileName, selectedParents) {
            $scope.newProfileName = newProfileName;
            $scope.createProfileDialog.close();
            var parents = selectedParents.map(function (profile) {
                return profile.id;
            });
            Fabric.createProfile(jolokia, $scope.activeVersionId, $scope.newProfileName, parents, function () {
                notification('success', "Created profile " + $scope.newProfileName);
                $scope.profileIdFilter = $scope.newProfileName;
                $scope.newProfileName = "";
                Core.$apply($scope);
            }, function (response) {
                notification('error', "Failed to create profile " + $scope.newProfileName + " due to " + response.error);
                Core.$apply($scope);
            });
        };

        // delete version dialog action
        $scope.deleteVersion = function () {
            var id = $scope.activeVersionId;

            jolokia.request({
                type: 'read',
                mbean: Fabric.managerMBean,
                attribute: 'DefaultVersion'
            }, onSuccess(function (response) {
                $scope.activeVersionId = response.value;
                Core.$apply($scope);
                setTimeout(function () {
                    Fabric.deleteVersion(jolokia, id, function () {
                        notification('success', "Deleted version " + id);
                        Core.$apply($scope);
                    }, function (response) {
                        notification('error', "Failed to delete version " + id + " due to " + response.error);
                        Core.$apply($scope);
                    });
                }, 100);
            }));
        };

        $scope.deleteSelectedProfiles = function () {
            $scope.selectedProfiles.each(function (profile) {
                var profileId = profile.id;
                Fabric.deleteProfile(jolokia, $scope.activeVersionId, profileId, function () {
                    notification('success', "Deleted profile " + profileId);
                }, function (response) {
                    notification('error', "Failed to delete profile " + profileId + ' due to ' + response.error);
                });
            });
        };

        $scope.patchVersion = function (versionId) {
            $location.url('/fabric/patching').search({ versionId: versionId });
        };

        $scope.migrateVersion = function (targetName, sourceName) {
            notification('info', "Moving " + targetName + " to " + sourceName);

            Fabric.migrateContainers(jolokia, sourceName, [targetName], function () {
                notification('success', "Moved " + targetName + " to version " + sourceName);
            }, function (response) {
                notification('error', "Failed to move " + targetName + " to version " + sourceName + " due to " + response.error);
            });
        };

        $scope.addProfiles = function (targetName, profiles) {
            notification('info', "Adding " + profiles.join(', ') + " to " + targetName);

            Fabric.addProfilesToContainer(jolokia, targetName, profiles, function () {
                notification('success', "Added " + profiles.join(', ') + " to " + targetName);
            }, function (response) {
                notification('error', "Failed to add " + profiles.join(', ') + " to " + targetName + " due to " + response.error);
            });
        };

        $scope.removeActiveProfiles = function () {
            $scope.selectedActiveProfiles.each(function (profile) {
                $scope.removeActiveProfile(profile);
            });
        };

        $scope.removeActiveProfile = function (profile) {
            if ($scope.selectedContainers.length > 0) {
                $scope.selectedContainers.each(function (container) {
                    if (container.profileIds.some(profile.id) && container.versionId === profile.versionId) {
                        $scope.removeProfile(container.id, profile.id);
                    }
                });
            } else {
                $scope.removeProfile($scope.activeContainerId, profile.id);
            }
        };

        $scope.removeProfile = function (containerId, profileId) {
            notification('info', "Removing " + profileId + " from " + containerId);

            Fabric.removeProfilesFromContainer(jolokia, containerId, [profileId], function () {
                notification('success', "Removed " + profileId + " from " + containerId);
            }, function (response) {
                notification('error', "Failed to remove " + profileId + " from " + containerId + " due to " + response.error);
            });
        };

        $scope.getFilteredName = function (item) {
            return (item.versionId + " / " + item.id);
        };

        $scope.filterContainer = function (container) {
            if ($scope.containerIdFilter) {
                var filterName = $scope.getFilteredName(container);
                var filterText = $scope.containerIdFilter;
                if (!Core.matchFilterIgnoreCase(filterName, filterText)) {
                    return false;
                }
            }

            if ($scope.selectedActiveProfiles.length > 0) {
                if ($scope.selectedActiveProfiles.none(function (ap) {
                    return ap.versionId === container.versionId && container.profileIds.any(function (id) {
                        return Core.matchFilterIgnoreCase(id, ap.id);
                    });
                })) {
                    return false;
                }
            }

            return true;
        };

        $scope.filterActiveProfile = function (profile) {
            var filterText = $scope.activeProfileIdFilter;
            var filterName = $scope.getFilteredName(profile);
            if (!Core.matchFilterIgnoreCase(filterName, filterText)) {
                return false;
            }

            if ($scope.filterActiveVersion && $scope.activeVersionId && $scope.activeVersionId !== '' && profile.versionId !== $scope.activeVersionId) {
                return false;
            }

            if ($scope.selectedContainers.length > 0) {
                if ($scope.selectedContainers.none(function (c) {
                    return c.versionId === profile.versionId && c.profileIds.some(profile.id);
                })) {
                    return false;
                }
            }

            if ($scope.activeContainerId && $scope.activeContainerId !== '') {
                if ($scope.activeContainerVersion && $scope.activeContainerVersion !== '' && $scope.activeContainerVersion !== profile.versionId) {
                    return false;
                }
                if (!profile.containers.some($scope.activeContainerId)) {
                    return false;
                }
            }
            return true;
        };

        $scope.showMigrateButton = function () {
            return $scope.selectedContainers.length > 0 && $scope.activeVersionId && $scope.activeVersionId !== '';
        };

        $scope.applyVersionToContainers = function () {
            $scope.selectedContainers.each(function (c) {
                $scope.migrateVersion(c.id, $scope.activeVersionId);
            });
        };

        $scope.showProfileAddButton = function () {
            return $scope.selectedProfiles.length > 0 && $scope.selectedContainers.length > 0 && $scope.selectedContainers.every(function (c) {
                return c.versionId === $scope.activeVersionId;
            });
        };

        $scope.addProfilesToContainers = function () {
            var profileIds = $scope.selectedProfiles.map(function (p) {
                return p.id;
            });

            $scope.selectedContainers.each(function (c) {
                $scope.addProfiles(c.id, profileIds);
            });
        };

        $scope.versionCanBeDeleted = function () {
            return $scope.containers.none(function (c) {
                return c.versionId === $scope.activeVersionId;
            });
        };

        $scope.profilesCanBeDeleted = function () {
            var possibleMatches = $scope.containers.filter(function (c) {
                return c.versionId === $scope.activeVersionId;
            });

            if (possibleMatches.length === 0) {
                return true;
            }

            possibleMatches = possibleMatches.filter(function (c) {
                return $scope.selectedProfiles.some(function (p) {
                    return c.profileIds.some(p.id);
                });
            });

            if (possibleMatches.length === 0) {
                return true;
            }
            return false;
        };

        $scope.getSelectedProfileIds = function () {
            return $scope.getIds($scope.selectedProfiles);
        };

        $scope.getSelectedContainerIds = function () {
            return $scope.getIds($scope.selectedContainers);
        };

        $scope.getIds = function (arr) {
            return arr.map(function (o) {
                return o.id;
            });
        };

        $scope.containersForVersion = function (id) {
            var count = $scope.containers.findAll(function (container) {
                return container.versionId === id;
            }).length;
            if (count === 0) {
                return '';
            }
            return "(" + count + ")";
        };

        $scope.containersForProfile = function (id) {
            var profile = $scope.currentActiveProfiles().find(function (profile) {
                return profile.versionId === $scope.activeVersionId && profile.id === id;
            });
            if (profile) {
                return "(" + profile.count + ")";
            } else {
                return "";
            }
        };

        $scope.isSelectedVersion = function (id) {
            if ($scope.activeVersionId === id) {
                return 'selected';
            }
            return '';
        };

        $scope.getSelectedClass = function (obj) {
            var answer = [];
            if (obj.selected) {
                answer.push('selected');
            }
            if (angular.isDefined(obj['root']) && obj['root'] === false) {
                answer.push('child-container');
            }
            return answer.join(' ');
        };

        $scope.setActiveVersionId = function (id) {
            $scope.activeVersionId = id;
        };

        $scope.showProfile = function (profile) {
            if (angular.isDefined(profile.versionId)) {
                Fabric.gotoProfile(workspace, jolokia, localStorage, $location, profile.versionId, profile);
            } else {
                Fabric.gotoProfile(workspace, jolokia, localStorage, $location, $scope.activeVersionId, profile);
            }
        };
    }
    Fabric.FabricViewController = FabricViewController;
})(Fabric || (Fabric = {}));
var Fabric;
(function (Fabric) {
    ;

    var IconRegistry = (function () {
        function IconRegistry() {
            this.icons = {};
        }
        IconRegistry.prototype.addIcons = function (icon, domain) {
            var _this = this;
            var domains = [];
            for (var _i = 0; _i < (arguments.length - 2); _i++) {
                domains[_i] = arguments[_i + 2];
            }
            this.addIcon(icon, domain);
            if (domains && angular.isArray(domains)) {
                domains.forEach(function (domain) {
                    _this.addIcon(icon, domain);
                });
            }
        };

        IconRegistry.prototype.addIcon = function (icon, domain) {
            this.icons[domain] = icon;
        };

        IconRegistry.prototype.getIcons = function (things) {
            var _this = this;
            var answer = [];
            if (things && angular.isArray(things)) {
                things.forEach(function (thing) {
                    if (_this.icons[thing]) {
                        answer.push(_this.icons[thing]);
                    }
                });
            }
            return answer.unique();
        };

        IconRegistry.prototype.getIcon = function (thing) {
            Fabric.log.debug("Returning icon for: ", thing);
            return this.icons[thing];
        };
        return IconRegistry;
    })();
    Fabric.IconRegistry = IconRegistry;

    // Common icons that functions could return directly
    Fabric.javaIcon = {
        title: "Java",
        type: "img",
        src: "img/icons/java.svg"
    };

    // Service Icon Registry, maps icons to JMX domains
    Fabric.serviceIconRegistry = new IconRegistry();

    Fabric.serviceIconRegistry.addIcons({
        title: "Fabric8",
        type: "img",
        src: "img/icons/fabric8_icon.svg"
    }, "io.fabric8", "org.fusesource.fabric");

    Fabric.serviceIconRegistry.addIcons({
        title: "Fabric8 Insight",
        type: "icon",
        src: "icon-eye-open"
    }, "org.fusesource.insight", "io.fabric8.insight");

    Fabric.serviceIconRegistry.addIcons({
        title: "hawtio",
        type: "img",
        src: "img/hawtio_icon.svg"
    }, "hawtio");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache ActiveMQ",
        type: "img",
        src: "img/icons/messagebroker.svg"
    }, "org.apache.activemq");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache Camel",
        type: "img",
        src: "img/icons/camel.svg"
    }, "org.apache.camel");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache CXF",
        type: "icon",
        src: "icon-puzzle-piece"
    }, "org.apache.cxf");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache Karaf",
        type: "icon",
        src: "icon-beaker"
    }, "org.apache.karaf");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache Zookeeper",
        type: "icon",
        src: "icon-group"
    }, "org.apache.zookeeper");

    Fabric.serviceIconRegistry.addIcons({
        title: "Jetty",
        type: "img",
        src: "img/icons/jetty.svg"
    }, "org.eclipse.jetty.server");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache Tomcat",
        type: "img",
        src: "img/icons/tomcat.svg"
    }, "Catalina", "Tomcat");

    Fabric.serviceIconRegistry.addIcons({
        title: "Apache Cassandra",
        type: "img",
        src: "img/icons/cassandra.svg",
        "class": "girthy"
    }, "org.apache.cassandra.db", "org.apache.cassandra.metrics", "org.apache.cassandra.net", "org.apache.cassandra.request");

    // Container Icon Registry, maps icons to container types
    Fabric.containerIconRegistry = new IconRegistry();

    Fabric.containerIconRegistry.addIcons({
        title: "Apache Karaf",
        type: "icon",
        src: "icon-beaker"
    }, "karaf");

    Fabric.containerIconRegistry.addIcons({
        title: "Apache Cassandra",
        type: "img",
        src: "img/icons/cassandra.svg",
        "class": "girthy"
    }, "Cassandra");

    Fabric.containerIconRegistry.addIcons({
        title: "Apache Tomcat",
        type: "img",
        src: "img/icons/tomcat.svg"
    }, "Tomcat");

    // TODO could use a TomEE specific icon really
    Fabric.containerIconRegistry.addIcons({
        title: "Apache TomEE",
        type: "img",
        src: "img/icons/tomcat.svg"
    }, "TomEE");

    Fabric.containerIconRegistry.addIcons({
        title: "Jetty",
        type: "img",
        src: "img/icons/jetty.svg"
    }, "Jetty");

    // TODO - placeholder for Java containers
    Fabric.containerIconRegistry.addIcons(Fabric.javaIcon, "java");
})(Fabric || (Fabric = {}));
/**
* @module ElasticSearch
* @main ElasticSearch
*/
var ES;
(function (ES) {
    var pluginName = 'elasticsearch';
    var base_url = 'app/elasticsearch/html';

    /* Application level module which depends on filters, controllers, and services */
    angular.module(pluginName, ['bootstrap', 'ngResource', 'elasticjs.service', 'dangle']).config([
        '$routeProvider', function ($routeProvider) {
            $routeProvider.when('/elasticsearch', { templateUrl: base_url + '/es.html' });
        }]).run(function ($location, workspace, viewRegistry, helpRegistry) {
        // Use Full Layout of Hawtio
        viewRegistry[pluginName] = 'app/elasticsearch/html/es.html';

        helpRegistry.addUserDoc(pluginName, 'app/elasticsearch/doc/help.md', function () {
            // TODO not sure how this plugin actually shows up in the toolbar
            return false;
        });
        /*
        // Set up top-level link to our plugin
        workspace.topLevelTabs.push({
        content: "ElasticSearch",
        title: "ElasticSearch",
        isValid: (workspace) => true,
        href: () => '#/elasticsearch',
        isActive: (workspace:Workspace) => workspace.isLinkActive("elasticsearch")
        });
        */
    });

    hawtioPluginLoader.addModule(pluginName);
})(ES || (ES = {}));
var ES;
(function (ES) {
    // Function to test if a property is empty, not null
    function isEmptyObject(value) {
        return $.isEmptyObject(value);
    }
    ES.isEmptyObject = isEmptyObject;

    // Search Angular Controller used by ES
    function SearchCtrl($scope, $location, $log, ejsResource) {
        // Retrieve by default parameters from config.js
        //var defaultEsServer = $scope.defaultEsServer = "http://localhost:9200";
        var esServer = $scope.esServer = ES.config["elasticsearch"];
        var query = $scope.queryTerm = ES.config["query"];
        var facetField = $scope.facetField = "tags";
        var facetType = $scope.facetType = "terms";
        var index = $scope.indice = ES.config["indice"];
        var type = $scope.docType = ES.config["doctype"];
        var ejs;
        var request;
        $scope.log = $log;

        /* Define search function that will be called when a user
        submits a Query String search
        Query syntax : *
        Query syntax : field: 'value'
        Query syntax : field: 'value' AND field: 'value'
        Query syntax : field: 'value' OR field: 'value'
        where value corresponds to text to search ortext + * symbol
        */
        $scope.search = function () {
            if (isEmptyObject(ejs)) {
                console.log("Init EJS server");
                ejs = initElasticsearchServer(esServer);
            }

            // Initialize ES Server to send request
            setupEsRequest();

            // Setup Query String
            request = request.query(ejs.QueryStringQuery(query));

            // Run query
            var results = request.doSearch();

            console.log("Do Elastic Search");

            results.then(function (results) {
                //$location.path("/elasticjs");
                // Reset field after search
                $scope.queryTerm = "";

                if (typeof results.error != 'undefined') {
                    // Message should be displayed in the web page as a modal window
                    console.error("ES error : " + results.error);

                    // Solution proposed by kibana3
                    // $scope.panel.error = $scope.parse_error(results.error);
                    return;
                }

                console.log(results.hits.total + " : results retrieved");
                $scope.results = results;
            });
        };

        $scope.facetTermsSearch = function () {
            if (isEmptyObject(ejs)) {
                console.log("Init EJS server");
                ejs = initElasticsearchServer(esServer);
            }

            // Initialize ES Server to send request
            setupEsRequest();

            if (!isEmptyObject($scope.facetField)) {
                facetField = $scope.facetField;
            }

            if (!isEmptyObject($scope.facetType)) {
                facetType = $scope.facetType;
            }

            // Setup QueryString and Facets
            request = request.query(ejs.QueryStringQuery(query)).facet(ejs.TermsFacet("termFacet").field(facetField).size(50));

            // Run query
            var results = request.doSearch();

            console.log("Do Elastic Search");

            results.then(function (results) {
                //$location.path("/elasticjs");
                // Reset field after search
                $scope.queryTerm = "";

                if (typeof results.error != 'undefined') {
                    // Message should be displayed in the web page as a modal window
                    console.error("ES error : " + results.error);

                    // Solution proposed by kibana3
                    // $scope.panel.error = $scope.parse_error(results.error);
                    return;
                }

                console.log(results.hits.total + " : results retrieved");
                $scope.results = results;
            });
        };

        $scope.facetDateHistogramSearch = function () {
            if (isEmptyObject(ejs)) {
                console.log("Init EJS server");
                ejs = initElasticsearchServer(esServer);
            }

            // Initialize ES Server to send request
            setupEsRequest();

            if (!isEmptyObject($scope.facetField)) {
                facetField = $scope.facetField;
            }

            if (!isEmptyObject($scope.facetType)) {
                facetType = $scope.facetType;
            }

            // Setup QueryString and Facets
            request = request.query(ejs.QueryStringQuery(query)).facet(ejs.DateHistogramFacet("dateHistoFacet").field(facetField).interval("minute"));

            // Run query
            var results = request.doSearch();

            console.log("Do Elastic Search");

            results.then(function (results) {
                //$location.path("/elasticjs");
                // Reset field after search
                $scope.queryTerm = "";

                if (typeof results.error != 'undefined') {
                    // Message should be displayed in the web page as a modal window
                    console.error("ES error : " + results.error);

                    // Solution proposed by kibana3
                    // $scope.panel.error = $scope.parse_error(results.error);
                    return;
                }

                console.log(results.hits.total + " : results retrieved");
                $scope.results = results;
            });
        };

        // index the sample documents using data
        // coming from json file
        $scope.indexSampleDocs = function () {
            var host = "http://" + location.host;

            if (isEmptyObject(ejs)) {
                console.log("EJS object is not defined - create it - setupEsRequest");
                ejs = initElasticsearchServer(esServer);
            }

            // Load json records from JSON file
            // & create elastcsearch document
            var docs = [];
            $.getJSON(host + "/hawtio/app/elasticsearch/js/data.json", function (result) {
                $.each(result, function (i, field) {
                    console.log("Field : " + field);
                    docs[i] = ejs.Document(index, type, i).source(field);
                    docs[i].refresh(true).doIndex();
                });
            });
            // Using sugarjs & ECMA5 forEach
            /*
            var doSearch = ( $scope.search ).after(docs.length);
            docs.forEach(function (doc) {
            console.log("Do Index called");
            doc.refresh(true).doIndex(doSearch);
            });
            */
        };

        function setupEsRequest() {
            console.log("ES Server = " + $scope.esServer);
            console.log("Indice = " + $scope.indice);
            console.log("Type = " + $scope.docType);
            console.log("Query = " + $scope.queryTerm);

            if (!isEmptyObject($scope.indice)) {
                index = $scope.indice;
            }

            if (!isEmptyObject($scope.esServer)) {
                esServer = $scope.esServer;
            }

            if (!isEmptyObject($scope.docType)) {
                type = $scope.docType;
            }

            if (!isEmptyObject($scope.queryTerm)) {
                query = $scope.queryTerm;
            }

            var ejs = ejsResource($scope.esServer);

            // Define Request to call ES
            request = ejs.Request().indices(index).types(type);

            console.log("Request to call ElasticSearch defined");
        }

        function initElasticsearchServer(esServer) {
            return ejsResource(esServer);
        }

        $scope.parse_error = function (data) {
            var _error = data.match("nested: (.*?);");
            return _error == null ? data : _error[1];
        };
    }
    ES.SearchCtrl = SearchCtrl;
})(ES || (ES = {}));
var ES;
(function (ES) {
    ES.config = {
        elasticsearch: "http://" + window.location.hostname + ":9200",
        indice: "twitter",
        doctype: "tweet",
        query: "*"
    };
})(ES || (ES = {}));
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    function JVMsController($scope, $window, $location, workspace, jolokia, mbeanName) {
        JVM.configureScope($scope, $location, workspace);
        $scope.data = [];
        $scope.deploying = false;
        $scope.status = '';

        $scope.fetch = function () {
            notification('info', 'Discovering local JVM processes, please wait...');
            jolokia.request({
                type: 'exec', mbean: mbeanName,
                operation: 'listLocalJVMs()',
                arguments: []
            }, {
                success: render,
                error: function (response) {
                    $scope.data = [];
                    $scope.status = 'Could not discover local JVM processes: ' + response.error;
                    Core.$apply($scope);
                }
            });
        };

        $scope.stopAgent = function (pid) {
            notification('info', "Attempting to detach agent from PID " + pid);
            jolokia.request({
                type: 'exec', mbean: mbeanName,
                operation: 'stopAgent(java.lang.String)',
                arguments: [pid]
            }, onSuccess(function () {
                notification('success', "Detached agent from PID " + pid);
                $scope.fetch();
            }));
        };

        $scope.startAgent = function (pid) {
            notification('info', "Attempting to attach agent to PID " + pid);
            jolokia.request({
                type: 'exec', mbean: mbeanName,
                operation: 'startAgent(java.lang.String)',
                arguments: [pid]
            }, onSuccess(function () {
                notification('success', "Attached agent to PID " + pid);
                $scope.fetch();
            }));
        };

        $scope.connectTo = function (url) {
            $window.open("?url=" + encodeURIComponent(url));
        };

        function render(response) {
            $scope.data = response.value;
            if ($scope.data.length === 0) {
                $scope.status = 'Could not discover local JVM processes';
            }
            Core.$apply($scope);
        }

        $scope.fetch();
    }
    JVM.JVMsController = JVMsController;
})(JVM || (JVM = {}));
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    JVM.log = Logger.get("JVM");

    JVM.connectControllerKey = "jvmConnectSettings";
    JVM.connectionSettingsKey = "jvmConnect";

    JVM.logoPath = 'img/icons/jvm/';

    JVM.logoRegistry = {
        'jetty': JVM.logoPath + 'jetty-logo-80x22.png',
        'tomcat': JVM.logoPath + 'tomcat-logo.gif',
        'generic': JVM.logoPath + 'java-logo.svg'
    };

    /**
    * Adds common properties and functions to the scope
    * @method configureScope
    * @for Jvm
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    */
    function configureScope($scope, $location, workspace) {
        $scope.isActive = function (href) {
            var tidy = Core.trimLeading(href, "#");
            var loc = $location.path();
            return loc === tidy;
        };

        $scope.isValid = function (link) {
            return link && link.isValid(workspace);
        };

        $scope.hasLocalMBean = function () {
            return JVM.hasLocalMBean(workspace);
        };

        $scope.breadcrumbs = [
            {
                content: '<i class=" icon-signin"></i> Remote',
                title: "Connect to a remote JVM running Jolokia",
                isValid: function (workspace) {
                    return true;
                },
                href: "#/jvm/connect"
            },
            {
                content: '<i class="icon-list-ul"></i> Local',
                title: "View a diagram of the route",
                isValid: function (workspace) {
                    return hasLocalMBean(workspace);
                },
                href: "#/jvm/local"
            },
            {
                content: '<i class="icon-signin"></i> Discovery',
                title: "Discover",
                isValid: function (workspace) {
                    return hasDiscoveryMBean(workspace);
                },
                href: "#/jvm/discover"
            }
        ];
    }
    JVM.configureScope = configureScope;

    function hasLocalMBean(workspace) {
        return workspace.treeContainsDomainAndProperties('hawtio', { type: 'JVMList' });
    }
    JVM.hasLocalMBean = hasLocalMBean;

    function hasDiscoveryMBean(workspace) {
        return workspace.treeContainsDomainAndProperties('jolokia', { type: 'Discovery' });
    }
    JVM.hasDiscoveryMBean = hasDiscoveryMBean;
})(JVM || (JVM = {}));
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    function ResetController($scope, localStorage) {
        $scope.doClearConnectSettings = function () {
            var doReset = function () {
                delete localStorage[JVM.connectControllerKey];
                delete localStorage[JVM.connectionSettingsKey];
                setTimeout(function () {
                    window.location.reload();
                }, 10);
            };
            doReset();
        };
    }
    JVM.ResetController = ResetController;
})(JVM || (JVM = {}));
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    function DiscoveryController($scope, localStorage, jolokia) {
        $scope.discovering = true;

        $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');
        };

        function doConnect(agent) {
            if (!agent.url) {
                notification('warning', 'No URL available to connect to agent');
                return;
            }
            var options = new Core.ConnectToServerOptions();

            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);
            }
        };

        $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;
        };

        function render(response) {
            if (!response.value) {
                return;
            }
            var responseJson = angular.toJson(response.value.sortBy(function (agent) {
                return agent['agent_id'];
            }), true);
            if ($scope.responseJson !== responseJson) {
                if ($scope.discovering) {
                    $scope.discovering = false;
                }
                $scope.responseJson = responseJson;
                JVM.log.debug("agents: ", $scope.agents);
                $scope.agents = response.value;
                Core.$apply($scope);
            }
        }

        var updateRate = localStorage['updateRate'];
        if (updateRate > 0) {
            Core.register(jolokia, $scope, {
                type: 'exec', mbean: 'jolokia:type=Discovery',
                operation: 'lookupAgentsWithTimeout',
                arguments: [updateRate]
            }, onSuccess(render));
        } else {
            Core.register(jolokia, $scope, {
                type: 'exec', mbean: 'jolokia:type=Discovery',
                operation: 'lookupAgents',
                arguments: []
            }, onSuccess(render));
        }
    }
    JVM.DiscoveryController = DiscoveryController;
})(JVM || (JVM = {}));
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    function NavController($scope, $location, workspace) {
        JVM.configureScope($scope, $location, workspace);
    }
    JVM.NavController = NavController;
})(JVM || (JVM = {}));
/**
* @module JVM
* @main JVM
*/
/*
var JVM;
(function (JVM) {
    JVM.rootPath = 'app/jvm';
    JVM.templatePath = JVM.rootPath + '/html/';
    JVM.pluginName = 'jvm';

    angular.module(JVM.pluginName, ['bootstrap', 'ngResource', 'datatable', 'hawtioCore', 'hawtio-forms', 'ui']).config(function ($routeProvider) {
        $routeProvider.when('/jvm/discover', { templateUrl: JVM.templatePath + 'discover.html' }).when('/jvm/connect', { templateUrl: JVM.templatePath + 'connect.html' }).when('/jvm/local', { templateUrl: JVM.templatePath + 'local.html' });
    }).constant('mbeanName', 'hawtio:type=JVMList').run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry, preferencesRegistry) {
        viewRegistry[JVM.pluginName] = JVM.templatePath + 'layoutConnect.html';
        helpRegistry.addUserDoc('jvm', 'app/jvm/doc/help.md');

        preferencesRegistry.addTab("Connect", 'app/jvm/html/reset.html');

        workspace.topLevelTabs.push({
            id: "connect",
            content: "Connect",
            title: "Connect to other JVMs",
            isValid: function (workspace) {
                return true;
            },
            href: function () {
                return '#/jvm/connect';
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("jvm");
            }
        });
    });

    hawtioPluginLoader.addModule(JVM.pluginName);
})(JVM || (JVM = {}));
*/
/**
* @module JVM
*/
var JVM;
(function (JVM) {
    function ConnectController($scope, $location, localStorage, workspace) {
        JVM.configureScope($scope, $location, workspace);

        $scope.forms = {};

        $scope.chromeApp = Core.isChromeApp();
        $scope.useProxy = $scope.chromeApp ? false : true;

        $scope.settings = {
            last: 1,
            lastConnection: ''
        };

        // load settings like current tab, last used connection
        if (JVM.connectControllerKey in localStorage) {
            try  {
                $scope.settings = angular.fromJson(localStorage[JVM.connectControllerKey]);
            } catch (e) {
                // corrupt config
                delete localStorage[JVM.connectControllerKey];
            }
        }

        // load connection settings
        // TODO add known default configurations here...
        $scope.connectionConfigs = {};

        if (JVM.connectionSettingsKey in localStorage) {
            try  {
                $scope.connectionConfigs = angular.fromJson(localStorage[JVM.connectionSettingsKey]);
            } catch (e) {
                // corrupt config
                delete localStorage[JVM.connectionSettingsKey];
            }
        }

        /*
        log.debug("Controller settings: ", $scope.settings);
        log.debug("Current config: ", $scope.currentConfig);
        log.debug("All connection settings: ", $scope.connectionConfigs);
        */
        $scope.formConfig = {
            properties: {
                connectionName: {
                    type: 'java.lang.String',
                    tooltip: 'Name for this connection',
                    'input-attributes': {
                        'placeholder': 'Unnamed...'
                    }
                },
                scheme: {
                    type: 'java.lang.String',
                    tooltip: 'HTTP or 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': 'chromeApp'
                    }
                }
            },
            type: 'void'
        };

        function newConfig() {
            var answer = {
                scheme: 'http',
                host: 'localhost',
                path: 'jolokia',
                port: '8181',
                userName: '',
                password: ''
            };

            if ($scope.chromeApp) {
                answer['useProxy'] = false;
            } else {
                answer['useProxy'] = true;
            }
            return answer;
        }

        $scope.clearSettings = function () {
            delete localStorage[JVM.connectControllerKey];
            delete localStorage[JVM.connectionSettingsKey];
            window.location.reload();
        };

        $scope.newConnection = function () {
            $scope.settings.lastConnection = '';
        };

        $scope.deleteConnection = function () {
            Core.removeRegex($scope.settings.lastConnection);
            delete $scope.connectionConfigs[$scope.settings.lastConnection];
            var tmp = Object.extended($scope.connectionConfigs);
            if (tmp.size() === 0) {
                $scope.settings.lastConnection = '';
            } else {
                $scope.settings.lastConnection = tmp.keys().first();
            }
            localStorage[JVM.connectionSettingsKey] = angular.toJson($scope.connectionConfigs);
        };

        $scope.$watch('settings', function (newValue, oldValue) {
            if (Core.isBlank($scope.settings['lastConnection'])) {
                $scope.currentConfig = newConfig();
            } else {
                $scope.currentConfig = Object.extended($scope.connectionConfigs[$scope.settings['lastConnection']]).clone();
            }

            if (newValue !== oldValue) {
                localStorage[JVM.connectControllerKey] = angular.toJson(newValue);
            }
        }, true);

        $scope.save = function () {
            $scope.gotoServer($scope.currentConfig, null, true);
        };

        $scope.gotoServer = function (json, form, saveOnly) {
            if (json) {
                var jsonCloned = Object.extended(json).clone(true);

                JVM.log.debug("json: ", jsonCloned);

                // new connection created via the form, let's save it
                var connectionName = jsonCloned['connectionName'];
                if (Core.isBlank(connectionName)) {
                    connectionName = "Unnamed" + $scope.settings.last++;
                    jsonCloned['connectionName'] = connectionName;
                }

                var regexs = Core.getRegexs();

                var hasFunc = function (r) {
                    return r['name'] === $scope.settings.lastConnection;
                };

                if ($scope.settings.lastConnection !== connectionName && !Core.isBlank($scope.settings.lastConnection)) {
                    //we're updating an existing connection...
                    delete $scope.connectionConfigs[$scope.settings.lastConnection];

                    // clean up any similarly named regex
                    if (regexs) {
                        regexs = regexs.exclude(hasFunc);
                    }
                }

                $scope.connectionConfigs[connectionName] = jsonCloned;
                localStorage[JVM.connectionSettingsKey] = angular.toJson($scope.connectionConfigs);
                if (regexs && !regexs.any(hasFunc)) {
                    Core.storeConnectionRegex(regexs, connectionName, jsonCloned);
                }

                // let's default to saved connections now that we've a new connection
                $scope.currentConfig = jsonCloned;
                $scope.settings.lastConnection = connectionName;
            }

            if (saveOnly === true) {
                Core.$apply($scope);
                return;
            }

            var options = new Core.ConnectToServerOptions();
            var host = $scope.currentConfig['host'] || 'localhost';

            JVM.log.info("using scheme: " + $scope.currentConfig['scheme'] + " and host name: " + host + " and user: " + $scope.currentConfig['userName'] + " and password: " + ($scope.currentConfig['password'] ? "********" : $scope.currentConfig['password']));
            options.name = $scope.currentConfig['connectionName'];
            options.scheme = $scope.currentConfig['scheme'];
            options.host = host;
            options.port = $scope.currentConfig['port'];
            options.path = $scope.currentConfig['path'];
            options.userName = $scope.currentConfig['userName'];
            options.password = $scope.currentConfig['password'];
            options.useProxy = $scope.currentConfig['useProxy'];

            Core.$apply($scope);

            Core.connectToServer(localStorage, options);
        };

        function init() {
            JVM.log.debug("Initializing");
            var schemeEnum = ['http', 'https'];
            Core.pathSet($scope.formConfig, ['properties', 'scheme', 'enum'], schemeEnum);
        }

        init();
    }
    JVM.ConnectController = ConnectController;
})(JVM || (JVM = {}));
/**
* @module OpenEJB
*/
var OpenEJB;
(function (OpenEJB) {
    function TreeController($scope, $location, workspace) {
        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        $scope.$watch('workspace.tree', function () {
            if (workspace.moveIfViewInvalid())
                return;

            var children = [];
            var tree = workspace.tree;
            if (tree) {
                var nodes = tree.children;
                angular.forEach(nodes, function (node) {
                    var nodeChildren = node.children;
                    if (node.title.startsWith("openejb") && nodeChildren) {
                        children = children.concat(nodeChildren);
                    }
                });
            }
            var treeElement = $("#openejbTree");
            Jmx.enableTree($scope, $location, workspace, treeElement, children, true);

            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURL($location, $("#openejbTree"), true);
        }
    }
    OpenEJB.TreeController = TreeController;
})(OpenEJB || (OpenEJB = {}));
/**
* @module OpenEJB
* @main OpenEJB
*/
var OpenEJB;
(function (OpenEJB) {
    var pluginName = 'openejb';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).config(function ($routeProvider) {
        // TODO custom tomcat views go here...
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['openojb'] = "app/openejb/html/layoutOpenEJBTree.html";
        helpRegistry.addUserDoc('openejb', 'app/openejb/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("openejb");
        });

        workspace.topLevelTabs.push({
            id: "openejb",
            content: "OpenEJB",
            title: "Manage your OpenEJB resources",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("openejb");
            },
            href: function () {
                return "#/jmx/attributes?tab=openejb";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("openejb");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(OpenEJB || (OpenEJB = {}));
/**
* @module Dashboard
* @main Dashboard
*/
var Dashboard;
(function (Dashboard) {
    Dashboard.templatePath = 'app/dashboard/html/';
    Dashboard.pluginName = 'dashboard';

    angular.module(Dashboard.pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/dashboard/add', { templateUrl: Dashboard.templatePath + 'addToDashboard.html' }).when('/dashboard/edit', { templateUrl: Dashboard.templatePath + 'editDashboards.html' }).when('/dashboard/idx/:dashboardIndex', { templateUrl: Dashboard.templatePath + 'dashboard.html' }).when('/dashboard/id/:dashboardId', { templateUrl: Dashboard.templatePath + 'dashboard.html' }).when('/dashboard/id/:dashboardId/share', { templateUrl: Dashboard.templatePath + 'share.html' }).when('/dashboard/import', { templateUrl: Dashboard.templatePath + 'import.html' });
    }).value('ui.config', {
        // The ui-jq directive namespace
        jq: {
            gridster: {
                widget_margins: [10, 10],
                widget_base_dimensions: [140, 140]
            }
        }
    }).factory('dashboardRepository', function (workspace, jolokia, localStorage) {
        return new Dashboard.DefaultDashboardRepository(workspace, jolokia, localStorage);
    }).directive('hawtioDashboard', function () {
        return new Dashboard.GridsterDirective();
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['dashboard'] = 'app/dashboard/html/layoutDashboard.html';
        helpRegistry.addUserDoc('dashboard', 'app/dashboard/doc/help.md');

        workspace.topLevelTabs.push({
            id: "dashboard",
            content: "Dashboard",
            title: "View and edit your own custom dashboards",
            isValid: function (workspace) {
                return workspace.hasMBeans();
            },
            href: function () {
                return "#/dashboard/idx/0?tab=dashboard";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("dashboard");
            }
        });
    });

    hawtioPluginLoader.addModule(Dashboard.pluginName);
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    var FabricDashboardRepository = (function () {
        function FabricDashboardRepository(workspace, jolokia, localStorage) {
            this.workspace = workspace;
            this.jolokia = jolokia;
            this.localStorage = localStorage;
            this.details = this.getBranchAndProfiles();
        }
        FabricDashboardRepository.prototype.getBranchAndProfiles = function () {
            if (Fabric.fabricCreated(this.workspace)) {
                var container = Fabric.getCurrentContainer(this.jolokia, ['id', 'versionId', 'profiles']);
                var profiles = [];
                if (container.profiles) {
                    profiles = container.profiles.unique();
                    profiles = Fabric.filterProfiles(this.jolokia, container.versionId, profiles);
                }

                return {
                    branch: container.versionId,
                    profiles: profiles
                };
            } else {
                return {
                    branch: "1.0",
                    profiles: []
                };
            }
        };

        FabricDashboardRepository.prototype.putDashboards = function (array, commitMessage, fn) {
            var _this = this;
            var jolokia = this.jolokia;
            var details = this.details;

            var toPut = array.length;

            var maybeCallback = function () {
                toPut = toPut - 1;
                if (toPut === 0) {
                    _this.getDashboards(fn);
                }
            };

            array.forEach(function (dashboard) {
                // console.log("Saving dash: ", dashboard);
                var data = angular.toJson(dashboard, true);
                var profileId = dashboard.profileId;
                if (!profileId) {
                    // TODO maybe not just pick the first one :-)
                    profileId = details.profiles.first();
                }
                var fileName = dashboard.fileName;
                if (!fileName) {
                    fileName = Core.getUUID() + ".dashboard";
                }
                Fabric.saveConfigFile(jolokia, details.branch, profileId, fileName, data.encodeBase64(), function () {
                    maybeCallback();
                    //notification('success', "Saved dashboard " + dashboard.title);
                }, function (response) {
                    Dashboard.log.error("Failed to store dashboard: ", dashboard.title, " due to: ", response.error, " stack trace: ", response.stacktrace);
                    maybeCallback();
                });
            });
        };

        FabricDashboardRepository.prototype.deleteDashboards = function (array, fn) {
            var _this = this;
            var jolokia = this.jolokia;
            var details = this.details;

            var toDelete = array.length;

            var maybeCallback = function () {
                toDelete = toDelete - 1;
                if (toDelete === 0) {
                    _this.getDashboards(fn);
                }
            };

            array.forEach(function (dashboard) {
                var profileId = dashboard.profileId;
                var fileName = dashboard.fileName;
                if (profileId && fileName) {
                    Fabric.deleteConfigFile(jolokia, details.branch, profileId, fileName, function () {
                        maybeCallback();
                    }, function (response) {
                        Dashboard.log.error("Failed to delete dashboard: ", dashboard.title, " due to: ", response.error, " stack trace: ", response.stacktrace);
                        maybeCallback();
                    });
                }
            });
        };

        FabricDashboardRepository.prototype.createDashboard = function (options) {
            var answer = {
                title: "New Dashboard",
                group: "Fabric",
                versionId: this.details.branch,
                profileId: this.details.profiles.first(),
                widgets: []
            };
            answer = angular.extend(answer, options);
            var uuid = Core.getUUID();
            answer['id'] = uuid;
            answer['fileName'] = uuid + ".dashboard";
            return answer;
        };

        FabricDashboardRepository.prototype.cloneDashboard = function (dashboard) {
            var newDashboard = Object.clone(dashboard);
            var uuid = Core.getUUID();
            newDashboard['id'] = uuid;
            newDashboard['fileName'] = uuid + ".dashboard";
            newDashboard['title'] = "Copy of " + dashboard.title;
            return newDashboard;
        };

        FabricDashboardRepository.prototype.getType = function () {
            return 'fabric';
        };

        FabricDashboardRepository.prototype.isValid = function () {
            return Fabric.hasFabric(this.workspace);
        };

        FabricDashboardRepository.prototype.getDashboards = function (fn) {
            var _this = this;
            var jolokia = this.jolokia;
            var details = this.details;
            var dashboards = [];

            jolokia.request({
                type: 'exec',
                mbean: Fabric.managerMBean,
                operation: 'getConfigurationFiles',
                arguments: [details.branch, details.profiles, ".*dashboard"]
            }, {
                method: 'POST',
                success: function (response) {
                    angular.forEach(response.value, function (value, profile) {
                        angular.forEach(value, function (value, fileName) {
                            var dashboard = angular.fromJson(value.decodeBase64());
                            dashboard['versionId'] = details.branch;
                            dashboard['profileId'] = profile;
                            dashboard['fileName'] = fileName;
                            dashboards.push(dashboard);
                        });
                    });

                    if (dashboards.isEmpty()) {
                        dashboards.push(_this.createDashboard({}));
                    }

                    // sort dash boards by title, so they dont appear in random order
                    dashboards = dashboards.sort(function (d1, d2) {
                        var title1 = d1.title;
                        var title2 = d2.title;
                        return title1.localeCompare(title2);
                    });

                    fn(dashboards);
                },
                error: function (response) {
                    Dashboard.log.error("Failed to load dashboard data: error: ", response.error, " stack trace: ", response.stacktrace);
                    fn([]);
                }
            });
        };

        FabricDashboardRepository.prototype.getDashboard = function (id, fn) {
            this.getDashboards(function (dashboards) {
                var dashboard = dashboards.find(function (dashboard) {
                    return dashboard.id === id;
                });
                fn(dashboard);
            });
        };
        return FabricDashboardRepository;
    })();
    Dashboard.FabricDashboardRepository = FabricDashboardRepository;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    function ShareController($scope, $location, $routeParams, workspace, dashboardRepository) {
        var id = $routeParams["dashboardId"];
        dashboardRepository.getDashboard(id, onDashboardLoad);

        var options = {
            mode: {
                name: "javascript"
            }
        };
        $scope.codeMirrorOptions = CodeEditor.createEditorSettings(options);

        function onDashboardLoad(dashboard) {
            $scope.dashboard = Dashboard.cleanDashboardData(dashboard);

            $scope.json = {
                "description": "hawtio dashboards",
                "public": true,
                "files": {
                    "dashboards.json": {
                        "content": JSON.stringify($scope.dashboard, null, "  ")
                    }
                }
            };

            $scope.source = JSON.stringify($scope.dashboard, null, "  ");
            Core.$applyNowOrLater($scope);
        }
    }
    Dashboard.ShareController = ShareController;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    /**
    * Implements the ng.ILocationService interface and is used by the dashboard to supply
    * controllers with a saved URL location
    *
    * @class RectangleLocation
    */
    var RectangleLocation = (function () {
        function RectangleLocation(delegate, path, search, hash) {
            this.delegate = delegate;
            this._path = path;
            this._search = search;
            this._hash = hash;
        }
        RectangleLocation.prototype.absUrl = function () {
            return this.protocol() + this.host() + ":" + this.port() + this.path() + this.search();
        };

        RectangleLocation.prototype.hash = function (newHash) {
            if (typeof newHash === "undefined") { newHash = null; }
            if (newHash) {
                return this.delegate.hash(newHash).search('tab', null);
                //this._hash = newHash;
            }
            return this._hash;
        };

        RectangleLocation.prototype.host = function () {
            return this.delegate.host();
        };

        RectangleLocation.prototype.path = function (newPath) {
            if (typeof newPath === "undefined") { newPath = null; }
            if (newPath) {
                return this.delegate.path(newPath).search('tab', null);
            }
            return this._path;
        };

        RectangleLocation.prototype.port = function () {
            return this.delegate.port();
        };

        RectangleLocation.prototype.protocol = function () {
            return this.delegate.port();
        };

        RectangleLocation.prototype.replace = function () {
            // TODO
            return this;
        };

        RectangleLocation.prototype.search = function (parametersMap) {
            if (typeof parametersMap === "undefined") { parametersMap = null; }
            if (parametersMap) {
                return this.delegate.search(parametersMap);
            }
            return this._search;
        };

        RectangleLocation.prototype.url = function (newValue) {
            if (typeof newValue === "undefined") { newValue = null; }
            if (newValue) {
                return this.delegate.url(newValue).search('tab', null);
            }
            return this.absUrl();
        };
        return RectangleLocation;
    })();
    Dashboard.RectangleLocation = RectangleLocation;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    /**
    * The default dashboard definition if no saved dashboards are available
    *
    * @property defaultDashboards
    * @for Dashboard
    * @type {any}
    */
    var defaultDashboards = [
        {
            "title": "Monitor",
            "group": "Personal",
            "widgets": [
                {
                    "id": "w1",
                    "title": "Operating System",
                    "row": 1,
                    "col": 1,
                    "size_x": 3,
                    "size_y": 4,
                    "path": "jmx/attributes",
                    "include": "app/jmx/html/attributes.html",
                    "search": {
                        "nid": "root-java.lang-OperatingSystem"
                    },
                    "hash": ""
                },
                {
                    "id": "w3",
                    "title": "Java Heap Memory",
                    "row": 1,
                    "col": 6,
                    "size_x": 2,
                    "size_y": 2,
                    "path": "jmx/widget/donut",
                    "include": "app/jmx/html/donutChart.html",
                    "search": {},
                    "hash": "",
                    "routeParams": "{\"type\":\"donut\",\"title\":\"Java Heap Memory\",\"mbean\":\"java.lang:type=Memory\",\"attribute\":\"HeapMemoryUsage\",\"total\":\"Max\",\"terms\":\"Used\",\"remaining\":\"Free\"}"
                },
                {
                    "id": "w4",
                    "title": "Java Non Heap Memory",
                    "row": 1,
                    "col": 8,
                    "size_x": 2,
                    "size_y": 2,
                    "path": "jmx/widget/donut",
                    "include": "app/jmx/html/donutChart.html",
                    "search": {},
                    "hash": "",
                    "routeParams": "{\"type\":\"donut\",\"title\":\"Java Non Heap Memory\",\"mbean\":\"java.lang:type=Memory\",\"attribute\":\"NonHeapMemoryUsage\",\"total\":\"Max\",\"terms\":\"Used\",\"remaining\":\"Free\"}"
                },
                {
                    "id": "w5",
                    "title": "",
                    "row": 3,
                    "col": 4,
                    "size_x": 6,
                    "size_y": 2,
                    "path": "jmx/charts",
                    "include": "app/jmx/html/charts.html",
                    "search": {
                        "size": "%7B%22size_x%22%3A2%2C%22size_y%22%3A2%7D",
                        "title": "Java%20Non%20Heap%20Memory",
                        "routeParams": "%7B%22type%22%3A%22donut%22%2C%22title%22%3A%22Java%20Non%20Heap%20Memory%22%2C%22mbean%22%3A%22java.lang%3Atype",
                        "nid": "root-java.lang-Threading"
                    },
                    "hash": ""
                },
                {
                    "id": "w6",
                    "title": "System CPU Load",
                    "row": 1,
                    "col": 4,
                    "size_x": 2,
                    "size_y": 2,
                    "path": "jmx/widget/area",
                    "include": "app/jmx/html/areaChart.html",
                    "search": {},
                    "hash": "",
                    "routeParams": "{\"type\":\"area\",\"title\":\"System CPU Load\",\"mbean\":\"java.lang:type=OperatingSystem\",\"attribute\":\"SystemCpuLoad\"}"
                }
            ],
            "id": "4e9d116173ca41767e"
        }
    ];

    

    /**
    * Registry of dashboard repositories that delegates to the current effective
    * dashboard repository
    *
    * @class DefaultDashboardRepository
    * @uses DashboardRepository
    */
    var DefaultDashboardRepository = (function () {
        function DefaultDashboardRepository(workspace, jolokia, localStorage) {
            this.workspace = workspace;
            this.jolokia = jolokia;
            this.localStorage = localStorage;
            this.repository = null;
        }
        DefaultDashboardRepository.prototype.putDashboards = function (array, commitMessage, fn) {
            this.getRepository().putDashboards(array, commitMessage, fn);
        };

        DefaultDashboardRepository.prototype.deleteDashboards = function (array, fn) {
            this.getRepository().deleteDashboards(array, fn);
        };

        /**
        * Loads the dashboards then asynchronously calls the function with the data
        * @method getDashboards
        * @param {Function} fn
        */
        DefaultDashboardRepository.prototype.getDashboards = function (fn) {
            this.getRepository().getDashboards(function (values) {
                fn(values);
            });
        };

        /**
        * Loads the given dashboard and invokes the given function with the result
        * @method getDashboard
        * @param {String} id
        * @param {Function} onLoad
        */
        DefaultDashboardRepository.prototype.getDashboard = function (id, onLoad) {
            this.getRepository().getDashboard(id, onLoad);
        };

        DefaultDashboardRepository.prototype.createDashboard = function (options) {
            return this.getRepository().createDashboard(options);
        };

        DefaultDashboardRepository.prototype.cloneDashboard = function (dashboard) {
            return this.getRepository().cloneDashboard(dashboard);
        };

        DefaultDashboardRepository.prototype.getType = function () {
            return this.getRepository().getType();
        };

        DefaultDashboardRepository.prototype.isValid = function () {
            return this.getRepository().isValid();
        };

        /**
        * Looks up the MBean in the JMX tree
        * @method getRepository
        * @return {DashboardRepository}
        */
        DefaultDashboardRepository.prototype.getRepository = function () {
            if (this.repository && this.repository.isValid()) {
                return this.repository;
            }
            if (Fabric.hasFabric(this.workspace)) {
                this.repository = new Dashboard.FabricDashboardRepository(this.workspace, this.jolokia, this.localStorage);
                return this.repository;
            }
            var git = Git.createGitRepository(this.workspace, this.jolokia, this.localStorage);
            if (git) {
                this.repository = new GitDashboardRepository(this.workspace, git);
                return this.repository;
            }
            this.repository = new LocalDashboardRepository(this.workspace);
            return this.repository;
        };
        return DefaultDashboardRepository;
    })();
    Dashboard.DefaultDashboardRepository = DefaultDashboardRepository;

    /**
    * @class LocalDashboardRepository
    * @uses DashboardRepository
    */
    var LocalDashboardRepository = (function () {
        function LocalDashboardRepository(workspace) {
            this.workspace = workspace;
            this.localStorage = null;
            this.localStorage = workspace.localStorage;

            if ('userDashboards' in this.localStorage) {
                // log.info("Found previously saved dashboards");
            } else {
                this.storeDashboards(defaultDashboards);
            }
        }
        LocalDashboardRepository.prototype.loadDashboards = function () {
            var answer = angular.fromJson(localStorage['userDashboards']);
            if (answer.length === 0) {
                answer.push(this.createDashboard({}));
            }
            Dashboard.log.debug("returning dashboards: ", answer);
            return answer;
        };

        LocalDashboardRepository.prototype.storeDashboards = function (dashboards) {
            Dashboard.log.debug("storing dashboards: ", dashboards);
            localStorage['userDashboards'] = angular.toJson(dashboards);
            return this.loadDashboards();
        };

        LocalDashboardRepository.prototype.putDashboards = function (array, commitMessage, fn) {
            var dashboards = this.loadDashboards();

            array.forEach(function (dash) {
                var existing = dashboards.findIndex(function (d) {
                    return d.id === dash.id;
                });
                if (existing >= 0) {
                    dashboards[existing] = dash;
                } else {
                    dashboards.push(dash);
                }
            });
            fn(this.storeDashboards(dashboards));
        };

        LocalDashboardRepository.prototype.deleteDashboards = function (array, fn) {
            var dashboards = this.loadDashboards();
            angular.forEach(array, function (item) {
                dashboards.remove(function (i) {
                    return i.id === item.id;
                });
            });
            fn(this.storeDashboards(dashboards));
        };

        LocalDashboardRepository.prototype.getDashboards = function (fn) {
            fn(this.loadDashboards());
        };

        LocalDashboardRepository.prototype.getDashboard = function (id, fn) {
            var dashboards = this.loadDashboards();
            var dashboard = dashboards.find(function (dashboard) {
                return dashboard.id === id;
            });
            fn(dashboard);
        };

        LocalDashboardRepository.prototype.createDashboard = function (options) {
            var answer = {
                title: "New Dashboard",
                group: "Personal",
                widgets: []
            };
            answer = angular.extend(answer, options);
            answer['id'] = Core.getUUID();
            return answer;
        };

        LocalDashboardRepository.prototype.cloneDashboard = function (dashboard) {
            var newDashboard = Object.clone(dashboard);
            newDashboard['id'] = Core.getUUID();
            newDashboard['title'] = "Copy of " + dashboard.title;
            return newDashboard;
        };

        LocalDashboardRepository.prototype.getType = function () {
            return 'container';
        };

        LocalDashboardRepository.prototype.isValid = function () {
            return !Fabric.hasFabric(this.workspace) && !Git.hasGit(this.workspace);
        };
        return LocalDashboardRepository;
    })();
    Dashboard.LocalDashboardRepository = LocalDashboardRepository;

    /**
    * @class GitDashboardRepository
    * @uses DashboardRepository
    */
    var GitDashboardRepository = (function () {
        function GitDashboardRepository(workspace, git) {
            this.workspace = workspace;
            this.git = git;
            this.branch = null;
        }
        GitDashboardRepository.prototype.putDashboards = function (array, commitMessage, fn) {
            var _this = this;
            var toPut = array.length;
            var maybeCallback = function () {
                toPut = toPut - 1;
                if (toPut === 0) {
                    _this.getDashboards(fn);
                }
            };

            angular.forEach(array, function (dash) {
                var path = _this.getDashboardPath(dash);
                var contents = JSON.stringify(dash, null, "  ");
                _this.git.write(_this.branch, path, commitMessage, contents, function () {
                    maybeCallback();
                });
            });
        };

        GitDashboardRepository.prototype.deleteDashboards = function (array, fn) {
            var _this = this;
            var toDelete = array.length;
            var maybeCallback = function () {
                toDelete = toDelete - 1;
                if (toDelete === 0) {
                    _this.getDashboards(fn);
                }
            };
            angular.forEach(array, function (dash) {
                var path = _this.getDashboardPath(dash);
                var commitMessage = "Removing dashboard " + path;
                _this.git.remove(_this.branch, path, commitMessage, function () {
                    maybeCallback();
                });
            });
        };

        GitDashboardRepository.prototype.createDashboard = function (options) {
            var answer = {
                title: "New Dashboard",
                group: "Personal",
                widgets: []
            };
            answer = angular.extend(answer, options);
            answer['id'] = Core.getUUID();
            return answer;
        };

        GitDashboardRepository.prototype.cloneDashboard = function (dashboard) {
            var newDashboard = Object.clone(dashboard);
            newDashboard['id'] = Core.getUUID();
            newDashboard['title'] = "Copy of " + dashboard.title;
            return newDashboard;
        };

        GitDashboardRepository.prototype.getType = function () {
            return 'git';
        };

        GitDashboardRepository.prototype.isValid = function () {
            return Git.hasGit(this.workspace);
        };

        GitDashboardRepository.prototype.getDashboardPath = function (dash) {
            // TODO assume a user dashboard for now
            // ideally we'd look up the teams path based on the group
            var id = dash.id || Core.getUUID();
            var path = this.getUserDashboardPath(id);
            return path;
        };

        GitDashboardRepository.prototype.getDashboards = function (fn) {
            var _this = this;
            // TODO lets look in each team directory as well and combine the results...
            var path = this.getUserDashboardDirectory();
            var dashboards = [];
            this.git.read(this.branch, path, function (details) {
                var files = details.children;

                var toRead = files.length;

                var maybeCallback = function () {
                    toRead = toRead - 1;
                    if (toRead === 0) {
                        // sort dash boards by title, so they dont appear in random order
                        dashboards = dashboards.sort(function (d1, d2) {
                            var title1 = d1.title;
                            var title2 = d2.title;
                            return title1.localeCompare(title2);
                        });

                        fn(dashboards);
                    }
                };

                if (files.length === 0) {
                    dashboards.push(_this.createDashboard({}));
                    fn(dashboards);
                    return;
                }

                // we now have all the files we need; lets read all their contents
                angular.forEach(files, function (file, idx) {
                    var path = file.path;
                    if (!file.directory && path.endsWith(".json")) {
                        _this.git.read(_this.branch, path, function (details) {
                            // lets parse the contents
                            var content = details.text;
                            if (content) {
                                try  {
                                    var json = JSON.parse(content);
                                    json.uri = path;
                                    dashboards.push(json);
                                } catch (e) {
                                    console.log("Failed to parse: " + content + " due to: " + e);
                                }
                            }
                            Dashboard.log.debug("git - read ", idx, " files, total: ", files.length);
                            maybeCallback();
                        });
                    }
                });
            });
        };

        GitDashboardRepository.prototype.getDashboard = function (id, fn) {
            var path = this.getUserDashboardPath(id);
            this.git.read(this.branch, path, function (details) {
                var dashboard = null;
                var content = details.text;
                if (content) {
                    try  {
                        dashboard = JSON.parse(content);
                    } catch (e) {
                        console.log("Failed to parse: " + content + " due to: " + e);
                    }
                }
                fn(dashboard);
            });
        };

        GitDashboardRepository.prototype.getUserDashboardDirectory = function () {
            // TODO until we fix #96 lets default to a common user name so
            // all the dashboards are shared for all users for now
            //return "/dashboards/user/" + this.git.getUserName();
            return "/dashboards/team/all";
        };

        GitDashboardRepository.prototype.getUserDashboardPath = function (id) {
            return this.getUserDashboardDirectory() + "/" + id + ".json";
        };
        return GitDashboardRepository;
    })();
    Dashboard.GitDashboardRepository = GitDashboardRepository;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    var GridsterDirective = (function () {
        function GridsterDirective() {
            this.restrict = 'A';
            this.replace = true;
            this.controller = function ($scope, $element, $attrs, $location, $routeParams, $injector, $route, $templateCache, workspace, dashboardRepository, $compile) {
                $scope.route = $route;
                $scope.injector = $injector;

                var gridSize = 150;
                var gridMargin = 6;
                var gridHeight;

                $scope.gridX = gridSize;
                $scope.gridY = gridSize;

                $scope.widgetMap = {};

                $scope.$on('$destroy', function () {
                    angular.forEach($scope.widgetMap, function (value, key) {
                        if ('scope' in value) {
                            var scope = value['scope'];
                            scope.$destroy();
                        }
                    });
                });

                updateWidgets();

                $scope.removeWidget = function (widget) {
                    var gridster = getGridster();
                    var widgetElem = null;

                    // lets destroy the widgets's scope
                    var widgetData = $scope.widgetMap[widget.id];
                    if (widgetData) {
                        delete $scope.widgetMap[widget.id];
                        var scope = widgetData.scope;
                        widgetElem = widgetData.widget;
                        if (scope) {
                            scope.$destroy();
                        }
                    }
                    if (!widgetElem) {
                        // lets get the li parent element of the template
                        widgetElem = $("div").find("[data-widgetId='" + widget.id + "']").parent();
                    }
                    if (gridster && widgetElem) {
                        gridster.remove_widget(widgetElem);
                    }

                    // no need to remove it...
                    //widgetElem.remove();
                    // lets trash the JSON metadata
                    if ($scope.dashboard) {
                        var widgets = $scope.dashboard.widgets;
                        if (widgets) {
                            widgets.remove(widget);
                        }
                    }

                    updateDashboardRepository("Removed widget " + widget.title);
                };

                function changeWidgetSize(widget, sizefunc, savefunc) {
                    var gridster = getGridster();
                    var entry = $scope.widgetMap[widget.id];
                    var w = entry.widget;
                    var scope = entry.scope;
                    sizefunc(entry);
                    gridster.resize_widget(w, entry.size_x, entry.size_y);
                    gridster.set_dom_grid_height();

                    setTimeout(function () {
                        var template = $templateCache.get("widgetTemplate");
                        var div = $('<div></div>');
                        div.html(template);
                        w.html($compile(div.contents())(scope));

                        makeResizable();
                        Core.$apply($scope);

                        setTimeout(function () {
                            savefunc(widget);
                        }, 50);
                    }, 30);
                }

                $scope.onWidgetRenamed = function (widget) {
                    updateDashboardRepository("Renamed widget to " + widget.title);
                };

                function updateWidgets() {
                    $scope.id = $routeParams["dashboardId"];
                    $scope.idx = $routeParams["dashboardIndex"];
                    if ($scope.id) {
                        $scope.$emit('loadDashboards');
                        dashboardRepository.getDashboard($scope.id, onDashboardLoad);
                    } else {
                        dashboardRepository.getDashboards(function (dashboards) {
                            $scope.$emit('dashboardsUpdated', dashboards);

                            var idx = $scope.idx ? parseInt($scope.idx) : 0;
                            var id = null;
                            if (dashboards.length > 0) {
                                var dashboard = dashboards.length > idx ? dashboards[idx] : dashboard[0];
                                id = dashboard.id;
                            }
                            if (id) {
                                $location.path("/dashboard/id/" + id);
                            } else {
                                $location.path("/dashboard/edit?tab=dashboard");
                            }
                            Core.$apply($scope);
                        });
                    }
                }

                function onDashboardLoad(dashboard) {
                    $scope.dashboard = dashboard;
                    var widgets = ((dashboard) ? dashboard.widgets : null) || [];

                    var minHeight = 10;
                    var minWidth = 6;

                    angular.forEach(widgets, function (widget) {
                        if (angular.isDefined(widget.row) && minHeight < widget.row) {
                            minHeight = widget.row + 1;
                        }
                        if (angular.isDefined(widget.size_x && angular.isDefined(widget.col))) {
                            var rightEdge = widget.col + widget.size_x;
                            if (rightEdge > minWidth) {
                                minWidth = rightEdge + 1;
                            }
                        }
                    });

                    var gridster = $element.gridster({
                        widget_margins: [gridMargin, gridMargin],
                        widget_base_dimensions: [$scope.gridX, $scope.gridY],
                        extra_rows: minHeight,
                        extra_cols: minWidth,
                        max_size_x: minWidth,
                        max_size_y: minHeight,
                        draggable: {
                            stop: function (event, ui) {
                                if (serializeDashboard()) {
                                    updateDashboardRepository("Changing dashboard layout");
                                }
                            }
                        }
                    }).data('gridster');

                    var template = $templateCache.get("widgetTemplate");
                    angular.forEach(widgets, function (widget) {
                        var childScope = $scope.$new(false);
                        childScope.widget = widget;
                        var path = widget.path;
                        var search = null;
                        if (widget.search) {
                            search = Dashboard.decodeURIComponentProperties(widget.search);
                        }
                        var hash = widget.hash;
                        var location = new Dashboard.RectangleLocation($location, path, search, hash);
                        var routeParams = null;
                        if (widget.routeParams) {
                            routeParams = angular.fromJson(widget.routeParams);
                        }

                        var childWorkspace = workspace.createChildWorkspace(location);

                        //var childWorkspace = workspace;
                        childWorkspace.$location = location;

                        // now we need to update the selection from the location search()
                        if (search) {
                            var key = location.search()['nid'];
                            if (key && workspace.tree) {
                                // lets find the node for this key...
                                childWorkspace.selection = workspace.keyToNodeMap[key];
                                if (!childWorkspace.selection) {
                                    var decodedKey = decodeURIComponent(key);
                                    childWorkspace.selection = workspace.keyToNodeMap[decodedKey];
                                }
                            }
                        }

                        var $$scopeInjections = {
                            workspace: childWorkspace,
                            location: location,
                            $location: location,
                            $routeParams: routeParams
                        };
                        childScope.$$scopeInjections = $$scopeInjections;
                        childScope.inDashboard = true;

                        if (!widget.size_x || widget.size_x < 1) {
                            widget.size_x = 1;
                        }
                        if (!widget.size_y || widget.size_y < 1) {
                            widget.size_y = 1;
                        }
                        var div = $('<div></div>');
                        div.html(template);

                        var outerDiv = $('<li class="grid-block" style="display: list-item; position: absolute"></li>');
                        outerDiv.html($compile(div.contents())(childScope));
                        var w = gridster.add_widget(outerDiv, widget.size_x, widget.size_y, widget.col, widget.row);

                        $scope.widgetMap[widget.id] = {
                            widget: w,
                            scope: childScope
                        };
                    });

                    makeResizable();
                    getGridster().enable();

                    Core.$apply($scope);
                }

                function serializeDashboard() {
                    var gridster = getGridster();
                    if (gridster) {
                        var data = gridster.serialize();

                        //console.log("got data: " + JSON.stringify(data));
                        var widgets = $scope.dashboard.widgets || [];

                        // console.log("Widgets: ", widgets);
                        // lets assume the data is in the order of the widgets...
                        angular.forEach(widgets, function (widget, idx) {
                            var value = data[idx];
                            if (value && widget) {
                                // lets copy the values across
                                angular.forEach(value, function (attr, key) {
                                    return widget[key] = attr;
                                });
                            }
                        });
                        return true;
                    }
                    return false;
                }

                function makeResizable() {
                    var blocks = $('.grid-block');
                    blocks.resizable('destroy');

                    blocks.resizable({
                        grid: [gridSize + (gridMargin * 2), gridSize + (gridMargin * 2)],
                        animate: false,
                        minWidth: gridSize,
                        minHeight: gridSize,
                        autoHide: false,
                        start: function (event, ui) {
                            gridHeight = getGridster().$el.height();
                        },
                        resize: function (event, ui) {
                            //set new grid height along the dragging period
                            var g = getGridster();
                            var delta = gridSize + gridMargin * 2;
                            if (event.offsetY > g.$el.height()) {
                                var extra = Math.floor((event.offsetY - gridHeight) / delta + 1);
                                var newHeight = gridHeight + extra * delta;
                                g.$el.css('height', newHeight);
                            }
                        },
                        stop: function (event, ui) {
                            var resized = $(this);
                            setTimeout(function () {
                                resizeBlock(resized);
                            }, 300);
                        }
                    });

                    $('.ui-resizable-handle').hover(function () {
                        getGridster().disable();
                    }, function () {
                        getGridster().enable();
                    });
                }

                function resizeBlock(elmObj) {
                    //var elmObj = $(elmObj);
                    var area = elmObj.find('.widget-area');
                    var w = elmObj.width() - gridSize;
                    var h = elmObj.height() - gridSize;

                    for (var grid_w = 1; w > 0; w -= (gridSize + (gridMargin * 2))) {
                        grid_w++;
                    }

                    for (var grid_h = 1; h > 0; h -= (gridSize + (gridMargin * 2))) {
                        grid_h++;
                    }

                    var widget = {
                        id: area.attr('data-widgetId')
                    };

                    changeWidgetSize(widget, function (widget) {
                        widget.size_x = grid_w;
                        widget.size_y = grid_h;
                    }, function (widget) {
                        if (serializeDashboard()) {
                            updateDashboardRepository("Changed size of widget: " + widget.id);
                        }
                    });
                }

                function updateDashboardRepository(message) {
                    if ($scope.dashboard) {
                        var commitMessage = message;
                        if ($scope.dashboard && $scope.dashboard.title) {
                            commitMessage += " on dashboard " + $scope.dashboard.title;
                        }
                        dashboardRepository.putDashboards([$scope.dashboard], commitMessage, Dashboard.onOperationComplete);
                    }
                }

                function getGridster() {
                    return $element.gridster().data('gridster');
                }
            };
        }
        return GridsterDirective;
    })();
    Dashboard.GridsterDirective = GridsterDirective;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    function NavBarController($scope, $routeParams, $rootScope, workspace, dashboardRepository) {
        $scope.hash = workspace.hash();
        $scope._dashboards = [];

        $scope.activeDashboard = $routeParams['dashboardId'];

        $rootScope.$on('loadDashboards', loadDashboards);

        $rootScope.$on('dashboardsUpdated', dashboardLoaded);

        $scope.dashboards = function () {
            return $scope._dashboards;
        };

        $scope.isActive = function (dash) {
            return workspace.isLinkActive("#/dashboard/id/" + dash.id);
        };

        $scope.isEditing = function () {
            return workspace.isLinkActive("#/dashboard/edit");
        };

        $scope.onTabRenamed = function (dash) {
            dashboardRepository.putDashboards([dash], "Renamed dashboard", function (dashboards) {
                dashboardLoaded(null, dashboards);
            });
        };

        function dashboardLoaded(event, dashboards) {
            Dashboard.log.debug("navbar dashboardLoaded: ", dashboards);
            $scope._dashboards = dashboards;
            if (event === null) {
                $rootScope.$broadcast('dashboardsUpdated', dashboards);
                Core.$apply($scope);
            }
        }

        function loadDashboards(event) {
            dashboardRepository.getDashboards(function (dashboards) {
                // prevent the broadcast from happening...
                dashboardLoaded(event, dashboards);
                Core.$apply($scope);
            });
        }
    }
    Dashboard.NavBarController = NavBarController;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    Dashboard.log = Logger.get('Dashboard');

    /**
    * Returns the cleaned up version of the dashboard data without any UI selection state
    * @method cleanDashboardData
    * @static
    * @for Dashboard
    * @param {any} item
    * @return {any}
    */
    function cleanDashboardData(item) {
        var cleanItem = {};
        angular.forEach(item, function (value, key) {
            if (!angular.isString(key) || (!key.startsWith("$") && !key.startsWith("_"))) {
                cleanItem[key] = value;
            }
        });
        return cleanItem;
    }
    Dashboard.cleanDashboardData = cleanDashboardData;

    /**
    * Runs decodeURIComponent() on each value in the object
    * @method decodeURIComponentProperties
    * @static
    * @for Dashboard
    * @param {any} hash
    * @return {any}
    */
    function decodeURIComponentProperties(hash) {
        if (!hash) {
            return hash;
        }
        var decodeHash = {};
        angular.forEach(hash, function (value, key) {
            decodeHash[key] = value ? decodeURIComponent(value) : value;
        });
        return decodeHash;
    }
    Dashboard.decodeURIComponentProperties = decodeURIComponentProperties;

    function onOperationComplete(result) {
        console.log("Completed adding the dashboard with response " + JSON.stringify(result));
    }
    Dashboard.onOperationComplete = onOperationComplete;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    function EditDashboardsController($scope, $routeParams, $route, $location, $rootScope, dashboardRepository, jolokia, workspace) {
        $scope.hash = workspace.hash();
        $scope.selectedItems = [];
        $scope.repository = dashboardRepository;
        $scope.duplicateDashboards = new UI.Dialog();
        $scope.selectedProfilesDialog = [];
        $scope._dashboards = [];

        $rootScope.$on('dashboardsUpdated', dashboardLoaded);

        $scope.hasUrl = function () {
            return ($scope.url) ? true : false;
        };

        $scope.hasSelection = function () {
            return $scope.selectedItems.length !== 0;
        };

        $scope.gridOptions = {
            selectedItems: $scope.selectedItems,
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                filterText: ''
            },
            data: '_dashboards',
            selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            columnDefs: [
                {
                    field: 'title',
                    displayName: 'Dashboard',
                    cellTemplate: '<div class="ngCellText"><a ng-href="#/dashboard/id/{{row.getProperty(' + "'id'" + ')}}{{hash}}"><editable-property class="inline-block" on-save="onDashRenamed(row.entity)" property="title" ng-model="row.entity"></editable-property></a></div>'
                },
                {
                    field: 'group',
                    displayName: 'Group'
                }
            ]
        };

        $scope.onDashRenamed = function (dash) {
            dashboardRepository.putDashboards([dash], "Renamed dashboard", function (dashboards) {
                dashboardLoaded(null, dashboards);
            });
        };

        // helpers so we can enable/disable parts of the UI depending on how
        // dashboard data is stored
        $scope.usingGit = function () {
            return dashboardRepository.getType() === 'git';
        };

        $scope.usingFabric = function () {
            return dashboardRepository.getType() === 'fabric';
        };

        $scope.usingLocal = function () {
            return dashboardRepository.getType() === 'container';
        };

        if ($scope.usingFabric()) {
            $scope.container = Fabric.getCurrentContainer(jolokia, ['versionId', 'profileIds']);

            $scope.gridOptions.columnDefs.add([
                {
                    field: 'versionId',
                    displayName: 'Version'
                }, {
                    field: 'profileId',
                    displayName: 'Profile'
                }, {
                    field: 'fileName',
                    displayName: 'File Name'
                }]);
        }

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateData, 100);
        });

        $scope.goBack = function () {
            var href = Core.trimLeading($scope.url, "#");
            if (href) {
                $location.url(href);
            }
        };

        $scope.duplicateToProfiles = function () {
            if ($scope.hasSelection()) {
                $scope.duplicateDashboards.open();
            }
        };

        $scope.doDuplicateToProfiles = function () {
            $scope.duplicateDashboards.close();

            var newDashboards = [];

            $scope.selectedItems.forEach(function (dashboard) {
                $scope.selectedProfilesDialog.forEach(function (profile) {
                    var newDash = dashboardRepository.cloneDashboard(dashboard);
                    newDash['profileId'] = profile.id;
                    newDash['title'] = dashboard.title;
                    newDashboards.push(newDash);
                });
            });

            var commitMessage = "Duplicating " + $scope.selectedItems.length + " dashboards to " + $scope.selectedProfilesDialog.length + " profiles";

            dashboardRepository.putDashboards(newDashboards, commitMessage, function (dashboards) {
                dashboardLoaded(null, dashboards);
            });
        };

        $scope.addViewToDashboard = function () {
            var nextHref = null;
            angular.forEach($scope.selectedItems, function (selectedItem) {
                // TODO this could be a helper function
                var text = $scope.url;
                var query = null;
                if (text) {
                    var idx = text.indexOf('?');
                    if (idx && idx > 0) {
                        query = text.substring(idx + 1);
                        text = text.substring(0, idx);
                    }
                    text = Core.trimLeading(text, "#");
                }
                var search = {};
                if (query) {
                    var expressions = query.split("&");
                    angular.forEach(expressions, function (expression) {
                        if (expression) {
                            var names = expression.split("=");
                            var key = names[0];
                            var value = names.length > 1 ? names[1] : null;
                            if (value) {
                                value = encodeURIComponent(value);
                            }
                            var old = search[key];
                            if (old) {
                                if (!angular.isArray(old)) {
                                    old = [old];
                                    search[key] = old;
                                }
                                old.push(value);
                            } else {
                                search[key] = value;
                            }
                        }
                    });
                }

                //console.log("path is: " + text + " the search is " + JSON.stringify(search));
                if ($route && $route.routes) {
                    var value = $route.routes[text];
                    if (value) {
                        var templateUrl = value["templateUrl"];
                        if (templateUrl) {
                            if (!selectedItem.widgets) {
                                selectedItem.widgets = [];
                            }
                            var nextNumber = selectedItem.widgets.length + 1;
                            var widget = {
                                id: "w" + nextNumber, title: "",
                                row: 1,
                                col: 1,
                                size_x: 1,
                                size_y: 1,
                                path: Core.trimLeading(text, "/"),
                                include: templateUrl,
                                search: search,
                                hash: ""
                            };

                            if ($scope.widgetTitle) {
                                widget.title = $scope.widgetTitle;
                            }

                            // figure out the width of the dash
                            var gridWidth = 0;

                            selectedItem.widgets.forEach(function (w) {
                                var rightSide = w.col + w.size_x;
                                if (rightSide > gridWidth) {
                                    gridWidth = rightSide;
                                }
                            });

                            if ($scope.preferredSize) {
                                widget.size_x = parseInt($scope.preferredSize['size_x']);
                                widget.size_y = parseInt($scope.preferredSize['size_y']);
                            }

                            var found = false;

                            var left = function (w) {
                                return w.col;
                            };

                            var right = function (w) {
                                return w.col + w.size_x - 1;
                            };

                            var top = function (w) {
                                return w.row;
                            };

                            var bottom = function (w) {
                                return w.row + w.size_y - 1;
                            };

                            var collision = function (w1, w2) {
                                return !(left(w2) > right(w1) || right(w2) < left(w1) || top(w2) > bottom(w1) || bottom(w2) < top(w1));
                            };

                            if (selectedItem.widgets.isEmpty()) {
                                found = true;
                            }

                            while (!found) {
                                widget.col = 1;
                                for (; (widget.col + widget.size_x) <= gridWidth; widget.col++) {
                                    if (!selectedItem.widgets.any(function (w) {
                                        var c = collision(w, widget);
                                        return c;
                                    })) {
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found) {
                                    widget.row = widget.row + 1;
                                }

                                // just in case, keep the script from running away...
                                if (widget.row > 50) {
                                    found = true;
                                }
                            }

                            if ($scope.routeParams) {
                                widget['routeParams'] = $scope.routeParams;
                            }
                            selectedItem.widgets.push(widget);

                            if (!nextHref && selectedItem.id) {
                                nextHref = "/dashboard/id/" + selectedItem.id;
                            }
                        }
                    } else {
                        // TODO we need to be able to match URI templates...
                    }
                }
            });

            // now lets update the actual dashboard config
            var commitMessage = "Add widget";
            dashboardRepository.putDashboards($scope.selectedItems, commitMessage, function (dashboards) {
                if (nextHref) {
                    // remove any dodgy query
                    delete $location.search()["href"];
                    $location.path(nextHref);
                    Core.$apply($scope);
                }
            });
        };

        $scope.create = function () {
            var counter = dashboards().length + 1;
            var title = "Untitled" + counter;
            var newDash = dashboardRepository.createDashboard({ title: title });

            dashboardRepository.putDashboards([newDash], "Created new dashboard: " + title, function (dashboards) {
                // let's just be safe and ensure there's no selections
                $scope.selectedItems.splice(0);
                dashboardLoaded(null, dashboards);
            });
        };

        $scope.duplicate = function () {
            var newDashboards = [];
            var commitMessage = "Duplicated dashboard(s) ";
            angular.forEach($scope.selectedItems, function (item, idx) {
                // lets unselect this item
                var commitMessage = "Duplicated dashboard " + item.title;
                var newDash = dashboardRepository.cloneDashboard(item);
                newDashboards.push(newDash);
            });

            // let's just be safe and ensure there's no selections
            $scope.selectedItems.splice(0);

            commitMessage = commitMessage + newDashboards.map(function (d) {
                return d.title;
            }).join(',');
            dashboardRepository.putDashboards(newDashboards, commitMessage, function (dashboards) {
                dashboardLoaded(null, dashboards);
            });
        };

        $scope.delete = function () {
            if ($scope.hasSelection()) {
                dashboardRepository.deleteDashboards($scope.selectedItems, function (dashboards) {
                    // let's just be safe and ensure there's no selections
                    $scope.selectedItems.splice(0);
                    dashboardLoaded(null, dashboards);
                });
            }
        };

        $scope.gist = function () {
            if ($scope.selectedItems.length > 0) {
                var id = $scope.selectedItems[0].id;
                $location.path("/dashboard/id/" + id + "/share");
            }
        };

        function updateData() {
            var url = $routeParams["href"];
            if (url) {
                $scope.url = decodeURIComponent(url);
            }

            var routeParams = $routeParams["routeParams"];
            if (routeParams) {
                $scope.routeParams = decodeURIComponent(routeParams);
            }
            var size = $routeParams["size"];
            if (size) {
                size = decodeURIComponent(size);
                $scope.preferredSize = angular.fromJson(size);
            }
            var title = $routeParams["title"];
            if (title) {
                title = decodeURIComponent(title);
                $scope.widgetTitle = title;
            }

            dashboardRepository.getDashboards(function (dashboards) {
                dashboardLoaded(null, dashboards);
            });
        }

        function dashboardLoaded(event, dashboards) {
            $scope._dashboards = dashboards;
            if (event === null) {
                $scope.$emit('dashboardsUpdated', dashboards);
            }
            Core.$apply($scope);
        }

        function dashboards() {
            return $scope._dashboards;
        }

        updateData();
        /*
        // TODO for case where we navigate to the add view
        // for some reason the route update event isn't enough...
        // and we need to do this async to avoid the size calculation being wrong
        // bit of a hack - would love to remove! :)
        setTimeout(updateData, 100);
        */
    }
    Dashboard.EditDashboardsController = EditDashboardsController;
})(Dashboard || (Dashboard = {}));
/**
* @module Dashboard
*/
var Dashboard;
(function (Dashboard) {
    function ImportController($scope, $location, $routeParams, workspace, dashboardRepository) {
        $scope.placeholder = "Paste the JSON here for the dashboard configuration to import...";
        $scope.source = $scope.placeholder;

        var options = {
            mode: {
                name: "javascript"
            }
        };
        $scope.codeMirrorOptions = CodeEditor.createEditorSettings(options);

        $scope.isValid = function () {
            return $scope.source && $scope.source !== $scope.placeholder;
        };

        $scope.importJSON = function () {
            var json = [];

            try  {
                json = JSON.parse($scope.source);
            } catch (e) {
                notification("error", "Could not parse the JSON\n" + e);
                json = [];
            }
            var array = [];
            if (angular.isArray(json)) {
                array = json;
            } else if (angular.isObject(json)) {
                array.push(json);
            }

            if (array.length) {
                // lets ensure we have some valid ids and stuff...
                angular.forEach(array, function (dash, index) {
                    angular.copy(dash, dashboardRepository.createDashboard(dash));
                });
                dashboardRepository.putDashboards(array, "Imported dashboard JSON", Dashboard.onOperationComplete);
                $location.path("/dashboard/edit");
            }
        };
    }
    Dashboard.ImportController = ImportController;
})(Dashboard || (Dashboard = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    var PreferencesRegistry = (function () {
        function PreferencesRegistry() {
            this.tabs = {};
        }
        PreferencesRegistry.prototype.addTab = function (name, template, isValid) {
            if (typeof isValid === "undefined") { isValid = undefined; }
            if (!isValid) {
                isValid = function () {
                    return true;
                };
            }
            this.tabs[name] = {
                template: template,
                isValid: isValid
            };
        };

        PreferencesRegistry.prototype.getTab = function (name) {
            return this.tabs[name];
        };

        PreferencesRegistry.prototype.getTabs = function () {
            var answer = {};
            angular.forEach(this.tabs, function (value, key) {
                if (value.isValid()) {
                    answer[key] = value;
                }
            });
            return answer;
        };
        return PreferencesRegistry;
    })();
    Core.PreferencesRegistry = PreferencesRegistry;
    ;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    /**
    * Parsers the given value as JSON if it is define
    */
    function parsePreferencesJson(value, key) {
        var answer = null;
        if (angular.isDefined(value)) {
            answer = Core.parseJsonText(value, "localStorage for " + key);
        }
        return answer;
    }
    Core.parsePreferencesJson = parsePreferencesJson;

    /**
    * Function to return the configured plugin for the given perspective. The returned
    * list is sorted in the configured order.
    * Notice the list contains plugins which may have been configured as disabled.
    */
    function configuredPluginsForPerspectiveId(perspectiveId, workspace, jolokia, localStorage) {
        // grab the top level tabs which is the plugins we can select as our default plugin
        var topLevelTabs = Perspective.topLevelTabsForPerspectiveId(workspace, perspectiveId);
        if (topLevelTabs && topLevelTabs.length > 0) {
            Core.log.debug("Found " + topLevelTabs.length + " plugins");

            // exclude invalid tabs at first
            topLevelTabs = topLevelTabs.filter(function (tab) {
                var href = tab.href();
                return href && isValidFunction(workspace, tab.isValid);
            });
            Core.log.debug("After filtering there are " + topLevelTabs.length + " plugins");

            var id = "plugins-" + perspectiveId;
            var initPlugins = parsePreferencesJson(localStorage[id], id);
            if (initPlugins) {
                // remove plugins which we cannot find active currently
                initPlugins = initPlugins.filter(function (p) {
                    return topLevelTabs.some(function (tab) {
                        return tab.id === p.id;
                    });
                });

                // add new active plugins which we didn't know about before
                topLevelTabs.forEach(function (tab) {
                    var knownPlugin = initPlugins.some(function (p) {
                        return p.id === tab.id;
                    });
                    if (!knownPlugin) {
                        Core.log.info("Discovered new plugin in JVM since loading configuration: " + tab.id);
                        initPlugins.push({ id: tab.id, index: -1, displayName: tab.content, enabled: true, isDefault: false });
                    }
                });
            } else {
                // okay no configured saved yet, so use what is active
                initPlugins = topLevelTabs;
            }
        }

        // okay push plugins to scope so we can see them in the UI
        var answer = safeTabsToPlugins(initPlugins);
        return answer;
    }
    Core.configuredPluginsForPerspectiveId = configuredPluginsForPerspectiveId;

    /**
    * Function which safely can turn tabs/plugins to plugins
    */
    function safeTabsToPlugins(tabs) {
        var answer = [];
        if (tabs) {
            tabs.forEach(function (tab, idx) {
                var name;
                if (angular.isUndefined(tab.displayName)) {
                    name = tab.content;
                } else {
                    name = tab.displayName;
                }
                var enabled;
                if (angular.isUndefined(tab.enabled)) {
                    enabled = true;
                } else {
                    enabled = tab.enabled;
                }
                var isDefault;
                if (angular.isUndefined(tab.isDefault)) {
                    isDefault = false;
                } else {
                    isDefault = tab.isDefault;
                }
                answer.push({ id: tab.id, index: idx, displayName: name, enabled: enabled, isDefault: isDefault });
            });
        }
        return answer;
    }
    Core.safeTabsToPlugins = safeTabsToPlugins;

    function filterTopLevelTabs(perspective, workspace, configuredPlugins) {
        var topLevelTabs = Perspective.topLevelTabsForPerspectiveId(workspace, perspective);
        if (perspective === "website")
            return topLevelTabs;

        // only include the tabs accordingly to configured
        var result = [];
        configuredPlugins.forEach(function (p) {
            if (p.enabled) {
                var pid = p.id;
                var tab = null;
                if (pid) {
                    tab = topLevelTabs.find(function (t) {
                        return t.id === pid;
                    });
                }
                if (tab) {
                    result.push(tab);
                }
            }
        });
        return result;
    }
    Core.filterTopLevelTabs = filterTopLevelTabs;

    function initPreferenceScope($scope, localStorage, defaults) {
        angular.forEach(defaults, function (_default, key) {
            $scope[key] = _default['value'];
            var converter = _default['converter'];
            var formatter = _default['formatter'];
            if (!formatter) {
                formatter = function (value) {
                    return value;
                };
            }
            if (!converter) {
                converter = function (value) {
                    return value;
                };
            }
            if (key in localStorage) {
                var value = converter(localStorage[key]);
                Core.log.debug("from local storage, setting ", key, " to ", value);
                $scope[key] = value;
            } else {
                var value = _default['value'];
                Core.log.debug("from default, setting ", key, " to ", value);
                localStorage[key] = value;
            }

            var watchFunc = _default['override'];
            if (!watchFunc) {
                watchFunc = function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if (angular.isFunction(_default['pre'])) {
                            _default.pre(newValue);
                        }

                        var value = formatter(newValue);
                        Core.log.debug("to local storage, setting ", key, " to ", value);
                        localStorage[key] = value;

                        if (angular.isFunction(_default['post'])) {
                            _default.post(newValue);
                        }
                    }
                };
            }

            if (_default['compareAsObject']) {
                $scope.$watch(key, watchFunc, true);
            } else {
                $scope.$watch(key, watchFunc);
            }
        });
    }
    Core.initPreferenceScope = initPreferenceScope;

    /**
    * Returns true if there is no validFn defined or if its defined
    * then the function returns true.
    *
    * @method isValidFunction
    * @for Perspective
    * @param {Core.Workspace} workspace
    * @param {Function} validFn
    * @return {Boolean}
    */
    function isValidFunction(workspace, validFn) {
        return !validFn || validFn(workspace);
    }
    Core.isValidFunction = isValidFunction;

    /**
    * Gets the default configured plugin for the given perspective, or <tt>null</tt> if no default has been configured.
    */
    function getDefaultPlugin(perspectiveId, workspace, jolokia, localStorage) {
        var plugins = Core.configuredPluginsForPerspectiveId(perspectiveId, workspace, jolokia, localStorage);

        // find the default plugins
        var defaultPlugin = null;
        plugins.forEach(function (p) {
            if (p.isDefault) {
                defaultPlugin = p;
            }
        });
        return defaultPlugin;
    }
    Core.getDefaultPlugin = getDefaultPlugin;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    var HelpRegistry = (function () {
        function HelpRegistry($rootScope) {
            this.$rootScope = $rootScope;
            this.discoverableDocTypes = {
                user: 'help.md'
            };
            this.topicNameMappings = {
                activemq: 'ActiveMQ',
                camel: 'Camel',
                jboss: 'JBoss',
                jclouds: 'jclouds',
                jmx: 'JMX',
                jvm: 'Connect',
                log: 'Logs',
                openejb: 'OpenEJB'
            };
            this.subTopicNameMappings = {
                user: 'For Users',
                developer: 'For Developers',
                faq: 'FAQ'
            };
            // map plugin names to their path in the app
            this.pluginNameMappings = {
                hawtioCore: 'core',
                'hawtio-branding': 'branding',
                forceGraph: 'forcegraph',
                'hawtio-ui': 'ui',
                'hawtio-forms': 'forms',
                elasticjs: 'elasticsearch'
            };
            // let's not auto-discover help files in these plugins
            this.ignoredPlugins = [
                'core',
                'branding',
                'datatable',
                'forcegraph',
                'forms',
                'perspective',
                'tree',
                'ui'
            ];
            this.topics = {};
        }
        HelpRegistry.prototype.addUserDoc = function (topic, path, isValid) {
            if (typeof isValid === "undefined") { isValid = null; }
            this.addSubTopic(topic, 'user', path, isValid);
        };

        HelpRegistry.prototype.addDevDoc = function (topic, path, isValid) {
            if (typeof isValid === "undefined") { isValid = null; }
            this.addSubTopic(topic, 'developer', path, isValid);
        };

        HelpRegistry.prototype.addSubTopic = function (topic, subtopic, path, isValid) {
            if (typeof isValid === "undefined") { isValid = null; }
            this.getOrCreateTopic(topic, isValid)[subtopic] = path;
        };

        HelpRegistry.prototype.getOrCreateTopic = function (topic, isValid) {
            if (typeof isValid === "undefined") { isValid = null; }
            if (!angular.isDefined(this.topics[topic])) {
                if (isValid === null) {
                    isValid = function () {
                        return true;
                    };
                }

                this.topics[topic] = {
                    isValid: isValid
                };
                this.$rootScope.$broadcast('hawtioNewHelpTopic');
            }
            return this.topics[topic];
        };

        HelpRegistry.prototype.mapTopicName = function (name) {
            if (angular.isDefined(this.topicNameMappings[name])) {
                return this.topicNameMappings[name];
            }
            return name.capitalize();
        };

        HelpRegistry.prototype.mapSubTopicName = function (name) {
            if (angular.isDefined(this.subTopicNameMappings[name])) {
                return this.subTopicNameMappings[name];
            }
            return name.capitalize();
        };

        HelpRegistry.prototype.getTopics = function () {
            var answer = {};

            angular.forEach(this.topics, function (value, key) {
                if (value.isValid()) {
                    Core.log.debug(key, " is available");

                    // strip out any functions...
                    answer[key] = angular.fromJson(angular.toJson(value));
                } else {
                    Core.log.debug(key, " is not available");
                }
            });

            return answer;
        };

        HelpRegistry.prototype.disableAutodiscover = function (name) {
            this.ignoredPlugins.push(name);
        };

        HelpRegistry.prototype.discoverHelpFiles = function (plugins) {
            var self = this;

            console.log("Ignored plugins: ", self.ignoredPlugins);

            plugins.forEach(function (plugin) {
                var pluginName = self.pluginNameMappings[plugin];
                if (!angular.isDefined(pluginName)) {
                    pluginName = plugin;
                }

                if (!self.ignoredPlugins.any(function (p) {
                    return p === pluginName;
                })) {
                    angular.forEach(self.discoverableDocTypes, function (value, key) {
                        // avoid trying to discover these if plugins register them
                        if (!angular.isDefined(self[pluginName]) || !angular.isDefined(self[pluginName][key])) {
                            var target = 'app/' + pluginName + '/doc/' + value;
                            console.log("checking: ", target);

                            $.ajax(target, {
                                type: 'HEAD',
                                statusCode: {
                                    200: function () {
                                        self.getOrCreateTopic(plugin)[key] = target;
                                    }
                                }
                            });
                        }
                    });
                }
            });
        };
        return HelpRegistry;
    })();
    Core.HelpRegistry = HelpRegistry;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function PreferencesController($scope, $location, workspace, preferencesRegistry, $element) {
        Core.bindModelToSearchParam($scope, $location, "pref", "pref", "Core");
        $scope.panels = {};

        $scope.$watch(function () {
            return $element.is(':visible');
        }, function (newValue, oldValue) {
            if (newValue) {
                setTimeout(function () {
                    $scope.panels = preferencesRegistry.getTabs();
                    Core.log.debug("Panels: ", $scope.panels);
                    Core.$apply($scope);
                }, 50);
            }
        });
    }
    Core.PreferencesController = PreferencesController;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function CorePreferences($scope, localStorage) {
        Core.initPreferenceScope($scope, localStorage, {
            'updateRate': {
                'value': 5000,
                'post': function (newValue) {
                    $scope.$emit('UpdateRate', newValue);
                }
            },
            'showWelcomePage': {
                'value': true,
                'converter': Core.parseBooleanValue
            },
            'regexs': {
                'value': "",
                'converter': function (value) {
                    if (angular.isArray(value)) {
                        return value;
                    } else if (Core.isBlank(value)) {
                        return [];
                    }
                    return angular.fromJson(value);
                },
                'formatter': function (value) {
                    return angular.toJson(value);
                },
                'compareAsObject': true
            }
        });

        $scope.newHost = {};
        $scope.forms = {};

        $scope.addRegexDialog = new UI.Dialog();

        $scope.onOk = function (json, form) {
            $scope.addRegexDialog.close();
            json['color'] = UI.colors.sample();
            if (!angular.isArray($scope.regexs)) {
                $scope.regexs = [json];
            } else {
                $scope.regexs.push(json);
            }
            $scope.newHost = {};
            Core.$apply($scope);
        };

        // used by add dialog in preferences.html
        $scope.hostSchema = {
            properties: {
                'name': {
                    description: 'Indicator name',
                    type: 'string',
                    required: true
                },
                'regex': {
                    description: 'Indicator regex',
                    type: 'string',
                    required: true
                }
            }
        };

        $scope.delete = function (index) {
            $scope.regexs.removeAt(index);
        };

        $scope.moveUp = function (index) {
            var tmp = $scope.hosts[index];
            $scope.regexs[index] = $scope.regexs[index - 1];
            $scope.regexs[index - 1] = tmp;
        };

        $scope.moveDown = function (index) {
            var tmp = $scope.regexs[index];
            $scope.regexs[index] = $scope.regexs[index + 1];
            $scope.regexs[index + 1] = tmp;
        };
    }
    Core.CorePreferences = CorePreferences;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    

    /**
    * @class Workspace
    */
    var Workspace = (function () {
        function Workspace(jolokia, jolokiaStatus, jmxTreeLazyLoadRegistry, $location, $compile, $templateCache, localStorage, $rootScope, userDetails) {
            this.jolokia = jolokia;
            this.jolokiaStatus = jolokiaStatus;
            this.jmxTreeLazyLoadRegistry = jmxTreeLazyLoadRegistry;
            this.$location = $location;
            this.$compile = $compile;
            this.$templateCache = $templateCache;
            this.localStorage = localStorage;
            this.$rootScope = $rootScope;
            this.userDetails = userDetails;
            this.operationCounter = 0;
            this.tree = new Core.Folder('MBeans');
            this.treeResponse = {};
            this.mbeanTypesToDomain = {};
            this.mbeanServicesToDomain = {};
            this.attributeColumnDefs = {};
            this.treePostProcessors = [];
            this.topLevelTabs = [];
            this.subLevelTabs = [];
            this.keyToNodeMap = {};
            this.pluginRegisterHandle = null;
            this.pluginUpdateCounter = null;
            this.treeWatchRegisterHandle = null;
            this.treeWatcherCounter = null;
            this.treeElement = null;
            // mapData allows to store arbitrary data on the workspace
            this.mapData = {};
            // set defaults
            if (!('autoRefresh' in localStorage)) {
                localStorage['autoRefresh'] = true;
            }
            if (!('updateRate' in localStorage)) {
                localStorage['updateRate'] = 5000;
            }
        }
        /**
        * Creates a shallow copy child workspace with its own selection and location
        * @method createChildWorkspace
        * @param {ng.ILocationService} location
        * @return {Workspace}
        */
        Workspace.prototype.createChildWorkspace = function (location) {
            var child = new Workspace(this.jolokia, this.jolokiaStatus, this.jmxTreeLazyLoadRegistry, this.$location, this.$compile, this.$templateCache, this.localStorage, this.$rootScope, this.userDetails);

            // lets copy across all the properties just in case
            angular.forEach(this, function (value, key) {
                return child[key] = value;
            });
            child.$location = location;
            return child;
        };

        Workspace.prototype.getLocalStorage = function (key) {
            return this.localStorage[key];
        };

        Workspace.prototype.setLocalStorage = function (key, value) {
            this.localStorage[key] = value;
        };

        Workspace.prototype.loadTree = function () {
            // Make an initial blocking call to ensure the JMX tree is populated while the
            // app is initializing...
            //var flags = {error: initialLoadError, ajaxError: initialLoadError, maxDepth: 2};
            var flags = { ignoreErrors: true, maxDepth: 2 };
            var data = this.jolokia.list(null, onSuccess(null, flags));

            if (data) {
                this.jolokiaStatus.xhr = null;
            }
            this.populateTree({
                value: data
            });
            // we now only reload the tree if the TreeWatcher mbean is present...
            // Core.register(this.jolokia, this, {type: 'list', maxDepth: 2}, onSuccess(angular.bind(this, this.populateTree), {maxDepth: 2}));
        };

        /**
        * Adds a post processor of the tree to swizzle the tree metadata after loading
        * such as correcting any typeName values or CSS styles by hand
        * @method addTreePostProcessor
        * @param {Function} processor
        */
        Workspace.prototype.addTreePostProcessor = function (processor) {
            this.treePostProcessors.push(processor);

            var tree = this.tree;
            if (tree) {
                // the tree is loaded already so lets process it now :)
                processor(tree);
            }
        };

        Workspace.prototype.maybeMonitorPlugins = function () {
            if (this.treeContainsDomainAndProperties("hawtio", { type: "Registry" })) {
                if (this.pluginRegisterHandle === null) {
                    this.pluginRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeUpdatePlugins), {
                        type: "read",
                        mbean: "hawtio:type=Registry",
                        attribute: "UpdateCounter"
                    });
                }
            } else {
                if (this.pluginRegisterHandle !== null) {
                    this.jolokia.unregister(this.pluginRegisterHandle);
                    this.pluginRegisterHandle = null;
                    this.pluginUpdateCounter = null;
                }
            }

            // lets also listen to see if we have a JMX tree watcher
            if (this.treeContainsDomainAndProperties("hawtio", { type: "TreeWatcher" })) {
                if (this.treeWatchRegisterHandle === null) {
                    this.treeWatchRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeReloadTree), {
                        type: "read",
                        mbean: "hawtio:type=TreeWatcher",
                        attribute: "Counter"
                    });
                }
            }
        };

        Workspace.prototype.maybeUpdatePlugins = function (response) {
            if (this.pluginUpdateCounter === null) {
                this.pluginUpdateCounter = response.value;
                return;
            }
            if (this.pluginUpdateCounter !== response.value) {
                if (Core.parseBooleanValue(localStorage['autoRefresh'])) {
                    window.location.reload();
                }
            }
        };

        Workspace.prototype.maybeReloadTree = function (response) {
            var counter = response.value;
            if (this.treeWatcherCounter === null) {
                this.treeWatcherCounter = counter;
                return;
            }
            if (this.treeWatcherCounter !== counter) {
                this.treeWatcherCounter = counter;
                var workspace = this;
                function wrapInValue(response) {
                    var wrapper = {
                        value: response
                    };
                    workspace.populateTree(wrapper);
                }
                this.jolokia.list(null, onSuccess(wrapInValue, { ignoreErrors: true, maxDepth: 2 }));
            }
        };

        Workspace.prototype.folderGetOrElse = function (folder, value) {
            if (folder) {
                try  {
                    return folder.getOrElse(value);
                } catch (e) {
                    Core.log.warn("Failed to find value " + value + " on folder " + folder);
                }
            }
            return null;
        };

        Workspace.prototype.populateTree = function (response) {
            if (!Object.equal(this.treeResponse, response.value)) {
                this.treeResponse = response.value;
                Core.log.debug("JMX tree has been loaded!");

                var rootId = 'root';
                var separator = '-';
                this.mbeanTypesToDomain = {};
                this.mbeanServicesToDomain = {};
                this.keyToNodeMap = {};
                var tree = new Core.Folder('MBeans');
                tree.key = rootId;
                var domains = response.value;
                for (var domain in domains) {
                    var domainClass = escapeDots(domain);
                    var mbeans = domains[domain];
                    for (var path in mbeans) {
                        var entries = {};
                        var folder = this.folderGetOrElse(tree, domain);

                        //if (!folder) continue;
                        folder.domain = domain;
                        if (!folder.key) {
                            folder.key = rootId + separator + domain;
                        }
                        var folderNames = [domain];
                        folder.folderNames = folderNames;
                        folderNames = folderNames.clone();
                        var items = path.split(',');
                        var paths = [];
                        var typeName = null;
                        var serviceName = null;
                        items.forEach(function (item) {
                            var kv = item.split('=');
                            var key = kv[0];
                            var value = kv[1] || key;
                            entries[key] = value;
                            var moveToFront = false;
                            var lowerKey = key.toLowerCase();
                            if (lowerKey === "type") {
                                typeName = value;

                                // if the type name value already exists in the root node
                                // of the domain then lets move this property around too
                                if (folder.map[value]) {
                                    moveToFront = true;
                                }
                            }
                            if (lowerKey === "service") {
                                serviceName = value;
                            }
                            if (moveToFront) {
                                paths.splice(0, 0, value);
                            } else {
                                paths.push(value);
                            }
                        });

                        var configureFolder = function (folder, name) {
                            folder.domain = domain;
                            if (!folder.key) {
                                folder.key = rootId + separator + folderNames.join(separator);
                            }
                            this.keyToNodeMap[folder.key] = folder;
                            folder.folderNames = folderNames.clone();

                            //var classes = escapeDots(folder.key);
                            var classes = "";
                            var entries = folder.entries;
                            var entryKeys = Object.keys(entries).filter(function (n) {
                                return n.toLowerCase().indexOf("type") >= 0;
                            });
                            if (entryKeys.length) {
                                angular.forEach(entryKeys, function (entryKey) {
                                    var entryValue = entries[entryKey];
                                    if (!folder.ancestorHasEntry(entryKey, entryValue)) {
                                        classes += " " + domainClass + separator + entryValue;
                                    }
                                });
                            } else {
                                var kindName = folderNames.last();

                                /*if (folder.parent && folder.parent.title === typeName) {
                                kindName = typeName;
                                } else */
                                if (kindName === name) {
                                    kindName += "-folder";
                                }
                                if (kindName) {
                                    classes += " " + domainClass + separator + kindName;
                                }
                            }
                            folder.addClass = escapeTreeCssStyles(classes);
                            return folder;
                        };

                        var lastPath = paths.pop();
                        var ws = this;
                        paths.forEach(function (value) {
                            folder = ws.folderGetOrElse(folder, value);
                            if (folder) {
                                folderNames.push(value);
                                angular.bind(ws, configureFolder, folder, value)();
                            }
                        });
                        var key = rootId + separator + folderNames.join(separator) + separator + lastPath;
                        var objectName = domain + ":" + path;

                        if (folder) {
                            folder = this.folderGetOrElse(folder, lastPath);
                            if (folder) {
                                // lets add the various data into the folder
                                folder.entries = entries;
                                folder.key = key;
                                angular.bind(this, configureFolder, folder, lastPath)();
                                folder.title = Core.trimQuotes(lastPath);
                                folder.objectName = objectName;
                                folder.typeName = typeName;

                                var addFolderByDomain = function (owner, typeName) {
                                    var map = owner[typeName];
                                    if (!map) {
                                        map = {};
                                        owner[typeName] = map;
                                    }
                                    var value = map[domain];
                                    if (!value) {
                                        map[domain] = folder;
                                    } else {
                                        var array = null;
                                        if (angular.isArray(value)) {
                                            array = value;
                                        } else {
                                            array = [value];
                                            map[domain] = array;
                                        }
                                        array.push(folder);
                                    }
                                };

                                if (serviceName) {
                                    angular.bind(this, addFolderByDomain, this.mbeanServicesToDomain, serviceName)();
                                }
                                if (typeName) {
                                    angular.bind(this, addFolderByDomain, this.mbeanTypesToDomain, typeName)();
                                }
                            }
                        } else {
                            Core.log.info("No folder found for lastPath: " + lastPath);
                        }
                    }
                }

                tree.sortChildren(true);

                // now lets mark the nodes with no children as lazy loading...
                this.enableLazyLoading(tree);
                this.tree = tree;

                var processors = this.treePostProcessors;
                angular.forEach(processors, function (processor) {
                    return processor(tree);
                });

                this.maybeMonitorPlugins();

                var rootScope = this.$rootScope;
                if (rootScope) {
                    rootScope.$broadcast('jmxTreeUpdated');
                }
            }
        };

        Workspace.prototype.enableLazyLoading = function (folder) {
            var _this = this;
            var children = folder.children;
            if (children && children.length) {
                angular.forEach(children, function (child) {
                    _this.enableLazyLoading(child);
                });
            } else {
                // we have no children so enable lazy loading if we have a custom loader registered
                var lazyFunction = Jmx.findLazyLoadingFunction(this, folder);
                if (lazyFunction) {
                    folder.isLazy = true;
                }
            }
        };

        /**
        * Returns the hash query argument to append to URL links
        * @method hash
        * @return {String}
        */
        Workspace.prototype.hash = function () {
            var hash = this.$location.search();
            var params = Core.hashToString(hash);
            if (params) {
                return "?" + params;
            }
            return "";
        };

        /**
        * Returns the currently active tab
        * @method getActiveTab
        * @return {Boolean}
        */
        Workspace.prototype.getActiveTab = function () {
            var workspace = this;
            return this.topLevelTabs.find(function (tab) {
                if (!angular.isDefined(tab.isActive)) {
                    return workspace.isLinkActive(tab.href());
                } else {
                    return tab.isActive(workspace);
                }
            });
        };

        Workspace.prototype.getStrippedPathName = function () {
            var pathName = Core.trimLeading((this.$location.path() || '/'), "#");
            pathName = Core.trimLeading(pathName, "/");
            return pathName;
        };

        Workspace.prototype.linkContains = function () {
            var words = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                words[_i] = arguments[_i + 0];
            }
            var pathName = this.getStrippedPathName();
            return words.all(function (word) {
                return pathName.has(word);
            });
        };

        /**
        * Returns true if the given link is active. The link can omit the leading # or / if necessary.
        * The query parameters of the URL are ignored in the comparison.
        * @method isLinkActive
        * @param {String} href
        * @return {Boolean} true if the given link is active
        */
        Workspace.prototype.isLinkActive = function (href) {
            // lets trim the leading slash
            var pathName = this.getStrippedPathName();

            var link = Core.trimLeading(href, "#");
            link = Core.trimLeading(link, "/");

            // strip any query arguments
            var idx = link.indexOf('?');
            if (idx >= 0) {
                link = link.substring(0, idx);
            }
            if (!pathName.length) {
                return link === pathName;
            } else {
                return pathName.startsWith(link);
            }
        };

        /**
        * Returns true if the given link is active. The link can omit the leading # or / if necessary.
        * The query parameters of the URL are ignored in the comparison.
        * @method isLinkActive
        * @param {String} href
        * @return {Boolean} true if the given link is active
        */
        Workspace.prototype.isLinkPrefixActive = function (href) {
            // lets trim the leading slash
            var pathName = this.getStrippedPathName();

            var link = Core.trimLeading(href, "#");
            link = Core.trimLeading(link, "/");

            // strip any query arguments
            var idx = link.indexOf('?');
            if (idx >= 0) {
                link = link.substring(0, idx);
            }
            return pathName.startsWith(link);
        };

        /**
        * Returns true if the tab query parameter is active or the URL starts with the given path
        * @method isTopTabActive
        * @param {String} path
        * @return {Boolean}
        */
        Workspace.prototype.isTopTabActive = function (path) {
            var tab = this.$location.search()['tab'];
            if (angular.isString(tab)) {
                return tab.startsWith(path);
            }
            return this.isLinkActive(path);
        };

        /**
        * Returns the selected mbean name if there is one
        * @method getSelectedMBeanName
        * @return {String}
        */
        Workspace.prototype.getSelectedMBeanName = function () {
            var selection = this.selection;
            if (selection) {
                return selection.objectName;
            }
            return null;
        };

        /**
        * Returns true if the path is valid for the current selection
        * @method validSelection
        * @param {String} uri
        * @return {Boolean}
        */
        Workspace.prototype.validSelection = function (uri) {
            var workspace = this;
            var filter = function (t) {
                var fn = t.href;
                if (fn) {
                    var href = fn();
                    if (href) {
                        if (href.startsWith("#/")) {
                            href = href.substring(2);
                        }
                        return href === uri;
                    }
                }
                return false;
            };
            var tab = this.subLevelTabs.find(filter);
            if (!tab) {
                tab = this.topLevelTabs.find(filter);
            }
            if (tab) {
                //console.log("Found tab " + JSON.stringify(tab));
                var validFn = tab['isValid'];
                return !angular.isDefined(validFn) || validFn(workspace);
            } else {
                Core.log.info("Could not find tab for " + uri);
                return false;
            }
            /*
            var value = this.uriValidations[uri];
            if (value) {
            if (angular.isFunction(value)) {
            return value();
            }
            }
            return true;
            */
        };

        /**
        * In cases where we have just deleted something we typically want to change
        * the selection to the parent node
        * @method removeAndSelectParentNode
        */
        Workspace.prototype.removeAndSelectParentNode = function () {
            var selection = this.selection;
            if (selection) {
                var parent = selection.parent;
                if (parent) {
                    // lets remove the selection from the parent so we don't do any more JMX attribute queries on the children
                    // or include it in table views etc
                    // would be nice to eagerly remove the tree node too?
                    var idx = parent.children.indexOf(selection);
                    if (idx < 0) {
                        idx = parent.children.findIndex(function (n) {
                            return n.key === selection.key;
                        });
                    }
                    if (idx >= 0) {
                        parent.children.splice(idx, 1);
                    }
                    this.updateSelectionNode(parent);
                }
            }
        };

        Workspace.prototype.selectParentNode = function () {
            var selection = this.selection;
            if (selection) {
                var parent = selection.parent;
                if (parent) {
                    this.updateSelectionNode(parent);
                }
            }
        };

        /**
        * Returns the view configuration key for the kind of selection
        * for example based on the domain and the node type
        * @method selectionViewConfigKey
        * @return {String}
        */
        Workspace.prototype.selectionViewConfigKey = function () {
            return this.selectionConfigKey("view/");
        };

        /**
        * Returns a configuration key for a node which is usually of the form
        * domain/typeName or for folders with no type, domain/name/folder
        * @method selectionConfigKey
        * @param {String} prefix
        * @return {String}
        */
        Workspace.prototype.selectionConfigKey = function (prefix) {
            if (typeof prefix === "undefined") { prefix = ""; }
            var key = null;
            var selection = this.selection;
            if (selection) {
                // lets make a unique string for the kind of select
                key = prefix + selection.domain;
                var typeName = selection.typeName;
                if (!typeName) {
                    typeName = selection.title;
                }
                key += "/" + typeName;
                if (selection.isFolder()) {
                    key += "/folder";
                }
            }
            return key;
        };

        Workspace.prototype.moveIfViewInvalid = function () {
            var workspace = this;
            var uri = Core.trimLeading(this.$location.path(), "/");
            if (this.selection) {
                var key = this.selectionViewConfigKey();
                if (this.validSelection(uri)) {
                    // lets remember the previous selection
                    this.setLocalStorage(key, uri);
                    return false;
                } else {
                    Core.log.info("the uri '" + uri + "' is not valid for this selection");

                    // lets look up the previous preferred value for this type
                    var defaultPath = this.getLocalStorage(key);
                    if (!defaultPath || !this.validSelection(defaultPath)) {
                        // lets find the first path we can find which is valid
                        defaultPath = null;
                        angular.forEach(this.subLevelTabs, function (tab) {
                            var fn = tab.isValid;
                            if (!defaultPath && tab.href && angular.isDefined(fn) && fn(workspace)) {
                                defaultPath = tab.href();
                            }
                        });
                    }
                    if (!defaultPath) {
                        defaultPath = "#/jmx/help";
                    }
                    Core.log.info("moving the URL to be " + defaultPath);
                    if (defaultPath.startsWith("#")) {
                        defaultPath = defaultPath.substring(1);
                    }
                    this.$location.path(defaultPath);
                    return true;
                }
            } else {
                return false;
            }
        };

        Workspace.prototype.updateSelectionNode = function (node) {
            var originalSelection = this.selection;
            this.selection = node;
            var key = null;
            if (node) {
                key = node['key'];
            }
            var $location = this.$location;
            var q = $location.search();
            if (key) {
                q['nid'] = key;
            }
            $location.search(q);

            // if we have updated the selection (rather than just loaded a page)
            // lets use the previous preferred view - otherwise we may be loading
            // a page from a bookmark so lets not change the view :)
            if (originalSelection) {
                key = this.selectionViewConfigKey();
                if (key) {
                    var defaultPath = this.getLocalStorage(key);
                    if (defaultPath) {
                        this.$location.path(defaultPath);
                    }
                }
            }
        };

        /**
        * Redraws the tree widget
        * @method redrawTree
        */
        Workspace.prototype.redrawTree = function () {
            var treeElement = this.treeElement;
            if (treeElement) {
                treeElement.dynatree("getTree").reload();
            }
        };

        /**
        * Expand / collapse the current active node
        * @method expandSelection
        * @param {Boolean} flag
        */
        Workspace.prototype.expandSelection = function (flag) {
            var treeElement = this.treeElement;
            if (treeElement) {
                var node = treeElement.dynatree("getActiveNode");
                if (node) {
                    node.expand(flag);
                }
            }
        };

        Workspace.prototype.matchesProperties = function (entries, properties) {
            if (!entries)
                return false;
            for (var key in properties) {
                var value = properties[key];
                if (!value || entries[key] !== value) {
                    return false;
                }
            }
            return true;
        };

        Workspace.prototype.treeContainsDomainAndProperties = function (domainName, properties) {
            var _this = this;
            if (typeof properties === "undefined") { properties = null; }
            var workspace = this;
            var tree = workspace.tree;
            if (tree) {
                var folder = tree.get(domainName);
                if (folder) {
                    if (properties) {
                        var children = folder.children || [];
                        var checkProperties = function (node) {
                            if (!_this.matchesProperties(node.entries, properties)) {
                                if (node.domain === domainName && node.children && node.children.length > 0) {
                                    return node.children.some(checkProperties);
                                } else {
                                    return false;
                                }
                            } else {
                                return true;
                            }
                        };
                        return children.some(checkProperties);
                    }
                    return true;
                } else {
                    // console.log("no hasMBean for " + objectName + " in tree " + tree);
                }
            } else {
                // console.log("workspace has no tree! returning false for hasMBean " + objectName);
            }
            return false;
        };

        Workspace.prototype.matches = function (folder, properties, propertiesCount) {
            if (folder) {
                var entries = folder.entries;
                if (properties) {
                    if (!entries)
                        return false;
                    for (var key in properties) {
                        var value = properties[key];
                        if (!value || entries[key] !== value) {
                            return false;
                        }
                    }
                }
                if (propertiesCount) {
                    return entries && Object.keys(entries).length === propertiesCount;
                }
                return true;
            }
            return false;
        };

        // only display stuff if we have an mbean with the given properties
        Workspace.prototype.hasDomainAndProperties = function (domainName, properties, propertiesCount) {
            if (typeof properties === "undefined") { properties = null; }
            if (typeof propertiesCount === "undefined") { propertiesCount = null; }
            var node = this.selection;
            if (node) {
                return this.matches(node, properties, propertiesCount) && node.domain === domainName;
            }
            return false;
        };

        // only display stuff if we have an mbean with the given properties
        Workspace.prototype.findMBeanWithProperties = function (domainName, properties, propertiesCount) {
            if (typeof properties === "undefined") { properties = null; }
            if (typeof propertiesCount === "undefined") { propertiesCount = null; }
            var tree = this.tree;
            if (tree) {
                return this.findChildMBeanWithProperties(tree.get(domainName), properties, propertiesCount);
            }
            return null;
        };

        Workspace.prototype.findChildMBeanWithProperties = function (folder, properties, propertiesCount) {
            var _this = this;
            if (typeof properties === "undefined") { properties = null; }
            if (typeof propertiesCount === "undefined") { propertiesCount = null; }
            var workspace = this;
            if (folder) {
                var children = folder.children;
                if (children) {
                    var answer = children.find(function (node) {
                        return _this.matches(node, properties, propertiesCount);
                    });
                    if (answer) {
                        return answer;
                    }
                    return children.map(function (node) {
                        return workspace.findChildMBeanWithProperties(node, properties, propertiesCount);
                    }).find(function (node) {
                        return node;
                    });
                }
            }
            return null;
        };

        Workspace.prototype.selectionHasDomainAndLastFolderName = function (objectName, lastName) {
            var lastNameLower = (lastName || "").toLowerCase();
            function isName(name) {
                return (name || "").toLowerCase() === lastNameLower;
            }
            var node = this.selection;
            if (node) {
                if (objectName === node.domain) {
                    var folders = node.folderNames;
                    if (folders) {
                        var last = folders.last();
                        return (isName(last) || isName(node.title)) && node.isFolder() && !node.objectName;
                    }
                }
            }
            return false;
        };

        Workspace.prototype.selectionHasDomain = function (domainName) {
            var node = this.selection;
            if (node) {
                return domainName === node.domain;
            }
            return false;
        };

        Workspace.prototype.selectionHasDomainAndType = function (objectName, typeName) {
            var node = this.selection;
            if (node) {
                return objectName === node.domain && typeName === node.typeName;
            }
            return false;
        };

        /**
        * Returns true if this workspace has any mbeans at all
        */
        Workspace.prototype.hasMBeans = function () {
            var answer = false;
            var tree = this.tree;
            if (tree) {
                var children = tree.children;
                if (angular.isArray(children) && children.length > 0) {
                    answer = true;
                }
            }
            return answer;
        };
        Workspace.prototype.hasFabricMBean = function () {
            return this.hasDomainAndProperties('io.fabric8', { type: 'Fabric' });
        };
        Workspace.prototype.isFabricFolder = function () {
            return this.hasDomainAndProperties('io.fabric8');
        };

        Workspace.prototype.isCamelContext = function () {
            return this.hasDomainAndProperties('org.apache.camel', { type: 'context' });
        };
        Workspace.prototype.isCamelFolder = function () {
            return this.hasDomainAndProperties('org.apache.camel');
        };
        Workspace.prototype.isEndpointsFolder = function () {
            return this.selectionHasDomainAndLastFolderName('org.apache.camel', 'endpoints');
        };
        Workspace.prototype.isEndpoint = function () {
            return this.hasDomainAndProperties('org.apache.camel', { type: 'endpoints' });
        };
        Workspace.prototype.isRoutesFolder = function () {
            return this.selectionHasDomainAndLastFolderName('org.apache.camel', 'routes');
        };
        Workspace.prototype.isRoute = function () {
            return this.hasDomainAndProperties('org.apache.camel', { type: 'routes' });
        };

        Workspace.prototype.isOsgiFolder = function () {
            return this.hasDomainAndProperties('osgi.core');
        };
        Workspace.prototype.isKarafFolder = function () {
            return this.hasDomainAndProperties('org.apache.karaf');
        };
        Workspace.prototype.isOsgiCompendiumFolder = function () {
            return this.hasDomainAndProperties('osgi.compendium');
        };
        return Workspace;
    })();
    Core.Workspace = Workspace;
})(Core || (Core = {}));

// TODO refactor other code to use Core.Workspace
var Workspace = (function (_super) {
    __extends(Workspace, _super);
    function Workspace() {
        _super.apply(this, arguments);
    }
    return Workspace;
})(Core.Workspace);
;
;
/**
* @module Core
*/
var Core;
(function (Core) {
    Core.fileUploadMBean = "hawtio:type=UploadManager";

    var FileUpload = (function () {
        function FileUpload() {
            this.restrict = 'A';
            this.replace = true;
            this.templateUrl = Core.templatePath + "fileUpload.html";
            this.scope = {
                files: '=hawtioFileUpload',
                target: '@',
                showFiles: '@'
            };
            this.controller = function ($scope, $element, $attrs, jolokia) {
                $scope.target = '';
                $scope.response = '';
                $scope.percentComplete = 0;

                UI.observe($scope, $attrs, 'target', '');
                UI.observe($scope, $attrs, 'showFiles', true);

                $scope.update = function (response) {
                    var responseJson = angular.toJson(response.value);
                    if ($scope.responseJson !== responseJson) {
                        $scope.responseJson = responseJson;
                        $scope.files = response.value;
                        Core.$applyNowOrLater($scope);
                    }
                };

                $scope.delete = function (fileName) {
                    //notification('info', 'Deleting ' + fileName);
                    jolokia.request({
                        type: 'exec', mbean: Core.fileUploadMBean,
                        operation: 'delete(java.lang.String, java.lang.String)',
                        arguments: [$scope.target, fileName] }, {
                        success: function () {
                            //notification('success', 'Deleted ' + fileName);
                            Core.$apply($scope);
                        },
                        error: function (response) {
                            Core.notification('error', "Failed to delete " + fileName + " due to: " + response.error);
                            Core.$apply($scope);
                        }
                    });
                };

                $scope.$watch('target', function (newValue, oldValue) {
                    if (oldValue !== newValue) {
                        Core.unregister(jolokia, $scope);
                    }
                    Core.register(jolokia, $scope, {
                        type: 'exec', mbean: Core.fileUploadMBean,
                        operation: 'list(java.lang.String)',
                        arguments: [$scope.target]
                    }, onSuccess($scope.update));
                });
            };
            this.link = function ($scope, $element, $attrs) {
                var fileInput = $element.find('input[type=file]');
                var form = $element.find('form[name=file-upload]');
                var button = $element.find('input[type=button]');

                var onFileChange = function () {
                    button.prop('disabled', true);

                    var files = fileInput.get(0).files;

                    var fileName = files.length + " files";
                    if (files.length === 1) {
                        fileName = files[0].name;
                    }

                    form.ajaxSubmit({
                        beforeSubmit: function (arr, $form, options) {
                            Core.notification('info', "Uploading " + fileName);
                            $scope.percentComplete = 0;
                            Core.$apply($scope);
                        },
                        success: function (response, statusText, xhr, $form) {
                            Core.notification('success', "Uploaded " + fileName);
                            setTimeout(function () {
                                button.prop('disabled', false);
                                $scope.percentComplete = 0;
                                Core.$apply($scope);
                            }, 1000);
                            Core.$apply($scope);
                        },
                        error: function (response, statusText, xhr, $form) {
                            Core.notification('error', "Failed to upload " + fileName + " due to " + statusText);
                            setTimeout(function () {
                                button.prop('disabled', false);
                                $scope.percentComplete = 0;
                                Core.$apply($scope);
                            }, 1000);
                            Core.$apply($scope);
                        },
                        uploadProgress: function (event, position, total, percentComplete) {
                            $scope.percentComplete = percentComplete;
                            Core.$apply($scope);
                        }
                    });
                    return false;
                };

                button.click(function () {
                    if (!button.prop('disabled')) {
                        fileInput.click();
                    }
                    return false;
                });

                form.submit(function () {
                    return false;
                });

                if ($.browser.msie) {
                    fileInput.click(function (event) {
                        setTimeout(function () {
                            if (fileInput.val().length > 0) {
                                onFileChange();
                            }
                        }, 0);
                    });
                } else {
                    fileInput.change(onFileChange);
                }
            };
        }
        return FileUpload;
    })();
    Core.FileUpload = FileUpload;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    /**
    * Directive that's used to ensure an ng-grid expands it's height to fit the viewport height
    * @class GridStyle
    */
    var GridStyle = (function () {
        function GridStyle($window) {
            var _this = this;
            this.$window = $window;
            this.restrict = 'C';
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        GridStyle.prototype.doLink = function (scope, element, attrs) {
            var lastHeight = 0;

            var resizeFunc = angular.bind(this, function (scope) {
                var top = element.position().top;
                var windowHeight = $(this.$window).height();
                var height = windowHeight - top - 15;
                var heightStr = height + 'px';

                element.css({
                    'min-height': heightStr,
                    'height': heightStr
                });

                if (lastHeight !== height) {
                    lastHeight = height;
                    element.trigger('resize');
                }
            });

            resizeFunc();

            scope.$watch(resizeFunc);

            $(this.$window).resize(function () {
                resizeFunc();
                Core.$apply(scope);
                return false;
            });
        };
        return GridStyle;
    })();
    Core.GridStyle = GridStyle;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function HelpController($scope, $routeParams, marked, helpRegistry, branding) {
        $scope.branding = branding;
        $scope.topics = helpRegistry.getTopics();

        if ('topic' in $routeParams) {
            $scope.topic = $routeParams['topic'];
        } else {
            $scope.topic = 'index';
        }

        if ('subtopic' in $routeParams) {
            $scope.subTopic = $routeParams['subtopic'];
        } else {
            $scope.subTopic = Object.extended($scope.topics[$scope.topic]).keys().first();
        }

        Core.log.debug("topic: ", $scope.topic, " subtopic: ", $scope.subTopic);

        // when on the index pages, filter the user subTopic unless on the dev page
        var isIndex = $scope.topic === "index";
        var filterSubTopic = $scope.subTopic;
        if (isIndex && filterSubTopic !== "developer") {
            filterSubTopic = "user";
        }

        $scope.breadcrumbs = [
            {
                topic: "index",
                subTopic: "user",
                label: "User Guide"
            },
            {
                topic: "index",
                subTopic: "faq",
                label: "FAQ"
            },
            {
                topic: "index",
                subTopic: "changes",
                label: "Changes"
            },
            {
                topic: "index",
                subTopic: "developer",
                label: "Developers"
            }
        ];

        $scope.sectionLink = function (section) {
            var topic = section.topic || "";
            var subTopic = section.subTopic || "";
            var link = Core.pathGet(helpRegistry.topics, [topic, subTopic]);
            if (link && link.indexOf("#") >= 0) {
                return link;
            } else {
                return "#/help/" + topic + "/" + subTopic;
            }
        };

        // lets select the active tab
        var activeBreadcrumb = $scope.breadcrumbs.find(function (b) {
            return b.topic === $scope.topic && b.subTopic === $scope.subTopic;
        });
        if (activeBreadcrumb)
            activeBreadcrumb.active = true;

        $scope.sections = [];
        angular.forEach($scope.topics, function (details, topic) {
            // lets hide any index topics or any topics which don't have a filter sub topic
            if (topic !== "index" && details[filterSubTopic]) {
                $scope.sections.push({
                    topic: topic,
                    subTopic: filterSubTopic,
                    label: helpRegistry.mapTopicName(topic),
                    active: topic === $scope.topic
                });
            }
        });
        $scope.sections = $scope.sections.sortBy("label");

        $scope.$on('hawtioNewHelpTopic', function () {
            $scope.topics = helpRegistry.getTopics();
        });

        $scope.$watch('topics', function (newValue, oldValue) {
            Core.log.debug("Topics: ", $scope.topics);
        });

        if (!angular.isDefined($scope.topics[$scope.topic])) {
            $scope.html = "Unable to download help data for " + $scope.topic;
        } else {
            $.ajax({
                url: $scope.topics[$scope.topic][$scope.subTopic],
                dataType: 'html',
                cache: false,
                success: function (data, textStatus, jqXHR) {
                    $scope.html = "Unable to download help data for " + $scope.topic;
                    if (angular.isDefined(data)) {
                        $scope.html = marked(data);
                    }
                    Core.$apply($scope);
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    $scope.html = "Unable to download help data for " + $scope.topic;
                    Core.$apply($scope);
                }
            });
        }
    }
    Core.HelpController = HelpController;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function LoggingPreferences($scope) {
        Core.initPreferenceScope($scope, localStorage, {
            'logBuffer': {
                'value': 100,
                'converter': parseInt,
                'formatter': parseInt,
                'post': function (newValue) {
                    window['LogBuffer'] = newValue;
                }
            },
            'logLevel': {
                'value': '{"value": 2, "name": "INFO"}',
                'post': function (value) {
                    var level = angular.fromJson(value);
                    Logger.setLevel(level);
                }
            }
        });
    }
    Core.LoggingPreferences = LoggingPreferences;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function d3ForceGraph(scope, nodes, links, canvasElement) {
        // lets remove the old graph first
        if (scope.graphForce) {
            scope.graphForce.stop();
        }
        if (!canvasElement) {
            canvasElement = $("#canvas")[0];
        }
        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'];
                }
            }

            //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 + ")";
                });
            });
        }
    }
    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 {
                if (node.edges === undefined)
                    node.edges = [];
                if (!node.label)
                    node.label = "node " + idx;
                stateKeys[idx] = node;
            }
        });
        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 {
                var edge = { source: source, target: target };
                transitions.push(edge);
                source.edges.push(edge);
                target.edges.push(edge);
                // TODO should we add the edge to the target?
            }
        });
        return states;
    }
    Core.createGraphStates = createGraphStates;

    // TODO Export as a service
    function dagreLayoutGraph(nodes, links, width, height, svgElement) {
        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("filter", "url(#drop-shadow)").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);

        // 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;
            }
            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;
        });

        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);
            }
        }

        // 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));
        });

        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);

        // 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 || "";
    }
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    // 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.
    function ViewController($scope, $route, $location, layoutTree, layoutFull, viewRegistry) {
        findViewPartial();

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            findViewPartial();
        });

        function searchRegistry(path) {
            var answer = undefined;
            Object.extended(viewRegistry).keys(function (key, value) {
                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) {
                            console.log("Invalid RegExp " + text + " for viewRegistry value: " + value);
                        }
                    } else {
                        if (path.startsWith(key)) {
                            answer = value;
                        }
                    }
                }
            });

            //console.log("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;

            console.log("Using view partial: " + answer);
            return answer;
        }
    }
    Core.ViewController = ViewController;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function NavBarController($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.$watch('localStorage.recentConnections', function (newValue, oldValue) {
            $scope.recentConnections = Core.getRecentConnections(localStorage);
            Core.log.debug("recent containers: ", $scope.recentConnections);
        });

        $scope.openConnection = function (connection) {
            window.open(connection.url);
        };

        $scope.goHome = function () {
            window.open(".");
        };

        $scope.clearConnections = Core.clearConnections;

        $scope.perspectiveDetails = {
            perspective: null
        };

        $scope.topLevelTabs = function () {
            reloadPerspective();

            // TODO transform the top level tabs based on the current perspective
            // TODO watch for changes to workspace.topLevelTabs and for the current perspective
            return workspace.topLevelTabs;
        };

        $scope.$on('jmxTreeUpdated', function () {
            reloadPerspective();
        });

        $scope.$watch('workspace.topLevelTabs', function () {
            reloadPerspective();
        });

        $scope.validSelection = function (uri) {
            return workspace.validSelection(uri);
        };

        $scope.isValid = function (nav) {
            return nav && nav.isValid(workspace);
        };

        $scope.switchPerspective = function (perspective) {
            if (perspective.onSelect && angular.isFunction(perspective.onSelect)) {
                perspective.onSelect.apply();
                return;
            }
            var searchPerspectiveId = $location.search()[Perspective.perspectiveSearchId];
            if (perspective && ($scope.currentPerspective !== perspective || perspective.id !== searchPerspectiveId)) {
                Logger.debug("Changed the perspective to " + JSON.stringify(perspective) + " from search id " + searchPerspectiveId);
                if ($scope.currentPerspective) {
                    $scope.currentPerspective.lastPage = $location.url();
                }
                var pid = perspective.id;
                $location.search(Perspective.perspectiveSearchId, pid);
                Logger.debug("Setting perspective to " + pid);
                $scope.currentPerspective = perspective;
                reloadPerspective();
                $scope.topLevelTabs = Perspective.getTopLevelTabsForPerspective($location, workspace, jolokia, localStorage);

                // is any of the top level tabs marked as default?
                var defaultPlugin = Core.getDefaultPlugin(pid, workspace, jolokia, localStorage);
                var defaultTab;
                var path;
                if (defaultPlugin) {
                    $scope.topLevelTabs.forEach(function (tab) {
                        if (tab.id === defaultPlugin.id) {
                            defaultTab = tab;
                        }
                    });
                    if (defaultTab) {
                        path = Core.trimLeading(defaultTab.href(), "#");
                    }
                } else {
                    // if no default plugin configured, then select the last page as the active location
                    if (perspective.lastPage) {
                        path = Core.trimLeading(perspective.lastPage, "#");
                    }
                }

                if (path) {
                    // lets avoid any old paths with ?p=" inside
                    var idx = path.indexOf("?p=") || path.indexOf("&p=");
                    if (idx > 0) {
                        path = path.substring(0, idx);
                    }
                    var sep = (path.indexOf("?") >= 0) ? "&" : "?";
                    path += sep + "p=" + pid;
                    $location.url(path);
                }
            }
        };

        $scope.$watch('hash', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                Core.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();
            reloadPerspective();
        });

        // use includePerspective = false as default as that was the previous behavior
        $scope.link = function (nav, includePerspective) {
            if (typeof includePerspective === "undefined") { includePerspective = false; }
            var href;
            if (angular.isString(nav)) {
                href = nav;
            } else {
                href = nav.href();
            }
            var removeParams = ['tab', 'nid', 'chapter', 'pref', 'q'];
            if (!includePerspective) {
                if (href.indexOf("?p=") >= 0 || href.indexOf("&p=") >= 0) {
                    removeParams.push("p");
                }
            }
            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);
            }
            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);
            });
            return tab ? tab['content'] : "";
        };

        function reloadPerspective() {
            var perspectives = Perspective.getPerspectives($location, workspace, jolokia, localStorage);
            var currentId = Perspective.currentPerspectiveId($location, workspace, jolokia, localStorage);

            console.log("Reloading current perspective: " + currentId);

            // any tabs changed
            var newTopLevelTabs = Perspective.getTopLevelTabsForPerspective($location, workspace, jolokia, localStorage);
            var diff = newTopLevelTabs.subtract($scope.topLevelTabs);

            if (diff && diff.length > 0) {
                $scope.topLevelTabs = newTopLevelTabs;

                $scope.perspectiveId = currentId;
                $scope.perspectives = perspectives;
                $scope.perspectiveDetails.perspective = $scope.perspectives.find(function (p) {
                    return p['id'] === currentId;
                });

                console.log("Refreshing top level tabs for current perspective: " + currentId);

                // make sure to update the UI as the top level tabs changed
                Core.$apply($scope);
            }
        }
    }
    Core.NavBarController = NavBarController;
})(Core || (Core = {}));
var _this = this;
/**
* The main entry point for hawtio
*
* @module Core
* @main Core
*/
var Core;
(function (Core) {
    /**
    * Returns true if we are running inside a Chrome app or extension
    */
    function isChromeApp() {
        var answer = false;
        try  {
            answer = (chrome && chrome.app && chrome.extension) ? true : false;
        } catch (e) {
            answer = false;
        }

        //log.info("isChromeApp is: " + answer);
        return answer;
    }
    Core.isChromeApp = isChromeApp;

    /**
    * Name of plugin registered to hawtio's plugin loader and Angularjs module name
    *
    * @property pluginName
    * @for Core
    * @type String
    */
    Core.pluginName = 'hawtioCore';

    Core.templatePath = 'app/core/html/';
})(Core || (Core = {}));

// Add any other known possible jolokia URLs here
var jolokiaUrls = [
    "/jolokia"
];

var jolokiaUrl = getJolokiaUrl();
console.log("jolokiaUrl " + jolokiaUrl);

function getJolokiaUrl() {
    var query = hawtioPluginLoader.parseQueryString();
    var localMode = query['localMode'];
    if (localMode) {
        console.log("local mode so not using jolokia URL");
        jolokiaUrls = [];
        return null;
    }
    var uri = query['url'];
    if (angular.isArray(uri)) {
        uri = uri[0];
    }
    return uri ? decodeURIComponent(uri) : null;
}

if (!jolokiaUrl) {
    jolokiaUrl = jolokiaUrls.find(function (url) {
        var jqxhr = $.ajax(url, {
            async: false,
            username: 'public',
            password: 'biscuit'
        });
        return jqxhr.status === 200 || jqxhr.status === 401 || jqxhr.status === 403;
    });
}

// bootstrap plugin loader
hawtioPluginLoader.addUrl(url("/plugin"));

if (jolokiaUrl) {
    // TODO replace with a jolokia call so we use authentication headers
    //hawtioPluginLoader.addUrl("jolokia:" + jolokiaUrl + ":hawtio:type=plugin,name=*");
}

/*
interface IMyAppScope extends ng.IRootScopeService, ng.IScope {
lineCount: (value:any) => number;
params: ng.IRouteParamsService;
is: (type:any, value:any) => boolean;
empty: (value:any) => boolean;
log: (variable:string) => void;
alert: (text:string) => void;
}
*/
hawtioPluginLoader.addModule(Core.pluginName);

var hawtioCoreModule = angular.module(Core.pluginName, ['bootstrap', 'ngResource', 'ui', 'ui.bootstrap.dialog', 'hawtio-ui']).config(function ($routeProvider, $dialogProvider) {
    $dialogProvider.options({
        backdropFade: true,
        dialogFade: true
    });

    $routeProvider.when('/login', { templateUrl: Core.templatePath + 'login.html' }).when('/welcome', { templateUrl: Core.templatePath + 'welcome.html' }).when('/about', { templateUrl: Core.templatePath + 'about.html' }).when('/help', {
        redirectTo: '/help/index'
    }).when('/help/:topic/', { templateUrl: Core.templatePath + 'help.html' }).when('/help/:topic/:subtopic', { templateUrl: Core.templatePath + 'help.html' }).otherwise({ redirectTo: '/perspective/defaultPage' });
}).constant('layoutTree', Core.templatePath + 'layoutTree.html').constant('layoutFull', Core.templatePath + 'layoutFull.html').service('localStorage', function () {
    return Core.getLocalStorage();
}).factory('pageTitle', function () {
    var answer = new Core.PageTitle();
    return answer;
}).factory('viewRegistry', function () {
    return {};
}).factory('lastLocation', function () {
    return {};
}).factory('postLoginTasks', function () {
    return Core.postLoginTasks;
}).factory('preLogoutTasks', function () {
    return Core.preLogoutTasks;
}).factory('helpRegistry', function ($rootScope) {
    return new Core.HelpRegistry($rootScope);
}).factory('preferencesRegistry', function () {
    return new Core.PreferencesRegistry();
}).factory('jolokiaUrl', function () {
    return jolokiaUrl;
}).factory('jolokiaStatus', function () {
    return {
        xhr: null
    };
}).factory('jolokiaParams', function (jolokiaUrl) {
    return {
        url: jolokiaUrl,
        canonicalNaming: false,
        ignoreErrors: true,
        mimeType: 'application/json'
    };
}).factory('branding', function () {
    var branding = Themes.brandings['hawtio'].setFunc({});
    branding.logoClass = function () {
        if (branding.logoOnly) {
            return "without-text";
        } else {
            return "with-text";
        }
    };
    return branding;
}).factory('userDetails', function (jolokiaUrl, localStorage) {
    var answer = angular.fromJson(localStorage[jolokiaUrl]);
    if (!angular.isDefined(answer) && jolokiaUrl) {
        answer = {
            username: '',
            password: ''
        };

        Core.log.debug("No username set, checking if we have a session");

        // fetch the username if we've already got a session at the server
        var userUrl = jolokiaUrl.replace("jolokia", "user");
        $.ajax(userUrl, {
            type: "GET",
            success: function (response) {
                Core.log.debug("Got user response: ", response);
                Core.executePostLoginTasks();
                /*
                // We'll only touch these if they're not set
                if (response !== '' && response !== null) {
                answer.username = response;
                if (!('loginDetails' in answer)) {
                answer['loginDetails'] = {};
                }
                }
                */
            },
            error: function (xhr, textStatus, error) {
                Core.log.debug("Failed to get session username: ", error);
                Core.executePostLoginTasks();
                // silently ignore, we could be using the proxy
            }
        });

        return answer;
    } else {
        return answer;
    }
}).factory('jolokia', function ($location, localStorage, jolokiaStatus, $rootScope, userDetails, jolokiaParams) {
    // TODO - Maybe have separate URLs or even jolokia instances for loading plugins vs. application stuff
    // var jolokiaUrl = $location.search()['url'] || url("/jolokia");
    console.log("Jolokia URL is " + jolokiaUrl);
    if (jolokiaUrl) {
        var credentials = hawtioPluginLoader.getCredentials(jolokiaUrl);

        // pass basic auth credentials down to jolokia if set
        var username = null;
        var password = null;

        //var userDetails = angular.fromJson(localStorage[jolokiaUrl]);
        if (credentials.length === 2) {
            username = credentials[0];
            password = credentials[1];
            // TODO we should try avoid both permutations of username / userName :)
        } else if (angular.isDefined(userDetails) && angular.isDefined(userDetails.username) && angular.isDefined(userDetails.password)) {
            username = userDetails.username;
            password = userDetails.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 = hawtioPluginLoader.parseQueryString();
            username = search["_user"];
            password = search["_pwd"];
            if (angular.isArray(username))
                username = username[0];
            if (angular.isArray(password))
                password = password[0];
        }

        if (username && password) {
            /*
            TODO can't use this, sets the username/password in the URL on every request, plus jolokia passes them on to $.ajax() which causes a fatal exception in firefox
            jolokiaParams['username'] = username;
            jolokiaParams['password'] = password;
            */
            //console.log("Using user / pwd " + username + " / " + password);
            userDetails.username = username;
            userDetails.password = password;

            $.ajaxSetup({
                beforeSend: function (xhr) {
                    xhr.setRequestHeader('Authorization', Core.getBasicAuthHeader(userDetails.username, userDetails.password));
                }
            });

            var loginUrl = jolokiaUrl.replace("jolokia", "auth/login/");
            $.ajax(loginUrl, {
                type: "POST",
                success: function (response) {
                    if (response['credentials'] || response['principals']) {
                        userDetails.loginDetails = {
                            'credentials': response['credentials'],
                            'principals': response['principals']
                        };
                    } else {
                        var doc = Core.pathGet(response, ['children', 0, 'innerHTML']);

                        // hmm, maybe we got an XML document, let's log it just in case...
                        if (doc) {
                            Core.log.debug("Response is a document (ignoring this): ", doc);
                        }
                    }
                    Core.executePostLoginTasks();
                },
                error: function (xhr, textStatus, error) {
                    // silently ignore, we could be using the proxy
                    Core.executePostLoginTasks();
                }
            });
        }

        jolokiaParams['ajaxError'] = function (xhr, textStatus, error) {
            if (xhr.status === 401 || xhr.status === 403) {
                userDetails.username = null;
                userDetails.password = null;
            } else {
                jolokiaStatus.xhr = xhr;
                if (!xhr.responseText && error) {
                    xhr.responseText = error.stack;
                }
            }
            Core.$apply($rootScope);
        };

        var jolokia = new Jolokia(jolokiaParams);
        localStorage['url'] = jolokiaUrl;
        jolokia.stop();
        return jolokia;
    } else {
        // empty jolokia that returns nothing
        return {
            request: function () {
                return null;
            },
            register: function () {
                return null;
            },
            list: function () {
                return null;
            },
            search: function () {
                return null;
            },
            read: function () {
                return null;
            },
            execute: function () {
                return null;
            },
            start: function () {
                _this.running = true;
                return null;
            },
            stop: function () {
                _this.running = false;
                return null;
            },
            isRunning: function () {
                return _this.running;
            },
            jobs: function () {
                return [];
            }
        };
    }
}).factory('toastr', function () {
    var win = window;
    var answer = win.toastr;
    if (!answer) {
        // lets avoid any NPEs
        answer = {};
        win.toaster = answer;
    }
    return answer;
}).factory('xml2json', function ($window) {
    var jquery = $;
    return jquery.xml2json;
}).factory('workspace', function ($location, jmxTreeLazyLoadRegistry, $compile, $templateCache, localStorage, jolokia, jolokiaStatus, $rootScope, userDetails) {
    var answer = new Workspace(jolokia, jolokiaStatus, jmxTreeLazyLoadRegistry, $location, $compile, $templateCache, localStorage, $rootScope, userDetails);
    answer.loadTree();
    return answer;
}).filter("valueToHtml", function () {
    return Core.valueToHtml;
}).filter('humanize', function () {
    return humanizeValue;
}).filter('humanizeMs', function () {
    return Core.humanizeMilliseconds;
}).filter('maskPassword', function () {
    return Core.maskPassword;
}).directive('autofill', [
    '$timeout', function ($timeout) {
        return {
            restrict: "A",
            require: 'ngModel',
            link: function (scope, elem, attrs, ctrl) {
                var ngModel = attrs["ngModel"];
                if (ngModel) {
                    var log = Logger.get("Core");

                    function checkForDifference() {
                        // lets compare the current DOM node value with the model
                        // in case we can default it ourselves
                        var modelValue = scope.$eval(ngModel);
                        var value = elem.val();
                        if (value && !modelValue) {
                            Core.pathSet(scope, ngModel, value);
                            //log.info("autofill: Updated ngModel: " + ngModel + " original model value: " + modelValue + " UI value: " + value + " new value: " + scope.$eval(ngModel));
                        } else {
                            //log.info("Got invoked with ngModel: " + ngModel + " modelValue: " + modelValue + " value: " + value);
                            // lets try trigger input/change events just in case
                            // try both approaches just in case one doesn't work ;)
                            elem.trigger('input');
                            elem.trigger('change');
                            if (elem.length) {
                                var firstElem = $(elem[0]);
                                firstElem.trigger('input');
                                firstElem.trigger('change');
                            }
                        }
                    }

                    $timeout(checkForDifference, 200);
                    $timeout(checkForDifference, 800);
                    $timeout(checkForDifference, 1500);
                }
            }
        };
    }]).run(function ($rootScope, $routeParams, jolokia, workspace, localStorage, viewRegistry, layoutFull, helpRegistry, pageTitle, branding, toastr, userDetails, preferencesRegistry, postLoginTasks, preLogoutTasks) {
    postLoginTasks.addTask("ResetPreLogoutTasks", function () {
        preLogoutTasks.reset();
    });

    preLogoutTasks.addTask("ResetPostLoginTasks", function () {
        postLoginTasks.reset();
    });

    $.support.cors = true;

    /*
    * Count the number of lines in the given text
    */
    $rootScope.lineCount = lineCount;

    /*
    * Easy access to route params
    */
    $rootScope.params = $routeParams;

    /*
    * Wrapper for angular.isArray, isObject, etc checks for use in the view
    *
    * @param type {string} the name of the check (casing sensitive)
    * @param value {string} value to check
    */
    $rootScope.is = function (type, value) {
        return angular['is' + type](value);
    };

    /*
    * Wrapper for $.isEmptyObject()
    *
    * @param value  {mixed} Value to be tested
    * @return booleanean
    */
    $rootScope.empty = function (value) {
        return $.isEmptyObject(value);
    };

    /*
    * Initialize jolokia polling and add handler to change poll
    * frequency
    */
    $rootScope.$on('UpdateRate', function (event, rate) {
        jolokia.stop();
        if (rate > 0) {
            jolokia.start(rate);
        }
        Core.log.debug("Set update rate to: ", rate);
    });

    $rootScope.$emit('UpdateRate', localStorage['updateRate']);

    /*
    * Debugging Tools
    *
    * Allows you to execute debug functions from the view
    */
    // TODO Doesn't support vargs like it should
    $rootScope.log = function (variable) {
        console.log(variable);
    };
    $rootScope.alert = function (text) {
        alert(text);
    };

    viewRegistry['fullscreen'] = layoutFull;
    viewRegistry['notree'] = layoutFull;
    viewRegistry['help'] = layoutFull;
    viewRegistry['welcome'] = layoutFull;
    viewRegistry['preferences'] = layoutFull;
    viewRegistry['about'] = layoutFull;
    viewRegistry['login'] = layoutFull;
    viewRegistry['ui'] = layoutFull;

    helpRegistry.addUserDoc('index', 'app/core/doc/overview.md');
    helpRegistry.addUserDoc('preferences', 'app/core/doc/preferences.md');
    helpRegistry.addSubTopic('index', 'faq', 'app/core/doc/FAQ.md');
    helpRegistry.addSubTopic('index', 'changes', 'app/core/doc/CHANGES.md');
    helpRegistry.addSubTopic('index', 'developer', 'app/core/doc/developer.md');
    helpRegistry.addDevDoc('Core', 'app/core/doc/coreDeveloper.md');
    helpRegistry.addDevDoc('UI', '#/ui/developerPage');
    helpRegistry.addDevDoc('datatable', 'app/datatable/doc/developer.md');
    helpRegistry.addDevDoc('Force Graph', 'app/forcegraph/doc/developer.md');

    preferencesRegistry.addTab("Core", "app/core/html/corePreferences.html");
    preferencesRegistry.addTab("Plugins", "app/core/html/pluginPreferences.html");
    preferencesRegistry.addTab("Console Logging", "app/core/html/loggingPreferences.html");
    preferencesRegistry.addTab("Editor", "app/ui/html/editorPreferences.html");
    preferencesRegistry.addTab("Reset", "app/core/html/resetPreferences.html");

    //helpRegistry.discoverHelpFiles(hawtioPluginLoader.getModules());
    var opts = localStorage['CodeMirrorOptions'];
    if (opts) {
        opts = angular.fromJson(opts);
        CodeEditor.GlobalCodeMirrorOptions = angular.extend(CodeEditor.GlobalCodeMirrorOptions, opts);
    }

    toastr.options = {
        'closeButton': true,
        'showMethod': 'slideDown',
        'hideMethod': 'slideUp'
    };

    window['logInterceptors'].push(function (level, message) {
        if (level === "WARN") {
            notification('warning', message);
        }
        if (level === "ERROR") {
            notification('error', message);
        }
    });

    setTimeout(function () {
        $("#main-body").fadeIn(2000).after(function () {
            Core.log.info(branding.appName + " started");
            Core.$apply($rootScope);
            $(window).trigger('resize');
        });
    }, 500);
}).directive('noClick', function () {
    return function ($scope, $element, $attrs) {
        $element.click(function (event) {
            event.preventDefault();
        });
    };
}).directive('gridStyle', function ($window) {
    return new Core.GridStyle($window);
}).directive('logToggler', function (localStorage) {
    return {
        restrict: 'A',
        link: function ($scope, $element, $attr) {
            $element.click(function () {
                var log = $("#log-panel");
                var body = $('body');
                if (log.height() !== 0) {
                    localStorage['showLog'] = 'false';
                    log.css({ 'bottom': '110%' });
                    body.css({
                        'overflow-y': 'auto'
                    });
                } else {
                    localStorage['showLog'] = 'true';
                    log.css({ 'bottom': '50%' });
                    body.css({
                        'overflow-y': 'hidden'
                    });
                }
                return false;
            });
        }
    };
}).directive('hawtioFileUpload', function () {
    return new Core.FileUpload();
});

String.prototype.unescapeHTML = function () {
    var txt = document.createElement("textarea");
    txt.innerHTML = this;
    return txt.value;
};

// for chrome packaged apps lets enable chrome-extension pages
if (hawtioCoreModule && Core.isChromeApp()) {
    hawtioCoreModule.config([
        '$compileProvider',
        function ($compileProvider) {
            //$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/);
            $compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/);
            // Angular before v1.2 uses $compileProvider.urlSanitizationWhitelist(...)
        }
    ]);
}

// enable bootstrap tooltips
$(function () {
    $("a[title]").tooltip({
        selector: '',
        delay: { show: 1000, hide: 100 }
    });
});

var adjustHeight = function () {
    var windowHeight = $(window).height();
    var headerHeight = $("#main-nav").height();
    var containerHeight = windowHeight - headerHeight;
    $("#main").css("min-height", "" + containerHeight + "px");
};

$(function () {
    hawtioPluginLoader.loadPlugins(function () {
        var doc = $(document);
        angular.bootstrap(doc, hawtioPluginLoader.getModules());
        $(document.documentElement).attr('xmlns:ng', "http://angularjs.org");
        $(document.documentElement).attr('ng-app', 'hawtioCore');
        adjustHeight();
        $(window).resize(adjustHeight);
    });
});
/**
* @module Core
*/
var Core;
(function (Core) {
    Core.username = null;
    Core.password = null;

    /**
    * Controller that handles the login page and actually logging in
    *
    * @method LoginController
    * @for Core
    * @static
    * @param $scope
    * @param jolokia
    * @param userDetails
    * @param jolokiaUrl
    * @param workspace
    * @param localStorage
    * @param branding
    */
    function LoginController($scope, jolokia, userDetails, jolokiaUrl, workspace, localStorage, branding, postLoginTasks) {
        jolokia.stop();

        $scope.entity = {
            username: '',
            password: ''
        };
        $scope.backstretch = $.backstretch(branding.loginBg);

        $scope.rememberMe = false;
        $scope.branding = branding;

        var details = angular.fromJson(localStorage[jolokiaUrl]);
        if (details) {
            $scope.entity.username = details['username'];
            $scope.entity.password = details['password'];
            $scope.rememberMe = details['rememberMe'];
        }

        $scope.$on('$routeChangeStart', function () {
            if ($scope.backstretch) {
                $scope.backstretch.destroy();
            }
        });

        jQuery(window).bind("beforeunload", function () {
            // auto logout if we should not remember me
            if (!userDetails.rememberMe) {
                console.log("Auto logging out as remember me is off");
                Core.logout(jolokiaUrl, userDetails, localStorage, $scope);
            }
        });

        $scope.doLogin = function () {
            if (jolokiaUrl) {
                var url = jolokiaUrl.replace("jolokia", "auth/login/");

                if ($scope.entity.username.trim() != '') {
                    $.ajax(url, {
                        type: "POST",
                        success: function (response) {
                            userDetails.username = $scope.entity.username;
                            userDetails.password = $scope.entity.password;
                            userDetails.rememberMe = $scope.rememberMe;
                            userDetails.loginDetails = response;

                            Core.username = $scope.entity.username;
                            Core.password = $scope.entity.password;
                            if ($scope.rememberMe) {
                                localStorage[jolokiaUrl] = angular.toJson(userDetails);
                            } else {
                                delete localStorage[jolokiaUrl];
                            }

                            jolokia.start();
                            workspace.loadTree();
                            Core.executePostLoginTasks();
                            Core.$apply($scope);
                        },
                        error: function (xhr, textStatus, error) {
                            switch (xhr.status) {
                                case 401:
                                    Core.notification('error', 'Failed to log in, ' + error);
                                    break;
                                case 403:
                                    Core.notification('error', 'Failed to log in, ' + error);
                                    break;
                                default:
                                    Core.notification('error', 'Failed to log in, ' + error);
                                    break;
                            }
                            Core.$apply($scope);
                        },
                        beforeSend: function (xhr) {
                            xhr.setRequestHeader('Authorization', Core.getBasicAuthHeader($scope.entity.username, $scope.entity.password));
                        }
                    });
                }
            }
        };
    }
    Core.LoginController = LoginController;
})(Core || (Core = {}));
/**
* module Core
*/
var Core;
(function (Core) {
    function PluginPreferences($scope, localStorage, $location, workspace, jolokia) {
        Core.initPreferenceScope($scope, localStorage, {
            'autoRefresh': {
                'value': true,
                'converter': Core.parseBooleanValue
            }
        });

        $scope.perspectiveId;
        $scope.perspectives = [];

        $scope.plugins = [];
        $scope.pluginDirty = false;

        $scope.pluginMoveUp = function (index) {
            $scope.pluginDirty = true;
            var tmp = $scope.plugins[index];
            $scope.plugins[index] = $scope.plugins[index - 1];
            $scope.plugins[index - 1] = tmp;
        };

        $scope.pluginMoveDown = function (index) {
            $scope.pluginDirty = true;
            var tmp = $scope.plugins[index];
            $scope.plugins[index] = $scope.plugins[index + 1];
            $scope.plugins[index + 1] = tmp;
        };

        $scope.pluginDisable = function (index) {
            $scope.pluginDirty = true;
            $scope.plugins[index].enabled = false;
            $scope.plugins[index].isDefault = false;
        };

        $scope.pluginEnable = function (index) {
            $scope.pluginDirty = true;
            $scope.plugins[index].enabled = true;
        };

        $scope.pluginDefault = function (index) {
            $scope.pluginDirty = true;
            $scope.plugins.forEach(function (p) {
                p.isDefault = false;
            });
            $scope.plugins[index].isDefault = true;
        };

        $scope.pluginApply = function () {
            $scope.pluginDirty = false;

            // set index before saving
            $scope.plugins.forEach(function (p, idx) {
                p.index = idx;
            });

            var json = angular.toJson($scope.plugins);
            if (json) {
                Core.log.debug("Saving plugin settings for perspective " + $scope.perspectiveId + " -> " + json);
                var id = "plugins-" + $scope.perspectiveId;
                localStorage[id] = json;
            }

            // force UI to update by reloading the page which works
            setTimeout(function () {
                window.location.reload();
            }, 10);
        };

        $scope.$watch('perspectiveId', function (newValue, oldValue) {
            if (newValue === oldValue) {
                return;
            }

            var perspective = Perspective.getPerspectiveById(newValue);
            if (perspective) {
                updateToPerspective(perspective);
                Core.$apply($scope);
            }
        });

        function updateToPerspective(perspective) {
            var plugins = Core.configuredPluginsForPerspectiveId(perspective.id, workspace, jolokia, localStorage);
            $scope.plugins = plugins;
            $scope.perspectiveId = perspective.id;
            Core.log.debug("Updated to perspective " + $scope.perspectiveId + " with " + plugins.length + " plugins");
        }

        // initialize the controller, and pick the 1st perspective
        $scope.perspectives = Perspective.getPerspectives($location, workspace, jolokia, localStorage);
        Core.log.debug("There are " + $scope.perspectives.length + " perspectives");

        // pick the current selected perspective
        var selectPerspective;
        var perspectiveId = Perspective.currentPerspectiveId($location, workspace, jolokia, localStorage);
        if (perspectiveId) {
            selectPerspective = $scope.perspectives.find(function (p) {
                return p.id === perspectiveId;
            });
        }
        if (!selectPerspective) {
            // just pick the 1st then
            selectPerspective = $scope.perspectives[0];
        }

        updateToPerspective(selectPerspective);

        // and force update the ui
        Core.$apply($scope);
    }
    Core.PluginPreferences = PluginPreferences;
    ;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function WelcomeController($scope, $location, branding, localStorage) {
        var log = Logger.get("Welcome");

        $scope.stopShowingWelcomePage = function () {
            log.debug("Stop showing welcome page");
            localStorage['showWelcomePage'] = false;

            $location.path("/");
        };

        // load the welcome.md file
        $.ajax({
            url: "app/core/doc/welcome.md",
            dataType: 'html',
            cache: false,
            success: function (data, textStatus, jqXHR) {
                $scope.html = "Unable to download welcome.md";
                if (angular.isDefined(data)) {
                    $scope.html = marked(data);
                    $scope.branding = branding;
                }
                Core.$apply($scope);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                $scope.html = "Unable to download welcome.md";
                Core.$apply($scope);
            }
        });
    }
    Core.WelcomeController = WelcomeController;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    

    /**
    * @class Folder
    * @uses NodeSelection
    */
    var Folder = (function () {
        function Folder(title) {
            this.title = title;
            this.key = null;
            this.typeName = null;
            this.children = [];
            this.folderNames = [];
            this.domain = null;
            this.objectName = null;
            this.map = {};
            this.entries = {};
            this.addClass = null;
            this.parent = null;
            this.isLazy = false;
            this.icon = null;
            this.tooltip = null;
            this.entity = null;
            this.addClass = escapeTreeCssStyles(title);
        }
        Folder.prototype.get = function (key) {
            return this.map[key];
        };

        Folder.prototype.isFolder = function () {
            return this.children.length > 0;
        };

        /**
        * Navigates the given paths and returns the value there or null if no value could be found
        * @method navigate
        * @for Folder
        * @param {Array} paths
        * @return {NodeSelection}
        */
        Folder.prototype.navigate = function () {
            var paths = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                paths[_i] = arguments[_i + 0];
            }
            var node = this;
            paths.forEach(function (path) {
                if (node) {
                    node = node.get(path);
                }
            });
            return node;
        };

        Folder.prototype.hasEntry = function (key, value) {
            var entries = this.entries;
            if (entries) {
                var actual = entries[key];
                return actual && value === actual;
            }
            return false;
        };

        Folder.prototype.parentHasEntry = function (key, value) {
            if (this.parent) {
                return this.parent.hasEntry(key, value);
            }
            return false;
        };

        Folder.prototype.ancestorHasEntry = function (key, value) {
            var parent = this.parent;
            while (parent) {
                if (parent.hasEntry(key, value))
                    return true;
                parent = parent.parent;
            }
            return false;
        };

        Folder.prototype.ancestorHasType = function (typeName) {
            var parent = this.parent;
            while (parent) {
                if (typeName === parent.typeName)
                    return true;
                parent = parent.parent;
            }
            return false;
        };

        Folder.prototype.getOrElse = function (key, defaultValue) {
            if (typeof defaultValue === "undefined") { defaultValue = new Folder(key); }
            var answer = this.map[key];
            if (!answer) {
                answer = defaultValue;
                this.map[key] = answer;
                this.children.push(answer);
                answer.parent = this;
                this.children = this.children.sortBy("title");
            }
            return answer;
        };

        Folder.prototype.sortChildren = function (recursive) {
            var children = this.children;
            if (children) {
                this.children = children.sortBy("title");
                if (recursive) {
                    angular.forEach(children, function (child) {
                        return child.sortChildren(recursive);
                    });
                }
            }
        };

        Folder.prototype.moveChild = function (child) {
            if (child && child.parent !== this) {
                child.detach();
                child.parent = this;
                this.children.push(child);
            }
        };

        /**
        * Removes this node from my parent if I have one
        * @method detach
        * @for Folder
        \   */
        Folder.prototype.detach = function () {
            var oldParent = this.parent;
            if (oldParent) {
                var oldParentChildren = oldParent.children;
                if (oldParentChildren) {
                    var idx = oldParentChildren.indexOf(this);
                    if (idx < 0) {
                        oldParent.children = oldParent.children.remove({ key: this.key });
                    } else {
                        oldParentChildren.splice(idx, 1);
                    }
                }
                this.parent = null;
            }
        };

        /**
        * Searches this folder and all its descendants for the first folder to match the filter
        * @method findDescendant
        * @for Folder
        * @param {Function} filter
        * @return {Folder}
        */
        Folder.prototype.findDescendant = function (filter) {
            if (filter(this)) {
                return this;
            }
            var answer = null;
            angular.forEach(this.children, function (child) {
                if (!answer) {
                    answer = child.findDescendant(filter);
                }
            });
            return answer;
        };
        return Folder;
    })();
    Core.Folder = Folder;
})(Core || (Core = {}));

;
var Folder = (function (_super) {
    __extends(Folder, _super);
    function Folder() {
        _super.apply(this, arguments);
    }
    return Folder;
})(Core.Folder);
;
/**
* @module Core
*/
var Core;
(function (Core) {
    function AboutController($scope, $location, jolokia, branding, localStorage) {
        var log = Logger.get("About");

        // load the about.md file
        $.ajax({
            url: "app/core/doc/about.md",
            dataType: 'html',
            cache: false,
            success: function (data, textStatus, jqXHR) {
                $scope.html = "Unable to download about.md";
                if (angular.isDefined(data)) {
                    $scope.html = marked(data);
                    $scope.branding = branding;
                    $scope.customBranding = branding.enabled;
                    try  {
                        $scope.hawtioVersion = jolokia.request({
                            type: "read",
                            mbean: "hawtio:type=About",
                            attribute: "HawtioVersion"
                        }).value;
                    } catch (Error) {
                        // ignore
                        $scope.hawtioVersion = "N/A";
                    }
                    $scope.jolokiaVersion = jolokia.version().agent;
                    $scope.serverProduct = jolokia.version().info.product;
                    $scope.serverVendor = jolokia.version().info.vendor;
                    $scope.serverVersion = jolokia.version().info.version;
                }
                Core.$apply($scope);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                $scope.html = "Unable to download about.md";
                Core.$apply($scope);
            }
        });
    }
    Core.AboutController = AboutController;
})(Core || (Core = {}));
// TODO Get these functions and variables out of the global namespace
var _urlPrefix = null;

var numberTypeNames = {
    'byte': true,
    'short': true,
    'int': true,
    'long': true,
    'float': true,
    'double': true,
    'java.lang.byte': true,
    'java.lang.short': true,
    'java.lang.integer': true,
    'java.lang.long': true,
    'java.lang.float': true,
    'java.lang.double': true
};

/**
* Returns the number of lines in the given text
*
* @method lineCount
* @static
* @param {String} value
* @return {Number}
*
*/
function lineCount(value) {
    var rows = 0;
    if (value) {
        rows = 1;
        value.toString().each(/\n/, function () {
            return rows++;
        });
    }
    return rows;
}

function url(path) {
    if (path) {
        if (path.startsWith && path.startsWith("/")) {
            if (!_urlPrefix) {
                _urlPrefix = window.location.pathname || "";
                var idx = _urlPrefix.lastIndexOf("/");
                if (idx >= 0) {
                    _urlPrefix = _urlPrefix.substring(0, idx);
                }
            }
            if (_urlPrefix) {
                return _urlPrefix + path;
            }
        }
    }
    return path;
}

function safeNull(value) {
    if (typeof value === 'boolean') {
        return value;
    } else if (typeof value === 'number') {
        // return numbers as-is
        return value;
    }
    if (value) {
        return value;
    } else {
        return "";
    }
}

function safeNullAsString(value, type) {
    if (typeof value === 'boolean') {
        return "" + value;
    } else if (typeof value === 'number') {
        // return numbers as-is
        return "" + value;
    } else if (typeof value === 'string') {
        // its a string
        return "" + value;
    } else if (type === 'javax.management.openmbean.CompositeData' || type === '[Ljavax.management.openmbean.CompositeData;') {
        // composite data or composite data array, we just display as json
        // use json representation
        var data = angular.toJson(value, true);
        return data;
    } else if (type === 'javax.management.openmbean.TabularData') {
        // tabular data is a key/value structure so loop each field and convert to array we can
        // turn into a String
        var arr = [];
        for (var key in value) {
            var val = value[key];
            var line = "" + key + "=" + val;
            arr.push(line);
        }

        // sort array so the values is listed nicely
        arr = arr.sortBy(function (row) {
            return row.toString();
        });
        return arr.join("\n");
    } else if (angular.isArray(value)) {
        // join array with new line, and do not sort as the order in the array may matter
        return value.join("\n");
    } else if (value) {
        // force as string
        return "" + value;
    } else {
        return "";
    }
}

/**
* Converts the given value to an array of query arguments.
*
* If the value is null an empty array is returned.
* If the value is a non empty string then the string is split by commas
*
* @method toSearchArgumentArray
* @static
* @param {*} value
* @return {String[]}
*
*/
function toSearchArgumentArray(value) {
    if (value) {
        if (angular.isArray(value))
            return value;
        if (angular.isString(value))
            return value.split(',');
    }
    return [];
}

function folderMatchesPatterns(node, patterns) {
    if (node) {
        var folderNames = node.folderNames;
        if (folderNames) {
            return patterns.any(function (ignorePaths) {
                for (var i = 0; i < ignorePaths.length; i++) {
                    var folderName = folderNames[i];
                    var ignorePath = ignorePaths[i];
                    if (!folderName)
                        return false;
                    var idx = ignorePath.indexOf(folderName);
                    if (idx < 0) {
                        return false;
                    }
                }
                return true;
            });
        }
    }
    return false;
}

function scopeStoreJolokiaHandle($scope, jolokia, jolokiaHandle) {
    // TODO do we even need to store the jolokiaHandle in the scope?
    if (jolokiaHandle) {
        $scope.$on('$destroy', function () {
            closeHandle($scope, jolokia);
        });
        $scope.jolokiaHandle = jolokiaHandle;
    }
}

function closeHandle($scope, jolokia) {
    var jolokiaHandle = $scope.jolokiaHandle;
    if (jolokiaHandle) {
        //console.log('Closing the handle ' + jolokiaHandle);
        jolokia.unregister(jolokiaHandle);
        $scope.jolokiaHandle = null;
    }
}

/**
* Pass in null for the success function to switch to sync mode
*
* @method onSuccess
* @static
* @param {Function} Success callback function
* @param {Object} Options object to pass on to Jolokia request
* @return {Object} initialized options object
*/
function onSuccess(fn, options) {
    if (typeof options === "undefined") { options = {}; }
    options['mimeType'] = 'application/json';
    if (angular.isDefined(fn)) {
        options['success'] = fn;
    }
    if (!options['method']) {
        options['method'] = "POST";
    }
    options['canonicalNaming'] = false;
    options['canonicalProperties'] = false;
    if (!options['error']) {
        options['error'] = function (response) {
            Core.defaultJolokiaErrorHandler(response, options);
        };
    }
    return options;
}

function supportsLocalStorage() {
    try  {
        return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
    }
}

function isNumberTypeName(typeName) {
    if (typeName) {
        var text = typeName.toString().toLowerCase();
        var flag = numberTypeNames[text];
        return flag;
    }
    return false;
}

function encodeMBeanPath(mbean) {
    return mbean.replace(/\//g, '!/').replace(':', '/').escapeURL();
}

function escapeMBeanPath(mbean) {
    return mbean.replace(/\//g, '!/').replace(':', '/');
}

function encodeMBean(mbean) {
    return mbean.replace(/\//g, '!/').escapeURL();
}

function escapeDots(text) {
    return text.replace(/\./g, '-');
}

/**
* Escapes all dots and 'span' text in the css style names to avoid clashing with bootstrap stuff
*
* @method escapeTreeCssStyles
* @static
* @param {String} text
* @return {String}
*/
function escapeTreeCssStyles(text) {
    return escapeDots(text).replace(/span/g, 'sp-an');
}

function showLogPanel() {
    var log = $("#log-panel");
    var body = $('body');
    localStorage['showLog'] = 'true';
    log.css({ 'bottom': '50%' });
    body.css({
        'overflow-y': 'hidden'
    });
}

/**
* Returns the CSS class for a log level based on if its info, warn, error etc.
*
* @method logLevelClass
* @static
* @param {String} level
* @return {String}
*/
function logLevelClass(level) {
    if (level) {
        var first = level[0];
        if (first === 'w' || first === "W") {
            return "warning";
        } else if (first === 'e' || first === "E") {
            return "error";
        } else if (first === 'i' || first === "I") {
            return "info";
        } else if (first === 'd' || first === "D") {
            // we have no debug css style
            return "";
        }
    }
    return "";
}

/**
* @module Core
*/
var Core;
(function (Core) {
    function parseMBean(mbean) {
        var answer = {};
        var parts = mbean.split(":");
        if (parts.length > 1) {
            answer['domain'] = parts.first();
            parts = parts.exclude(parts.first());
            parts = parts.join(":");
            answer['attributes'] = {};
            var nameValues = parts.split(",");
            nameValues.forEach(function (str) {
                var nameValue = str.split('=');
                var name = nameValue.first().trim();
                nameValue = nameValue.exclude(nameValue.first());
                answer['attributes'][name] = nameValue.join('=').trim();
            });
        }
        return answer;
    }
    Core.parseMBean = parseMBean;

    function executePostLoginTasks() {
        Core.log.debug("Executing post login tasks");
        Core.postLoginTasks.execute();
    }
    Core.executePostLoginTasks = executePostLoginTasks;

    function executePreLogoutTasks(onComplete) {
        Core.log.debug("Executing pre logout tasks");
        Core.preLogoutTasks.onComplete(onComplete);
        Core.preLogoutTasks.execute();
    }
    Core.executePreLogoutTasks = executePreLogoutTasks;

    /**
    * log out the current user
    * @for Core
    * @static
    * @method logout
    * @param {String} jolokiaUrl
    * @param {*} userDetails
    * @param {Object} localStorage
    * @param {Object} $scope
    * @param {Function} successCB
    * @param {Function} errorCB
    *
    */
    function logout(jolokiaUrl, userDetails, localStorage, $scope, successCB, errorCB) {
        if (typeof successCB === "undefined") { successCB = null; }
        if (typeof errorCB === "undefined") { errorCB = null; }
        if (jolokiaUrl) {
            var url = jolokiaUrl.replace("jolokia", "auth/logout/");

            Core.executePreLogoutTasks(function () {
                $.ajax(url, {
                    type: "POST",
                    success: function () {
                        userDetails.username = null;
                        userDetails.password = null;
                        userDetails.loginDetails = null;
                        userDetails.rememberMe = false;
                        localStorage[jolokiaUrl] = angular.toJson(userDetails);
                        if (successCB && angular.isFunction(successCB)) {
                            successCB();
                        }
                        Core.$apply($scope);
                    },
                    error: function (xhr, textStatus, error) {
                        switch (xhr.status) {
                            case 401:
                                Core.log.error('Failed to log out, ', error);
                                break;
                            case 403:
                                Core.log.error('Failed to log out, ', error);
                                break;
                            default:
                                Core.log.error('Failed to log out, ', error);
                                break;
                        }
                        if (errorCB && angular.isFunction(errorCB)) {
                            errorCB();
                        }
                        Core.$apply($scope);
                    }
                });
            });
        }
    }
    Core.logout = logout;

    Core.log = Logger.get("Core");

    /**
    * Creates a link by appending the current $location.search() hash to the given href link,
    * removing any required parameters from the link
    * @method createHref
    * @for Core
    * @static
    * @param {ng.ILocationService} $location
    * @param {String} href the link to have any $location.search() hash parameters appended
    * @param {Array} removeParams any parameters to be removed from the $location.search()
    * @return {Object} the link with any $location.search() parameters added
    */
    function createHref($location, href, removeParams) {
        if (typeof removeParams === "undefined") { removeParams = null; }
        var hashMap = angular.copy($location.search());

        // lets remove any top level nav bar related hash searches
        if (removeParams) {
            angular.forEach(removeParams, function (param) {
                return delete hashMap[param];
            });
        }
        var hash = Core.hashToString(hashMap);
        if (hash) {
            var prefix = (href.indexOf("?") >= 0) ? "&" : "?";
            href += prefix + hash;
        }
        return href;
    }
    Core.createHref = createHref;

    /**
    * Trims the leading prefix from a string if its present
    * @method trimLeading
    * @for Core
    * @static
    * @param {String} text
    * @param {String} prefix
    * @return {String}
    */
    function trimLeading(text, prefix) {
        if (text && prefix) {
            if (text.startsWith(prefix)) {
                return text.substring(prefix.length);
            }
        }
        return text;
    }
    Core.trimLeading = trimLeading;

    /**
    * Trims the trailing postfix from a string if its present
    * @method trimTrailing
    * @for Core
    * @static
    * @param {String} trim
    * @param {String} postfix
    * @return {String}
    */
    function trimTrailing(text, postfix) {
        if (text && postfix) {
            if (text.endsWith(postfix)) {
                return text.substring(0, text.length - postfix.length);
            }
        }
        return text;
    }
    Core.trimTrailing = trimTrailing;

    /**
    * Turns the given search hash into a URI style query string
    * @method hashToString
    * @for Core
    * @static
    * @param {Object} hash
    * @return {String}
    */
    function hashToString(hash) {
        var keyValuePairs = [];
        angular.forEach(hash, function (value, key) {
            keyValuePairs.push(key + "=" + value);
        });
        var params = keyValuePairs.join("&");
        return encodeURI(params);
    }
    Core.hashToString = hashToString;

    /**
    * Parses the given string of x=y&bar=foo into a hash
    * @method stringToHash
    * @for Core
    * @static
    * @param {String} hashAsString
    * @return {Object}
    */
    function stringToHash(hashAsString) {
        var entries = {};
        if (hashAsString) {
            var text = decodeURI(hashAsString);
            var items = text.split('&');
            angular.forEach(items, function (item) {
                var kv = item.split('=');
                var key = kv[0];
                var value = kv[1] || key;
                entries[key] = value;
            });
        }
        return entries;
    }
    Core.stringToHash = stringToHash;

    /**
    * Register a JMX operation to poll for changes
    * @method register
    * @for Core
    * @static
    * @return {Function} a zero argument function for unregistering  this registration
    * @param {*} jolokia
    * @param {*} scope
    * @param {Object} arguments
    * @param {Function} callback
    */
    function register(jolokia, scope, arguments, callback) {
        if (scope && !Core.isBlank(scope.name)) {
            Core.log.debug("Calling register from scope: ", scope.name);
        } else {
            Core.log.debug("Calling register from anonymous scope");
        }
        if (!angular.isDefined(scope.$jhandle) || !angular.isArray(scope.$jhandle)) {
            scope.$jhandle = [];
        }
        if (angular.isDefined(scope.$on)) {
            scope.$on('$destroy', function (event) {
                unregister(jolokia, scope);
            });
        }

        var handle = null;

        if (angular.isArray(arguments)) {
            if (arguments.length >= 1) {
                // TODO can't get this to compile in typescript :)
                //var args = [callback].concat(arguments);
                var args = [callback];
                angular.forEach(arguments, function (value) {
                    return args.push(value);
                });

                //var args = [callback];
                //args.push(arguments);
                var registerFn = jolokia.register;
                handle = registerFn.apply(jolokia, args);
                scope.$jhandle.push(handle);
                jolokia.request(arguments, callback);
            }
        } else {
            handle = jolokia.register(callback, arguments);
            scope.$jhandle.push(handle);
            jolokia.request(arguments, callback);
        }
        return function () {
            if (handle !== null) {
                scope.$jhandle.remove(handle);
                jolokia.unregister(handle);
            }
        };
    }
    Core.register = register;

    /**
    * Register a JMX operation to poll for changes using a jolokia search using the given mbean pattern
    * @method registerSearch
    * @for Core
    * @static
    * @paran {*} jolokia
    * @param {*} scope
    * @param {String} mbeanPattern
    * @param {Function} callback
    */
    function registerSearch(jolokia, scope, mbeanPattern, callback) {
        if (!angular.isDefined(scope.$jhandle) || !angular.isArray(scope.$jhandle)) {
            scope.$jhandle = [];
        }
        if (angular.isDefined(scope.$on)) {
            scope.$on('$destroy', function (event) {
                unregister(jolokia, scope);
            });
        }
        if (angular.isArray(arguments)) {
            if (arguments.length >= 1) {
                // TODO can't get this to compile in typescript :)
                //var args = [callback].concat(arguments);
                var args = [callback];
                angular.forEach(arguments, function (value) {
                    return args.push(value);
                });

                //var args = [callback];
                //args.push(arguments);
                var registerFn = jolokia.register;
                var handle = registerFn.apply(jolokia, args);
                scope.$jhandle.push(handle);
                jolokia.search(mbeanPattern, callback);
            }
        } else {
            var handle = jolokia.register(callback, arguments);
            scope.$jhandle.push(handle);
            jolokia.search(mbeanPattern, callback);
        }
    }
    Core.registerSearch = registerSearch;

    function unregister(jolokia, scope) {
        if (angular.isDefined(scope.$jhandle)) {
            scope.$jhandle.forEach(function (handle) {
                jolokia.unregister(handle);
            });
            delete scope.$jhandle;
        }
    }
    Core.unregister = unregister;

    /**
    * The default error handler which logs errors either using debug or log level logging based on the silent setting
    * @param response the response from a jolokia request
    */
    function defaultJolokiaErrorHandler(response, options) {
        if (typeof options === "undefined") { options = {}; }
        //alert("Jolokia request failed: " + response.error);
        var stacktrace = response.stacktrace;
        if (stacktrace) {
            var silent = options['silent'];
            if (!silent) {
                var operation = Core.pathGet(response, ['request', 'operation']) || "unknown";
                if (stacktrace.indexOf("javax.management.InstanceNotFoundException") >= 0 || stacktrace.indexOf("javax.management.AttributeNotFoundException") >= 0 || stacktrace.indexOf("java.lang.IllegalArgumentException: No operation") >= 0) {
                    // ignore these errors as they can happen on timing issues
                    // such as its been removed
                    // or if we run against older containers
                    Core.log.debug("Operation ", operation, " failed due to: ", response['error']);
                    Core.log.debug("Stack trace: ", Logger.formatStackTraceString(response['stacktrace']));
                } else {
                    Core.log.warn("Operation ", operation, " failed due to: ", response['error']);
                    Core.log.info("Stack trace: ", Logger.formatStackTraceString(response['stacktrace']));
                }
            } else {
                Core.log.debug("Operation ", operation, " failed due to: ", response['error']);
                Core.log.debug("Stack trace: ", Logger.formatStackTraceString(response['stacktrace']));
            }
        }
    }
    Core.defaultJolokiaErrorHandler = defaultJolokiaErrorHandler;

    /**
    * Logs any failed operation and stack traces
    */
    function logJolokiaStackTrace(response) {
        var stacktrace = response.stacktrace;
        if (stacktrace) {
            var operation = Core.pathGet(response, ['request', 'operation']) || "unknown";
            Core.log.info("Operation ", operation, " failed due to: ", response['error']);
            Core.log.info("Stack trace: ", Logger.formatStackTraceString(response['stacktrace']));
        }
    }
    Core.logJolokiaStackTrace = logJolokiaStackTrace;

    /**
    * Converts the given XML node to a string representation of the XML
    * @method xmlNodeToString
    * @for Core
    * @static
    * @param {Object} xmlNode
    * @return {Object}
    */
    function xmlNodeToString(xmlNode) {
        try  {
            // Gecko- and Webkit-based browsers (Firefox, Chrome), Opera.
            return (new XMLSerializer()).serializeToString(xmlNode);
        } catch (e) {
            try  {
                // Internet Explorer.
                return xmlNode.xml;
            } catch (e) {
                //Other browsers without XML Serializer
                console.log('WARNING: XMLSerializer not supported');
            }
        }
        return false;
    }
    Core.xmlNodeToString = xmlNodeToString;

    /**
    * Returns true if the given DOM node is a text node
    * @method isTextNode
    * @for Core
    * @static
    * @param {Object} node
    * @return {Boolean}
    */
    function isTextNode(node) {
        return node && node.nodeType === 3;
    }
    Core.isTextNode = isTextNode;

    /**
    * Returns the lowercase file extension of the given file name or returns the empty
    * string if the file does not have an extension
    * @method fileExtension
    * @for Core
    * @static
    * @param {String} name
    * @param {String} defaultValue
    * @return {String}
    */
    function fileExtension(name, defaultValue) {
        if (typeof defaultValue === "undefined") { defaultValue = ""; }
        var extension = defaultValue;
        if (name) {
            var idx = name.lastIndexOf(".");
            if (idx > 0) {
                extension = name.substring(idx + 1, name.length).toLowerCase();
            }
        }
        return extension;
    }
    Core.fileExtension = fileExtension;

    function getUUID() {
        var d = new Date();
        var ms = (d.getTime() * 1000) + d.getUTCMilliseconds();
        var random = Math.floor((1 + Math.random()) * 0x10000);
        return ms.toString(16) + random.toString(16);
    }
    Core.getUUID = getUUID;

    var _versionRegex = /[^\d]*(\d+)\.(\d+)(\.(\d+))?.*/;

    /**
    * Parses some text of the form "xxxx2.3.4xxxx"
    * to extract the version numbers as an array of numbers then returns an array of 2 or 3 numbers.
    *
    * Characters before the first digit are ignored as are characters after the last digit.
    * @method parseVersionNumbers
    * @for Core
    * @static
    * @param {String} text a maven like string containing a dash then numbers separated by dots
    * @return {Array}
    */
    function parseVersionNumbers(text) {
        if (text) {
            var m = text.match(_versionRegex);
            if (m && m.length > 4) {
                var m1 = m[1];
                var m2 = m[2];
                var m4 = m[4];
                if (angular.isDefined(m4)) {
                    return [parseInt(m1), parseInt(m2), parseInt(m4)];
                } else if (angular.isDefined(m2)) {
                    return [parseInt(m1), parseInt(m2)];
                } else if (angular.isDefined(m1)) {
                    return [parseInt(m1)];
                }
            }
        }
        return null;
    }
    Core.parseVersionNumbers = parseVersionNumbers;

    /**
    * Converts a version string with numbers and dots of the form "123.456.790" into a string
    * which is sortable as a string, by left padding each string between the dots to at least 4 characters
    * so things just sort as a string.
    *
    * @param text
    * @return {string} the sortable version string
    */
    function versionToSortableString(version, maxDigitsBetweenDots) {
        if (typeof maxDigitsBetweenDots === "undefined") { maxDigitsBetweenDots = 4; }
        return (version || "").split(".").map(function (x) {
            var length = x.length;
            return (length >= maxDigitsBetweenDots) ? x : x.padLeft(' ', maxDigitsBetweenDots - length);
        }).join(".");
    }
    Core.versionToSortableString = versionToSortableString;

    function time(message, fn) {
        var start = new Date().getTime();
        var answer = fn();
        var elapsed = new Date().getTime() - start;
        console.log(message + " " + elapsed);
        return answer;
    }
    Core.time = time;

    /**
    * Compares the 2 version arrays and returns -1 if v1 is less than v2 or 0 if they are equal or 1 if v1 is greater than v2
    * @method compareVersionNumberArrays
    * @for Core
    * @static
    * @param {Array} v1 an array of version numbers with the most significant version first (major, minor, patch).
    * @param {Array} v2
    * @return {Number}
    */
    function compareVersionNumberArrays(v1, v2) {
        if (v1 && !v2) {
            return 1;
        }
        if (!v1 && v2) {
            return -1;
        }
        if (v1 === v2) {
            return 0;
        }
        for (var i = 0; i < v1.length; i++) {
            var n1 = v1[i];
            if (i >= v2.length) {
                return 1;
            }
            var n2 = v2[i];
            if (!angular.isDefined(n1)) {
                return -1;
            }
            if (!angular.isDefined(n2)) {
                return 1;
            }
            if (n1 > n2) {
                return 1;
            } else if (n1 < n2) {
                return -1;
            }
        }
        return 0;
    }
    Core.compareVersionNumberArrays = compareVersionNumberArrays;

    /**
    * Helper function which converts objects into tables of key/value properties and
    * lists into a <ul> for each value.
    * @method valueToHtml
    * @for Core
    * @static
    * @param {any} value
    * @return {String}
    */
    function valueToHtml(value) {
        if (angular.isArray(value)) {
            var size = value.length;
            if (!size) {
                return "";
            } else if (size === 1) {
                return valueToHtml(value[0]);
            } else {
                var buffer = "<ul>";
                angular.forEach(value, function (childValue) {
                    buffer += "<li>" + valueToHtml(childValue) + "</li>";
                });
                return buffer + "</ul>";
            }
        } else if (angular.isObject(value)) {
            var buffer = "<table>";
            angular.forEach(value, function (childValue, key) {
                buffer += "<tr><td>" + key + "</td><td>" + valueToHtml(childValue) + "</td></tr>";
            });
            return buffer + "</table>";
        } else if (angular.isString(value)) {
            var uriPrefixes = ["http://", "https://", "file://", "mailto:"];
            var answer = value;
            angular.forEach(uriPrefixes, function (prefix) {
                if (answer.startsWith(prefix)) {
                    answer = "<a href='" + value + "'>" + value + "</a>";
                }
            });
            return answer;
        }
        return value;
    }
    Core.valueToHtml = valueToHtml;

    /**
    * If the string starts and ends with [] {} then try parse as JSON and return the parsed content or return null
    * if it does not appear to be JSON
    * @method tryParseJson
    * @for Core
    * @static
    * @param {String} text
    * @return {Object}
    */
    function tryParseJson(text) {
        text = text.trim();
        if ((text.startsWith("[") && text.endsWith("]")) || (text.startsWith("{") && text.endsWith("}"))) {
            try  {
                return JSON.parse(text);
            } catch (e) {
                // ignore
            }
        }
        return null;
    }
    Core.tryParseJson = tryParseJson;

    /**
    * Given values (n, "person") will return either "1 person" or "2 people" depending on if a plural
    * is required using the String.pluralize() function from sugarjs
    * @method maybePlural
    * @for Core
    * @static
    * @param {Number} count
    * @param {String} word
    * @return {String}
    */
    function maybePlural(count, word) {
        var pluralWord = (count === 1) ? word : word.pluralize();
        return "" + count + " " + pluralWord;
    }
    Core.maybePlural = maybePlural;

    /**
    * given a JMX ObjectName of the form <code>domain:key=value,another=something</code> then return the object
    * <code>{key: "value", another: "something"}</code>
    * @method objectNameProperties
    * @for Core
    * @static
    * @param {String} name
    * @return {Object}
    */
    function objectNameProperties(objectName) {
        var entries = {};
        if (objectName) {
            var idx = objectName.indexOf(":");
            if (idx > 0) {
                var path = objectName.substring(idx + 1);
                var items = path.split(',');
                angular.forEach(items, function (item) {
                    var kv = item.split('=');
                    var key = kv[0];
                    var value = kv[1] || key;
                    entries[key] = value;
                });
            }
        }
        return entries;
    }
    Core.objectNameProperties = objectNameProperties;

    function setPageTitle($document, title) {
        $document.attr('title', title.getTitleWithSeparator(' '));
    }
    Core.setPageTitle = setPageTitle;

    function setPageTitleWithTab($document, title, tab) {
        $document.attr('title', title.getTitleWithSeparator(' ') + " " + tab);
    }
    Core.setPageTitleWithTab = setPageTitleWithTab;

    /**
    * Returns the Folder object for the given domain name and type name or null if it can not be found
    * @method getMBeanTypeFolder
    * @for Core
    * @static
    * @param {Workspace} workspace
    * @param {String} domain
    * @param {String} typeName}
    * @return {Folder}
    */
    function getMBeanTypeFolder(workspace, domain, typeName) {
        if (workspace) {
            var mbeanTypesToDomain = workspace.mbeanTypesToDomain || {};
            var types = mbeanTypesToDomain[typeName] || {};
            var answer = types[domain];
            if (angular.isArray(answer) && answer.length) {
                return answer[0];
            }
            return answer;
        }
        return null;
    }
    Core.getMBeanTypeFolder = getMBeanTypeFolder;

    /**
    * Returns the JMX objectName for the given jmx domain and type name
    * @method getMBeanTypeObjectName
    * @for Core
    * @static
    * @param {Workspace} workspace
    * @param {String} domain
    * @param {String} typeName
    * @return {String}
    */
    function getMBeanTypeObjectName(workspace, domain, typeName) {
        var folder = Core.getMBeanTypeFolder(workspace, domain, typeName);
        return Core.pathGet(folder, ["objectName"]);
    }
    Core.getMBeanTypeObjectName = getMBeanTypeObjectName;

    /**
    * Removes dodgy characters from a value such as '/' or '.' so that it can be used as a DOM ID value
    * and used in jQuery / CSS selectors
    * @method toSafeDomID
    * @for Core
    * @static
    * @param {String} text
    * @return {String}
    */
    function toSafeDomID(text) {
        return text ? text.replace(/(\/|\.)/g, "_") : text;
    }
    Core.toSafeDomID = toSafeDomID;

    /**
    * Invokes the given function on each leaf node in the array of folders
    * @method forEachLeafFolder
    * @for Core
    * @static
    * @param {Array[Folder]} folders
    * @param {Function} fn
    */
    function forEachLeafFolder(folders, fn) {
        angular.forEach(folders, function (folder) {
            var children = folder["children"];
            if (angular.isArray(children) && children.length > 0) {
                forEachLeafFolder(children, fn);
            } else {
                fn(folder);
            }
        });
    }
    Core.forEachLeafFolder = forEachLeafFolder;

    function extractHashURL(url) {
        var parts = url.split('#');
        if (parts.length === 0) {
            return url;
        }
        var answer = parts[1];
        if (parts.length > 1) {
            var remaining = parts.last(parts.length - 2);
            remaining.forEach(function (part) {
                answer = answer + "#" + part;
            });
        }
        return answer;
    }
    Core.extractHashURL = extractHashURL;

    function getBasicAuthHeader(username, password) {
        var authInfo = username + ":" + password;
        authInfo = authInfo.encodeBase64();
        return "Basic " + authInfo;
    }
    Core.getBasicAuthHeader = getBasicAuthHeader;

    var httpRegex = new RegExp('^(https?):\/\/(([^:/?#]*)(?::([0-9]+))?)');

    /**
    * Breaks a URL up into a nice object
    * @method parseUrl
    * @for Core
    * @static
    * @param url
    * @returns object
    */
    function parseUrl(url) {
        if (Core.isBlank(url)) {
            return null;
        }

        var matches = url.match(httpRegex);

        if (matches === null) {
            return null;
        }

        //log.debug("matches: ", matches);
        var scheme = matches[1];
        var host = matches[3];
        var port = matches[4];

        var parts = null;
        if (!Core.isBlank(port)) {
            parts = url.split(port);
        } else {
            parts = url.split(host);
        }

        var path = parts[1];
        if (path && path.startsWith('/')) {
            path = path.slice(1, path.length);
        }

        //log.debug("parts: ", parts);
        return {
            scheme: scheme,
            host: host,
            port: port,
            path: path
        };
    }
    Core.parseUrl = parseUrl;

    var ConnectToServerOptions = (function () {
        function ConnectToServerOptions() {
            this.scheme = "http";
            this.useProxy = true;
        }
        return ConnectToServerOptions;
    })();
    Core.ConnectToServerOptions = ConnectToServerOptions;

    function getDocHeight() {
        var D = document;
        return Math.max(Math.max(D.body.scrollHeight, D.documentElement.scrollHeight), Math.max(D.body.offsetHeight, D.documentElement.offsetHeight), Math.max(D.body.clientHeight, D.documentElement.clientHeight));
    }
    Core.getDocHeight = getDocHeight;

    /**
    * If a URL is external to the current web application, then
    * replace the URL with the proxy servlet URL
    * @method useProxyIfExternal
    * @for Core
    * @static
    * @param {String} connectUrl
    * @return {String}
    */
    function useProxyIfExternal(connectUrl) {
        if (Core.isChromeApp()) {
            return connectUrl;
        }
        var host = window.location.host;
        if (!connectUrl.startsWith("http://" + host + "/") && !connectUrl.startsWith("https://" + host + "/")) {
            // lets remove the http stuff
            var idx = connectUrl.indexOf("://");
            if (idx > 0) {
                connectUrl = connectUrl.substring(idx + 3);
            }

            // lets replace the : with a /
            connectUrl = connectUrl.replace(":", "/");
            connectUrl = Core.trimLeading(connectUrl, "/");
            connectUrl = Core.trimTrailing(connectUrl, "/");
            connectUrl = url("/proxy/" + connectUrl);
        }
        return connectUrl;
    }
    Core.useProxyIfExternal = useProxyIfExternal;

    function getRecentConnections(localStorage) {
        if (Core.isBlank(localStorage['recentConnections'])) {
            Core.clearConnections();
        }
        return angular.fromJson(localStorage['recentConnections']);
    }
    Core.getRecentConnections = getRecentConnections;

    function addRecentConnection(localStorage, name, url) {
        var recent = getRecentConnections(localStorage);
        recent = recent.add({
            'name': name,
            'url': url
        }).unique(function (c) {
            return c.name;
        }).first(5);
        localStorage['recentConnections'] = angular.toJson(recent);
    }
    Core.addRecentConnection = addRecentConnection;

    function removeRecentConnection(localStorage, name) {
        var recent = getRecentConnections(localStorage);
        recent = recent.exclude(function (conn) {
            return conn.name === name;
        });
        localStorage['recentConnections'] = angular.toJson(recent);
    }
    Core.removeRecentConnection = removeRecentConnection;

    function clearConnections() {
        localStorage['recentConnections'] = '[]';
    }
    Core.clearConnections = clearConnections;

    function connectToServer(localStorage, options) {
        Core.log.debug("Connect to server, options: ", options);

        var connectUrl = options.jolokiaUrl;

        var userDetails = {
            username: options['userName'],
            password: options['password']
        };

        var json = angular.toJson(userDetails);
        if (connectUrl) {
            localStorage[connectUrl] = json;
        }
        var view = options.view;
        var full = "";
        var useProxy = options.useProxy && !Core.isChromeApp();
        if (connectUrl) {
            if (useProxy) {
                // lets remove the http stuff
                var idx = connectUrl.indexOf("://");
                if (idx > 0) {
                    connectUrl = connectUrl.substring(idx + 3);
                }

                // lets replace the : with a /
                connectUrl = connectUrl.replace(":", "/");
                connectUrl = Core.trimLeading(connectUrl, "/");
                connectUrl = Core.trimTrailing(connectUrl, "/");
                connectUrl = url("/proxy/" + connectUrl);
            } else {
                if (connectUrl.indexOf("://") < 0) {
                    connectUrl = options.scheme + "://" + connectUrl;
                }
            }
            console.log("going to server: " + connectUrl + " as user " + options.userName);
            localStorage[connectUrl] = json;

            full = "?url=" + encodeURIComponent(connectUrl);
            if (view) {
                full += "#" + view;
            }
        } else {
            var host = options.host || "localhost";
            var port = options.port;
            var path = Core.trimLeading(options.path || "jolokia", "/");
            path = Core.trimTrailing(path, "/");

            if (port > 0) {
                host += ":" + port;
            }
            var connectUrl = host + "/" + path;
            localStorage[connectUrl] = json;
            if (useProxy) {
                connectUrl = url("/proxy/" + connectUrl);
            } else {
                if (connectUrl.indexOf("://") < 0) {
                    connectUrl = options.scheme + "://" + connectUrl;
                }
            }
            console.log("going to server: " + connectUrl + " as user " + options.userName);
            localStorage[connectUrl] = json;

            full = "?url=" + encodeURIComponent(connectUrl);
            if (view) {
                full += "#" + view;
            }
        }
        if (full) {
            Core.log.info("Full URL is: " + full);
            Core.addRecentConnection(localStorage, options.name, full);
            window.open(full);
        }
    }
    Core.connectToServer = connectToServer;

    /**
    * Extracts the url of the target, eg usually http://localhost:port, but if we use fabric to proxy to another host,
    * then we return the url that we proxied too (eg the real target)
    *
    * @param {ng.ILocationService} $location
    * @param {String} scheme to force use a specific scheme, otherwise the scheme from location is used
    * @param {Number} port to force use a specific port number, otherwise the port from location is used
    */
    function extractTargetUrl($location, scheme, port) {
        if (angular.isUndefined(scheme)) {
            scheme = $location.scheme();
        }

        var host = $location.host();

        //  $location.search()['url']; does not work for some strange reason
        // var qUrl = $location.search()['url'];
        // if its a proxy request using hawtio-proxy servlet, then the url parameter
        // has the actual host/port
        var qUrl = $location.absUrl();
        var idx = qUrl.indexOf("url=");
        if (idx > 0) {
            qUrl = qUrl.substr(idx + 4);
            var value = decodeURIComponent(qUrl);
            if (value) {
                idx = value.indexOf("/proxy/");

                // after proxy we have host and optional port (if port is not 80)
                if (idx > 0) {
                    value = value.substr(idx + 7);
                    var data = value.split("/");
                    if (data.length >= 1) {
                        host = data[0];
                    }
                    if (angular.isUndefined(port) && data.length >= 2) {
                        var qPort = Core.parseIntValue(data[1], "port number");
                        if (qPort) {
                            port = qPort;
                        }
                    }
                }
            }
        }

        if (angular.isUndefined(port)) {
            port = $location.port();
        }

        var url = scheme + "://" + host;
        if (port != 80) {
            url += ":" + port;
        }
        return url;
    }
    Core.extractTargetUrl = extractTargetUrl;

    /**
    * Binds a $location.search() property to a model on a scope; so that its initialised correctly on startup
    * and its then watched so as the model changes, the $location.search() is updated to reflect its new value
    * @method bindModelToSearchParam
    * @for Core
    * @static
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {String} modelName
    * @param {String} paramName
    * @param {Object} initialValue
    */
    function bindModelToSearchParam($scope, $location, modelName, paramName, initialValue) {
        if (typeof initialValue === "undefined") { initialValue = null; }
        function currentValue() {
            return $location.search()[paramName] || initialValue;
        }

        var value = currentValue();
        Core.pathSet($scope, modelName, value);
        $scope.$watch(modelName, function () {
            var current = Core.pathGet($scope, modelName);
            if (current) {
                var params = $location.search();
                var old = currentValue();
                if (current !== old) {
                    $location.search(paramName, current);
                }
            } else {
                $location.search(paramName, null);
            }
        });
    }
    Core.bindModelToSearchParam = bindModelToSearchParam;

    /**
    * For controllers where reloading is disabled via "reloadOnSearch: false" on the registration; lets pick which
    * query parameters need to change to force the reload. We default to the JMX selection parameter 'nid'
    * @method reloadWhenParametersChange
    * @for Core
    * @static
    * @param {Object} $route
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {Array[String]} parameters
    */
    function reloadWhenParametersChange($route, $scope, $location, parameters) {
        if (typeof parameters === "undefined") { parameters = ["nid"]; }
        var initial = angular.copy($location.search());
        $scope.$on('$routeUpdate', function () {
            // lets check if any of the parameters changed
            var current = $location.search();
            var changed = [];
            angular.forEach(parameters, function (param) {
                if (current[param] !== initial[param]) {
                    changed.push(param);
                }
            });
            if (changed.length) {
                Core.log.info("Reloading page due to change to parameters: " + changed);
                $route.reload();
            }
        });
    }
    Core.reloadWhenParametersChange = reloadWhenParametersChange;

    /**
    * Creates a jolokia object for connecting to the container with the given remote jolokia URL,
    * username and password
    * @method createJolokia
    * @for Core
    * @static
    * @param {String} url
    * @param {String} username
    * @param {String} password
    * @return {Object}
    */
    function createJolokia(url, username, password) {
        var jolokiaParams = {
            url: url,
            username: username,
            password: password,
            canonicalNaming: false, ignoreErrors: true, mimeType: 'application/json'
        };
        return new Jolokia(jolokiaParams);
    }
    Core.createJolokia = createJolokia;

    /**
    * Returns a new function which ensures that the delegate function is only invoked at most once
    * within the given number of millseconds
    * @method throttled
    * @for Core
    * @static
    * @param {Function} fn the function to be invoked at most once within the given number of millis
    * @param {Number} millis the time window during which this function should only be called at most once
    * @return {Object}
    */
    function throttled(fn, millis) {
        var nextInvokeTime = 0;
        var lastAnswer = null;
        return function () {
            var now = Date.now();
            if (nextInvokeTime < now) {
                nextInvokeTime = now + millis;
                lastAnswer = fn();
            } else {
                Core.log.debug("Not invoking function as we did call " + (now - (nextInvokeTime - millis)) + " ms ago");
            }
            return lastAnswer;
        };
    }
    Core.throttled = throttled;

    /**
    * Attempts to parse the given JSON text and returns the JSON object structure or null.
    *Bad JSON is logged at info level.
    *
    * @param text a JSON formatted string
    * @param message description of the thing being parsed logged if its invalid
    */
    function parseJsonText(text, message) {
        if (typeof message === "undefined") { message = "JSON"; }
        var answer = null;
        try  {
            answer = angular.fromJson(text);
        } catch (e) {
            Core.log.info("Failed to parse " + message + " from: " + text + ". " + e);
        }
        return answer;
    }
    Core.parseJsonText = parseJsonText;

    /**
    * Returns the humanized markup of the given value
    */
    function humanizeValueHtml(value) {
        var formattedValue = "";
        if (value === true) {
            formattedValue = '<i class="icon-check"></i>';
        } else if (value === false) {
            formattedValue = '<i class="icon-check-empty"></i>';
        } else {
            formattedValue = Core.humanizeValue(value);
        }
        return formattedValue;
    }
    Core.humanizeValueHtml = humanizeValueHtml;

    /**
    * Gets a query value from the given url
    *
    * @param url  url
    * @param parameterName the uri parameter value to get
    * @returns {*}
    */
    function getQueryParameterValue(url, parameterName) {
        var parts;

        var query = (url || '').split('?');
        if (query && query.length > 0) {
            parts = query[1];
        } else {
            parts = '';
        }

        var vars = parts.split('&');
        for (var i = 0; i < vars.length; i++) {
            var pair = vars[i].split('=');
            if (decodeURIComponent(pair[0]) == parameterName) {
                return decodeURIComponent(pair[1]);
            }
        }

        // not found
        return null;
    }
    Core.getQueryParameterValue = getQueryParameterValue;

    /**
    * Creates a remote workspace given a remote jolokia for querying the JMX MBeans inside the jolokia
    * @param remoteJolokia
    * @param $location
    * @param localStorage
    * @return {Core.Workspace|Workspace}
    */
    function createRemoteWorkspace(remoteJolokia, $location, localStorage, $rootScope, $compile, $templateCache, userDetails) {
        if (typeof $rootScope === "undefined") { $rootScope = null; }
        if (typeof $compile === "undefined") { $compile = null; }
        if (typeof $templateCache === "undefined") { $templateCache = null; }
        if (typeof userDetails === "undefined") { userDetails = null; }
        // lets create a child workspace object for the remote container
        var jolokiaStatus = {
            xhr: null
        };

        // disable reload notifications
        var jmxTreeLazyLoadRegistry = Jmx.lazyLoaders;
        var profileWorkspace = new Core.Workspace(remoteJolokia, jolokiaStatus, jmxTreeLazyLoadRegistry, $location, $compile, $templateCache, localStorage, $rootScope, userDetails);

        Core.log.info("Loading the profile using jolokia: " + remoteJolokia);
        profileWorkspace.loadTree();
        return profileWorkspace;
    }
    Core.createRemoteWorkspace = createRemoteWorkspace;

    /**
    * Takes a value in ms and returns a human readable
    * duration
    * @param value
    */
    function humanizeMilliseconds(value) {
        if (!angular.isNumber(value)) {
            return "XXX";
        }

        var seconds = value / 1000;
        var years = Math.floor(seconds / 31536000);
        if (years) {
            return maybePlural(years, "year");
        }
        var days = Math.floor((seconds %= 31536000) / 86400);
        if (days) {
            return maybePlural(days, "day");
        }
        var hours = Math.floor((seconds %= 86400) / 3600);
        if (hours) {
            return maybePlural(hours, 'hour');
        }
        var minutes = Math.floor((seconds %= 3600) / 60);
        if (minutes) {
            return maybePlural(minutes, 'minute');
        }
        seconds = Math.floor(seconds % 60);
        if (seconds) {
            return maybePlural(seconds, 'second');
        }
        return value + " ms";
    }
    Core.humanizeMilliseconds = humanizeMilliseconds;

    function storeConnectionRegex(regexs, name, json) {
        if (!regexs.any(function (r) {
            r['name'] === name;
        })) {
            var regex = '';

            if (json['useProxy']) {
                regex = '/hawtio/proxy/';
            } else {
                regex = '//';
            }
            regex += json['host'] + ':' + json['port'] + '/' + json['path'];
            regexs.push({
                name: name,
                regex: regex.escapeURL(true),
                color: UI.colors.sample()
            });
            writeRegexs(regexs);
        }
    }
    Core.storeConnectionRegex = storeConnectionRegex;

    function getRegexs() {
        var regexs = [];
        try  {
            regexs = angular.fromJson(localStorage['regexs']);
        } catch (e) {
            // corrupted config
            delete localStorage['regexs'];
        }
        return regexs;
    }
    Core.getRegexs = getRegexs;

    function removeRegex(name) {
        var regexs = Core.getRegexs();
        var hasFunc = function (r) {
            return r['name'] === name;
        };
        if (regexs.any(hasFunc)) {
            regexs = regexs.exclude(hasFunc);
            Core.writeRegexs(regexs);
        }
    }
    Core.removeRegex = removeRegex;

    function writeRegexs(regexs) {
        localStorage['regexs'] = angular.toJson(regexs);
    }
    Core.writeRegexs = writeRegexs;

    function maskPassword(value) {
        if (value) {
            var text = value.toString();

            // we use the same patterns as in Apache Camel in its
            // org.apache.camel.util.URISupport.sanitizeUri
            var userInfoPattern = "(.*://.*:)(.*)(@)";
            value = value.replace(new RegExp(userInfoPattern, 'i'), "$1xxxxxx$3");
        }

        return value;
    }
    Core.maskPassword = maskPassword;

    /**
    * Match the given filter against the text, ignoring any case.
    * <p/>
    * This operation will regard as a match if either filter or text is null/undefined.
    * As its used for filtering out, unmatched.
    * <p/>
    *
    * @param text   the text
    * @param filter the filter
    * @return true if matched, false if not.
    */
    function matchFilterIgnoreCase(text, filter) {
        if (angular.isUndefined(text) || angular.isUndefined(filter)) {
            return true;
        }

        text = text.toString().trim().toLowerCase();
        filter = filter.toString().trim().toLowerCase();

        if (text.length === 0 || filter.length === 0) {
            return true;
        }

        return text.indexOf(filter) > -1;
    }
    Core.matchFilterIgnoreCase = matchFilterIgnoreCase;
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    /**
    * Controller that's attached to hawtio's drop-down console, mainly handles the
    * clipboard icon at the bottom-right of the console.
    *
    * @method ConsoleController
    * @for Core
    * @static
    * @param {*} $scope
    * @param {*} $element
    * @param {*} $templateCache
    */
    function ConsoleController($scope, $element, $templateCache) {
        $scope.setHandler = function (clip) {
            clip.addEventListener('mouseDown', function (client, args) {
                // this is apparently a global event handler for zero clipboard
                // so you have to make sure you're handling the right click event
                var icon = $element.find('.icon-copy');
                var icon2 = $element.find('.icon-trash');
                if (this !== icon.get(0) && this !== icon2.get(0)) {
                    return;
                }

                if (this == icon.get(0)) {
                    copyToClipboard();
                } else {
                    clearLogs();
                    Core.notification('info', "Cleared logging console23");
                }
                Core.$apply($scope);
            });

            function copyToClipboard() {
                var text = $templateCache.get("logClipboardTemplate").lines();
                text.removeAt(0);
                text.removeAt(text.length - 1);
                $element.find('#log-panel-statements').children().each(function (index, child) {
                    text.push('  <li>' + child.innerHTML + '</li>');
                });
                text.push('</ul>');
                clip.setText(text.join('\n'));
            }

            function clearLogs() {
                $element.find('#log-panel-statements').children().remove();
            }
        };
    }
    Core.ConsoleController = ConsoleController;

    /**
    * Outermost controller attached to almost the root of the document, handles
    * logging in and logging out, the PID/container indicator at the bottom right
    * of the window and the document title
    *
    * @method AppController
    * @for Core
    * @static
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @param {*} jolokiaStatus
    * @param {*} $document
    * @param {Core.PageTitle} pageTitle
    * @param {*} localStorage
    * @param {*} userDetails
    * @param {*} lastLocation
    * @param {*} jolokiaUrl
    * @param {*} branding
    */
    function AppController($scope, $location, workspace, jolokia, jolokiaStatus, $document, pageTitle, localStorage, userDetails, lastLocation, jolokiaUrl, branding) {
        if (!userDetails) {
            userDetails = {};
        }
        if (userDetails.username === null) {
            $location.url(defaultPage());
        }

        $scope.collapse = '';
        $scope.match = null;
        $scope.pageTitle = [];
        $scope.userDetails = userDetails;

        $scope.confirmLogout = false;
        $scope.connectionFailed = false;
        $scope.connectFailure = {};

        $scope.showPrefs = false;

        $scope.logoClass = function () {
            if (branding.logoOnly) {
                return "without-text";
            } else {
                return "with-text";
            }
        };

        setTimeout(function () {
            if ('showPrefs' in localStorage) {
                $scope.showPrefs = Core.parseBooleanValue(localStorage['showPrefs']);
                Core.$apply($scope);
            }
        }, 500);

        $scope.branding = branding;

        $scope.$watch('showPrefs', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                localStorage['showPrefs'] = newValue;
            }
        });

        $scope.hasMBeans = function () {
            return workspace.hasMBeans();
        };

        $scope.$watch('jolokiaStatus.xhr', function () {
            var failure = jolokiaStatus.xhr;
            $scope.connectionFailed = failure ? true : false;
            $scope.connectFailure.summaryMessage = null;
            if ($scope.connectionFailed) {
                $scope.connectFailure.status = failure.status;
                $scope.connectFailure.statusText = failure.statusText;
                var text = failure.responseText;
                if (text) {
                    try  {
                        var html = $(text);
                        var markup = html.find("body");
                        if (markup && markup.length) {
                            html = markup;
                        }

                        // lets tone down the size of the headers
                        html.each(function (idx, e) {
                            var name = e.localName;
                            if (name && name.startsWith("h")) {
                                $(e).addClass("ajaxError");
                            }
                        });
                        var container = $("<div></div>");
                        container.append(html);
                        $scope.connectFailure.summaryMessage = container.html();
                        console.log("Found HTML: " + $scope.connectFailure.summaryMessage);
                    } catch (e) {
                        if (text.indexOf('<') < 0) {
                            // lets assume its not html markup if it doesn't have a tag in it
                            $scope.connectFailure.summaryMessage = "<p>" + text + "</p>";
                        }
                    }
                }
            }
        });

        $scope.showPreferences = function () {
            $scope.showPrefs = true;
        };

        $scope.closePreferences = function () {
            $scope.showPrefs = false;
        };

        $scope.confirmConnectionFailed = function () {
            // I guess we should close the window now?
            window.close();
        };

        $scope.setPageTitle = function () {
            $scope.pageTitle = pageTitle.getTitleArrayExcluding([branding.appName]);
            var tab = workspace.getActiveTab();
            if (tab && tab.content) {
                Core.setPageTitleWithTab($document, pageTitle, tab.content);
            } else {
                Core.setPageTitle($document, pageTitle);
            }
        };

        $scope.setRegexIndicator = function () {
            try  {
                var regexs = angular.fromJson(localStorage['regexs']);
                if (regexs) {
                    regexs.reverse().each(function (regex) {
                        var r = new RegExp(regex.regex, 'g');
                        if (r.test($location.absUrl())) {
                            $scope.match = {
                                name: regex.name,
                                color: regex.color
                            };
                        }
                    });
                }
            } catch (e) {
                // ignore
            }
        };

        $scope.loggedIn = function () {
            return userDetails.username !== null && userDetails.username !== 'public';
        };

        $scope.showLogout = function () {
            return $scope.loggedIn() && angular.isDefined(userDetails.loginDetails);
        };

        $scope.logout = function () {
            $scope.confirmLogout = true;
        };

        $scope.getUsername = function () {
            if (userDetails.username && !userDetails.username.isBlank()) {
                return userDetails.username;
            } else {
                return 'user';
            }
        };

        $scope.doLogout = function () {
            $scope.confirmLogout = false;
            Core.logout(jolokiaUrl, userDetails, localStorage, $scope);
        };

        $scope.$watch(function () {
            return localStorage['regexs'];
        }, $scope.setRegexIndicator);

        $scope.maybeRedirect = function () {
            if (userDetails.username === null) {
                var currentUrl = $location.url();
                if (!currentUrl.startsWith('/login')) {
                    lastLocation.url = currentUrl;
                    $location.url('/login');
                }
            } else {
                if ($location.url().startsWith('/login')) {
                    var url = defaultPage();
                    if (angular.isDefined(lastLocation.url)) {
                        url = lastLocation.url;
                    }
                    $location.url(url);
                }
            }
        };

        $scope.$watch('userDetails', function (newValue, oldValue) {
            $scope.maybeRedirect();
        }, true);

        $scope.$on('hawtioOpenPrefs', function () {
            $scope.showPrefs = true;
        });

        $scope.$on('hawtioClosePrefs', function () {
            $scope.showPrefs = false;
        });

        $scope.$on('$routeChangeStart', function (event, args) {
            if ((!args.params || !args.params.pref) && $scope.showPrefs) {
                $scope.showPrefs = false;
            }
            $scope.maybeRedirect();
        });

        $scope.$on('$routeChangeSuccess', function () {
            $scope.setPageTitle();
            $scope.setRegexIndicator();
        });

        $scope.fullScreen = function () {
            if ($location.url().startsWith("/login")) {
                return branding.fullscreenLogin;
            }
            var tab = $location.search()['tab'];
            if (tab) {
                return tab === "fullscreen";
            }
            return false;
        };

        $scope.login = function () {
            return $location.url().startsWith("/login");
        };

        function defaultPage() {
            return Perspective.defaultPage($location, workspace, jolokia, localStorage);
        }
    }
    Core.AppController = AppController;
})(Core || (Core = {}));
var Core;
(function (Core) {
    var TasksImpl = (function () {
        function TasksImpl() {
            this.tasks = {};
            this.tasksExecuted = false;
            this._onComplete = null;
        }
        TasksImpl.prototype.addTask = function (name, task) {
            this.tasks[name] = task;
            if (this.tasksExecuted) {
                this.executeTask(name, task);
            }
        };

        TasksImpl.prototype.executeTask = function (name, task) {
            if (angular.isFunction(task)) {
                Core.log.debug("Executing task : ", name);
                try  {
                    task();
                } catch (error) {
                    Core.log.debug("Failed to execute task: ", name, " error: ", error);
                }
            }
        };

        TasksImpl.prototype.onComplete = function (cb) {
            this._onComplete = cb;
        };

        TasksImpl.prototype.execute = function () {
            var _this = this;
            if (this.tasksExecuted) {
                return;
            }
            angular.forEach(this.tasks, function (task, name) {
                _this.executeTask(name, task);
            });
            this.tasksExecuted = true;
            if (angular.isFunction(this._onComplete)) {
                this._onComplete();
            }
        };

        TasksImpl.prototype.reset = function () {
            this.tasksExecuted = false;
        };
        return TasksImpl;
    })();
    Core.TasksImpl = TasksImpl;

    Core.postLoginTasks = new Core.TasksImpl();
    Core.preLogoutTasks = new Core.TasksImpl();
})(Core || (Core = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    function ResetPreferences($scope, userDetails, jolokiaUrl, localStorage) {
        $scope.doReset = function () {
            Core.log.info("Resetting");

            var doReset = function () {
                localStorage.clear();
                setTimeout(function () {
                    window.location.reload();
                }, 10);
            };
            if (Core.isBlank(userDetails.username) && Core.isBlank(userDetails.password)) {
                doReset();
            } else {
                Core.logout(jolokiaUrl, userDetails, localStorage, $scope, doReset);
            }
        };
    }
    Core.ResetPreferences = ResetPreferences;
})(Core || (Core = {}));
var Forms;
(function (Forms) {
    var ResetForm = (function () {
        function ResetForm() {
            var _this = this;
            this.restrict = 'A';
            this.scope = true;
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        ResetForm.prototype.doLink = function (scope, element, attrs) {
            var el = $(element);

            var target = 'form[name=' + attrs['hawtioReset'] + ']';

            el.click(function () {
                var forms = $(target);
                for (var i = 0; i < forms.length; i++) {
                    forms[i].reset();
                }
                return false;
            });
        };
        return ResetForm;
    })();
    Forms.ResetForm = ResetForm;
})(Forms || (Forms = {}));
var Forms;
(function (Forms) {
    Forms.pluginName = 'hawtio-forms';
    var log = Logger.get("Forms");

    angular.module(Forms.pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'datatable', 'ui.bootstrap', 'ui.bootstrap.dialog', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/forms/test', { templateUrl: 'app/forms/html/test.html' }).when('/forms/testTable', { templateUrl: 'app/forms/html/testTable.html' });
    }).directive('simpleForm', function (workspace, $compile) {
        return new Forms.SimpleForm(workspace, $compile);
    }).directive('hawtioInputTable', function (workspace, $compile) {
        return new Forms.InputTable(workspace, $compile);
    }).directive('hawtioFormText', function (workspace, $compile) {
        return new Forms.TextInput(workspace, $compile);
    }).directive('hawtioFormPassword', function (workspace, $compile) {
        return new Forms.PasswordInput(workspace, $compile);
    }).directive('hawtioFormHidden', function (workspace, $compile) {
        return new Forms.HiddenText(workspace, $compile);
    }).directive('hawtioFormNumber', function (workspace, $compile) {
        return new Forms.NumberInput(workspace, $compile);
    }).directive('hawtioFormSelect', function (workspace, $compile) {
        return new Forms.SelectInput(workspace, $compile);
    }).directive('hawtioFormArray', function (workspace, $compile) {
        return new Forms.ArrayInput(workspace, $compile);
    }).directive('hawtioFormStringArray', function (workspace, $compile) {
        return new Forms.StringArrayInput(workspace, $compile);
    }).directive('hawtioFormCheckbox', function (workspace, $compile) {
        return new Forms.BooleanInput(workspace, $compile);
    }).directive('hawtioFormCustom', function (workspace, $compile) {
        return new Forms.CustomInput(workspace, $compile);
    }).directive('hawtioSubmit', function () {
        return new Forms.SubmitForm();
    }).directive('hawtioReset', function () {
        return new Forms.ResetForm();
    }).run(function (helpRegistry) {
        helpRegistry.addDevDoc("forms", 'app/forms/doc/developer.md');
    });

    hawtioPluginLoader.addModule(Forms.pluginName);
})(Forms || (Forms = {}));
var Forms;
(function (Forms) {
    var SubmitForm = (function () {
        function SubmitForm() {
            var _this = this;
            this.restrict = 'A';
            this.scope = true;
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        SubmitForm.prototype.doLink = function (scope, element, attrs) {
            var el = $(element);

            var target = 'form[name=' + attrs['hawtioSubmit'] + ']';

            el.click(function () {
                $(target).submit();
                return false;
            });
        };
        return SubmitForm;
    })();
    Forms.SubmitForm = SubmitForm;
})(Forms || (Forms = {}));
/**
* @module Forms
*/
var Forms;
(function (Forms) {
    /**
    * @class InputBaseConfig
    */
    var InputBaseConfig = (function () {
        function InputBaseConfig() {
            this.name = 'input';
            this.type = '';
            this.description = '';
            this._default = '';
            this.scope = null;
            // Can also be 'view'
            this.mode = 'edit';
            // the name of the full schema
            this.schemaName = "schema";
            this.controlgroupclass = 'control-group';
            this.controlclass = 'controls';
            this.labelclass = 'control-label';
            this.showtypes = 'false';
            /**
            * Custom template for custom form controls
            * @property
            * @type String
            */
            this.formtemplate = null;
            /**
            * the name of the attribute in the scope which is the data to be edited
            * @property
            * @type String
            */
            this.entity = 'entity';
            /**
            * the model expression to bind to. If omitted this defaults to entity + "." + name
            * @property
            * @type String
            */
            this.model = undefined;
        }
        InputBaseConfig.prototype.getEntity = function () {
            return this.entity || "entity";
        };

        InputBaseConfig.prototype.getMode = function () {
            return this.mode || "edit";
        };

        InputBaseConfig.prototype.isReadOnly = function () {
            return this.getMode() === "view";
        };
        return InputBaseConfig;
    })();
    Forms.InputBaseConfig = InputBaseConfig;

    var InputBase = (function () {
        function InputBase(workspace, $compile) {
            var _this = this;
            this.workspace = workspace;
            this.$compile = $compile;
            this.restrict = 'A';
            this.scope = true;
            this.replace = false;
            this.transclude = false;
            this.attributeName = '';
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        InputBase.prototype.doLink = function (scope, element, attrs) {
            var config = new InputBaseConfig;
            config = Forms.configure(config, null, attrs);
            config.scope = scope;
            config.schemaName = attrs["schema"] || "schema";

            var id = Forms.safeIdentifier(config.name);
            var group = this.getControlGroup(config, config, id);

            var modelName = config.model;
            if (!angular.isDefined(modelName)) {
                // TODO always use 2 way binding?
                modelName = config.getEntity() + "." + id;
            }

            // allow the prefix to be trimmed from the label
            var defaultLabel = id;
            if ("true" === attrs["ignorePrefixInLabel"]) {
                var idx = id.lastIndexOf('.');
                if (idx > 0) {
                    defaultLabel = id.substring(idx + 1);
                }
            }
            group.append(Forms.getLabel(config, config, attrs["title"] || humanizeValue(defaultLabel)));
            var controlDiv = Forms.getControlDiv(config);
            controlDiv.append(this.getInput(config, config, id, modelName));
            controlDiv.append(Forms.getHelpSpan(config, config, id));
            group.append(controlDiv);
            $(element).append(this.$compile(group)(scope));

            if (scope && modelName) {
                scope.$watch(modelName, onModelChange);
            }
            function onModelChange(newValue) {
                scope.$emit("hawtio.form.modelChange", modelName, newValue);
            }
        };

        InputBase.prototype.getControlGroup = function (config1, config2, id) {
            return Forms.getControlGroup(config1, config2, id);
        };

        InputBase.prototype.getInput = function (config, arg, id, modelName) {
            var rc = $('<span class="form-data"></span>');
            if (modelName) {
                rc.attr('ng-model', modelName);
                rc.append('{{' + modelName + '}}');
            }
            return rc;
        };
        return InputBase;
    })();
    Forms.InputBase = InputBase;

    var TextInput = (function (_super) {
        __extends(TextInput, _super);
        function TextInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
            this.type = "text";
        }
        /*public getControlGroup(config1, config2, id) {
        return super.getControlGroup(config1, config2, id);
        }*/
        TextInput.prototype.getInput = function (config, arg, id, modelName) {
            if (config.isReadOnly()) {
                return _super.prototype.getInput.call(this, config, arg, id, modelName);
            }
            var rc = $('<input type="' + this.type + '">');
            rc.attr('name', id);
            if (modelName) {
                rc.attr('ng-model', modelName);
            }
            if (config.isReadOnly()) {
                rc.attr('readonly', 'true');
            }
            var required = config.$attr["required"];
            if (required && required !== "false") {
                rc.attr('required', 'true');
            }
            return rc;
        };
        return TextInput;
    })(InputBase);
    Forms.TextInput = TextInput;

    var HiddenText = (function (_super) {
        __extends(HiddenText, _super);
        function HiddenText(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
            this.type = "hidden";
        }
        HiddenText.prototype.getControlGroup = function (config1, config2, id) {
            var group = _super.prototype.getControlGroup.call(this, config1, config2, id);
            group.css({ 'display': 'none' });
            return group;
        };

        HiddenText.prototype.getInput = function (config, arg, id, modelName) {
            var rc = _super.prototype.getInput.call(this, config, arg, id, modelName);
            rc.attr('readonly', 'true');
            return rc;
        };
        return HiddenText;
    })(TextInput);
    Forms.HiddenText = HiddenText;

    var PasswordInput = (function (_super) {
        __extends(PasswordInput, _super);
        function PasswordInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
            this.type = "password";
        }
        return PasswordInput;
    })(TextInput);
    Forms.PasswordInput = PasswordInput;

    var CustomInput = (function (_super) {
        __extends(CustomInput, _super);
        function CustomInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        CustomInput.prototype.getInput = function (config, arg, id, modelName) {
            var template = arg.formtemplate;
            template = Core.unescapeHtml(template);
            var rc = $(template);
            if (!rc.attr("name")) {
                rc.attr('name', id);
            }
            if (modelName) {
                rc.attr('ng-model', modelName);
            }
            if (config.isReadOnly()) {
                rc.attr('readonly', 'true');
            }
            return rc;
        };
        return CustomInput;
    })(InputBase);
    Forms.CustomInput = CustomInput;

    var SelectInput = (function (_super) {
        __extends(SelectInput, _super);
        function SelectInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        SelectInput.prototype.getInput = function (config, arg, id, modelName) {
            if (config.isReadOnly()) {
                return _super.prototype.getInput.call(this, config, arg, id, modelName);
            }

            // TODO calculate from input attributes...
            var required = true;

            // TODO we could configure the null option...
            var defaultOption = required ? "" : '<option value=""></option>';
            var rc = $('<select>' + defaultOption + '</select>');
            rc.attr('name', id);

            var scope = config.scope;
            var data = config.data;
            if (data && scope) {
                // this is a big ugly - would be nice to expose this a bit easier...
                // maybe nested objects should expose the model easily...
                var fullSchema = scope[config.schemaName];
                var model = scope[data];

                // now we need to keep walking the model to find the enum values
                var paths = id.split(".");
                var property = null;
                angular.forEach(paths, function (path) {
                    property = Core.pathGet(model, ["properties", path]);
                    var typeName = Core.pathGet(property, ["type"]);
                    var alias = Forms.lookupDefinition(typeName, fullSchema);
                    if (alias) {
                        model = alias;
                    }
                });
                var values = Core.pathGet(property, ["enum"]);
                scope["$selectValues"] = values;
                rc.attr("ng-options", "value for value in $selectValues");
            }
            if (modelName) {
                rc.attr('ng-model', modelName);
            }
            if (config.isReadOnly()) {
                rc.attr('readonly', 'true');
            }
            return rc;
        };
        return SelectInput;
    })(InputBase);
    Forms.SelectInput = SelectInput;

    var NumberInput = (function (_super) {
        __extends(NumberInput, _super);
        function NumberInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        NumberInput.prototype.getInput = function (config, arg, id, modelName) {
            if (config.isReadOnly()) {
                return _super.prototype.getInput.call(this, config, arg, id, modelName);
            }
            var rc = $('<input type="number">');
            rc.attr('name', id);

            if (angular.isDefined(arg.def)) {
                rc.attr('value', arg.def);
            }

            if (angular.isDefined(arg.minimum)) {
                rc.attr('min', arg.minimum);
            }

            if (angular.isDefined(arg.maximum)) {
                rc.attr('max', arg.maximum);
            }

            if (modelName) {
                rc.attr('ng-model', modelName);
            }
            if (config.isReadOnly()) {
                rc.attr('readonly', 'true');
            }

            // lets coerce any string values to numbers so that they work properly with the UI
            var scope = config.scope;
            if (scope) {
                function onModelChange() {
                    var value = Core.pathGet(scope, modelName);
                    if (value && angular.isString(value)) {
                        var numberValue = Number(value);
                        Core.pathSet(scope, modelName, numberValue);
                    }
                }
                scope.$watch(modelName, onModelChange);
                onModelChange();
            }
            return rc;
        };
        return NumberInput;
    })(InputBase);
    Forms.NumberInput = NumberInput;

    /**
    * Generates a list of strings which can be added / edited / removed
    * @class StringArrayInput
    */
    var StringArrayInput = (function (_super) {
        __extends(StringArrayInput, _super);
        function StringArrayInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        StringArrayInput.prototype.getInput = function (config, arg, id, modelName) {
            var rowScopeName = "_" + id;
            var ngRepeat = rowScopeName + ' in ' + modelName;

            var readOnlyWidget = '{{' + rowScopeName + '}}';
            if (config.isReadOnly()) {
                return $('<ul><li ng-repeat="' + rowScopeName + ' in ' + modelName + '">' + readOnlyWidget + '</li></ul>');
            } else {
                // TODO there should be an easier way to find the property / schema!
                var scope = config.scope;
                var fallbackSchemaName = (arg.$attr || {})["schema"] || "schema";
                var schema = scope[config.schemaName] || scope[fallbackSchemaName] || {};
                var properties = schema.properties || {};
                var arrayProperty = properties[id] || {};

                // lets refer to the property of the item, rather than the array
                var property = arrayProperty["items"] || {};
                var propTypeName = property.type;
                var ignorePrefixInLabel = true;
                var configScopeName = null;

                // lets create an empty array if its not yet set
                var value = Core.pathGet(scope, modelName);
                if (!value) {
                    Core.pathSet(scope, modelName, []);
                }

                var methodPrefix = "_form_stringArray" + rowScopeName + "_";
                var itemKeys = methodPrefix + "keys";
                var addMethod = methodPrefix + "add";
                var removeMethod = methodPrefix + "remove";

                // we maintain a separate object of all the keys (indices) of the array
                // and use that to lookup the values
                function updateKeys() {
                    var value = Core.pathGet(scope, modelName);
                    scope[itemKeys] = value ? Object.keys(value) : [];
                    scope.$emit("hawtio.form.modelChange", modelName, value);
                }

                updateKeys();

                scope[addMethod] = function () {
                    var value = Core.pathGet(scope, modelName) || [];
                    value.push("");
                    Core.pathSet(scope, modelName, value);
                    updateKeys();
                };
                scope[removeMethod] = function (idx) {
                    var value = Core.pathGet(scope, modelName) || [];
                    if (idx < value.length) {
                        value.splice(idx, 1);
                    }
                    Core.pathSet(scope, modelName, value);
                    updateKeys();
                };

                // the expression for an item value
                var itemId = modelName + "[" + rowScopeName + "]";
                var itemsConfig = {
                    model: itemId
                };
                var widget = Forms.createWidget(propTypeName, property, schema, itemsConfig, itemId, ignorePrefixInLabel, configScopeName, false);
                if (!widget) {
                    widget = $(readOnlyWidget);
                }
                var markup = $('<div style="white-space: nowrap" ng-repeat="' + rowScopeName + ' in ' + itemKeys + '"></div>');
                markup.append(widget);
                markup.append($('<a ng-click="' + removeMethod + '(' + rowScopeName + ')" title="Remove this value"><i class="red icon-remove"></i></a>'));
                markup.after($('<a ng-click="' + addMethod + '()" title="Add a new value"><i class="icon-plus"></i></a>'));
                return markup;
            }
        };
        return StringArrayInput;
    })(InputBase);
    Forms.StringArrayInput = StringArrayInput;

    var ArrayInput = (function (_super) {
        __extends(ArrayInput, _super);
        function ArrayInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        ArrayInput.prototype.doLink = function (scope, element, attrs) {
            var config = new InputBaseConfig;
            config = Forms.configure(config, null, attrs);

            var id = config.name;
            var dataName = attrs["data"] || "";
            var entityName = attrs["entity"] || config.entity;
            var schemaName = attrs["schema"] || config.schemaName;

            function renderRow(cell, type, data) {
                if (data) {
                    var description = data["description"];
                    if (!description) {
                        angular.forEach(data, function (value, key) {
                            if (value && !description) {
                                description = value;
                            }
                        });
                    }
                    return description;
                }
                return null;
            }

            // Had to fudge some of this
            // create a table UI!
            var tableConfigPaths = ["properties", id, "inputTable"];

            //var scope = config.scope;
            var tableConfig = null;
            Core.pathGet(scope, tableConfigPaths);

            // lets auto-create a default configuration if there is none
            if (!tableConfig) {
                // TODO ideally we should merge this config with whatever folks have hand-defined
                var tableConfigScopeName = tableConfigPaths.join(".");

                //var cellDescription = a["description"] || humanizeValue(id);
                var cellDescription = humanizeValue(id);
                tableConfig = {
                    formConfig: config,
                    title: cellDescription,
                    data: config.entity + "." + id,
                    displayFooter: false,
                    showFilter: false,
                    columnDefs: [
                        {
                            field: '_id',
                            displayName: cellDescription,
                            render: renderRow
                        }
                    ]
                };
                Core.pathSet(scope, tableConfigPaths, tableConfig);
            }
            var table = $('<div hawtio-input-table="' + tableConfigScopeName + '" data="' + dataName + '" property="' + id + '" entity="' + entityName + '" schema="' + schemaName + '"></div>');
            if (config.isReadOnly()) {
                table.attr("readonly", "true");
            }
            $(element).append(this.$compile(table)(scope));
        };
        return ArrayInput;
    })(InputBase);
    Forms.ArrayInput = ArrayInput;

    var BooleanInput = (function (_super) {
        __extends(BooleanInput, _super);
        function BooleanInput(workspace, $compile) {
            _super.call(this, workspace, $compile);
            this.workspace = workspace;
            this.$compile = $compile;
        }
        BooleanInput.prototype.getInput = function (config, arg, id, modelName) {
            var rc = $('<input class="hawtio-checkbox" type="checkbox">');
            rc.attr('name', id);

            if (config.isReadOnly()) {
                rc.attr('disabled', 'true');
            }
            if (modelName) {
                rc.attr('ng-model', modelName);
            }
            if (config.isReadOnly()) {
                rc.attr('readonly', 'true');
            }

            // lets coerce any string values to boolean so that they work properly with the UI
            var scope = config.scope;
            if (scope) {
                function onModelChange() {
                    var value = Core.pathGet(scope, modelName);
                    if (value && "true" === value) {
                        //console.log("coercing String to boolean for " + modelName);
                        Core.pathSet(scope, modelName, true);
                    }
                }
                scope.$watch(modelName, onModelChange);
                onModelChange();
            }
            return rc;
        };
        return BooleanInput;
    })(InputBase);
    Forms.BooleanInput = BooleanInput;
})(Forms || (Forms = {}));
/**
* @module Forms
*/
var Forms;
(function (Forms) {
    Forms.log = Logger.get("Forms");

    /**
    * Default any values in the schema on the entity if they are not already present
    * @method defaultValues
    * @param {any} entity
    * @param {any} schema
    */
    function defaultValues(entity, schema) {
        if (entity && schema) {
            angular.forEach(schema.properties, function (property, key) {
                var defaultValue = property.default;
                if (defaultValue && !entity[key]) {
                    console.log("===== defaulting value " + defaultValue + " into entity[" + key + "]");
                    entity[key] = defaultValue;
                }
            });
        }
    }
    Forms.defaultValues = defaultValues;

    /**
    * If the type name refers to an alias in the schemas definitions then perform the lookup and return the real type name
    * @method resolveTypeNAmeAlias
    * @param {String} type
    * @param {any} schema
    *
    */
    function resolveTypeNameAlias(type, schema) {
        if (type && schema) {
            var alias = lookupDefinition(type, schema);
            if (alias) {
                var realType = alias["type"];
                if (realType) {
                    type = realType;
                }
            }
        }
        return type;
    }
    Forms.resolveTypeNameAlias = resolveTypeNameAlias;

    /**
    * Walks the base class hierarchy checking if the given type is an instance of the given type name
    * @method isJsonType
    * @param {String} name
    * @param {any} schema
    * @param {String} typeName
    * @return {Boolean}
    */
    function isJsonType(name, schema, typeName) {
        var definition = lookupDefinition(name, schema);
        while (definition) {
            var extendsTypes = Core.pathGet(definition, ["extends", "type"]);
            if (extendsTypes) {
                if (typeName === extendsTypes) {
                    return true;
                } else {
                    definition = lookupDefinition(extendsTypes, schema);
                }
            } else {
                return false;
            }
        }
        return false;
    }
    Forms.isJsonType = isJsonType;

    /**
    * Removes any dodgy characters for a valid identifier in angularjs such as for '-' characters
    * which are replaced with '_'
    * @method safeIdentifier
    * @param {String} id
    * @return {String}
    */
    function safeIdentifier(id) {
        if (id) {
            return id.replace(/-/g, "_");
        }
        return id;
    }
    Forms.safeIdentifier = safeIdentifier;

    /**
    * Looks up the given type name in the schemas definitions
    * @method lookupDefinition
    * @param {String} name
    * @param {any} schema
    */
    function lookupDefinition(name, schema) {
        if (schema) {
            var defs = schema.definitions;
            if (defs) {
                var answer = defs[name];
                if (answer) {
                    var fullSchema = answer["fullSchema"];
                    if (fullSchema) {
                        return fullSchema;
                    }

                    // we may extend another, if so we need to copy in the base properties
                    var extendsTypes = Core.pathGet(answer, ["extends", "type"]);
                    if (extendsTypes) {
                        fullSchema = angular.copy(answer);
                        fullSchema.properties = fullSchema.properties || {};
                        if (!angular.isArray(extendsTypes)) {
                            extendsTypes = [extendsTypes];
                        }
                        angular.forEach(extendsTypes, function (extendType) {
                            if (angular.isString(extendType)) {
                                var extendDef = lookupDefinition(extendType, schema);
                                var properties = Core.pathGet(extendDef, ["properties"]);
                                if (properties) {
                                    angular.forEach(properties, function (property, key) {
                                        fullSchema.properties[key] = property;
                                    });
                                }
                            }
                        });
                        answer["fullSchema"] = fullSchema;
                        return fullSchema;
                    }
                }
                return answer;
            }
        }
        return null;
    }
    Forms.lookupDefinition = lookupDefinition;

    /**
    * For an array property, find the schema of the items which is either nested inside this property
    * in the 'items' property; or the type name is used to lookup in the schemas definitions
    * @method findArrayItemsSchema
    * @param {String} property
    * @param {any} schema
    */
    function findArrayItemsSchema(property, schema) {
        var items = null;
        if (property && schema) {
            items = property.items;
            if (items) {
                var typeName = items["type"];
                if (typeName) {
                    var definition = lookupDefinition(typeName, schema);
                    if (definition) {
                        return definition;
                    }
                }
            }

            // are we a json schema properties with a link to the schema doc?
            var additionalProperties = property.additionalProperties;
            if (additionalProperties) {
                if (additionalProperties["$ref"] === "#") {
                    return schema;
                }
            }
        }
        return items;
    }
    Forms.findArrayItemsSchema = findArrayItemsSchema;

    /**
    * Returns true if the given schema definition is an object
    * @method isObjectType
    * @param {any} definition
    */
    function isObjectType(definition) {
        var typeName = Core.pathGet(definition, "type");
        return typeName && "object" === typeName;
    }
    Forms.isObjectType = isObjectType;

    /**
    * Returns true if the given property represents a nested object or array of objects
    * @method isArrayOrNestedObject
    * @param {any} property
    * @param {any} schema
    */
    function isArrayOrNestedObject(property, schema) {
        if (property) {
            var propType = resolveTypeNameAlias(property["type"], schema);
            if (propType) {
                if (propType === "object" || propType === "array") {
                    return true;
                }
            }
        }
        return false;
    }
    Forms.isArrayOrNestedObject = isArrayOrNestedObject;

    function configure(config, scopeConfig, attrs) {
        if (angular.isDefined(scopeConfig)) {
            config = angular.extend(config, scopeConfig);
        }
        return angular.extend(config, attrs);
    }
    Forms.configure = configure;

    function getControlGroup(config, arg, id) {
        var rc = $('<div class="' + config.controlgroupclass + '"></div>');
        if (angular.isDefined(arg.description)) {
            rc.attr('title', arg.description);
        }

        // log.debug("getControlGroup, config:", config, " arg: ", arg, " id: ", id);
        if (config['properties'] && config['properties'][id]) {
            var elementConfig = config['properties'][id];

            // log.debug("elementConfig: ", elementConfig);
            if (elementConfig && 'control-attributes' in elementConfig) {
                angular.forEach(elementConfig['control-attributes'], function (value, key) {
                    rc.attr(key, value);
                });
            }
        }

        return rc;
    }
    Forms.getControlGroup = getControlGroup;

    function getLabel(config, arg, label) {
        return $('<label class="' + config.labelclass + '">' + label + ': </label>');
    }
    Forms.getLabel = getLabel;

    function getControlDiv(config) {
        return $('<div class="' + config.controlclass + '"></div>');
    }
    Forms.getControlDiv = getControlDiv;

    function getHelpSpan(config, arg, id) {
        var help = Core.pathGet(config.data, ['properties', id, 'help']);
        if (!Core.isBlank(help)) {
            return $('<span class="help-block">' + help + '</span>');
        } else {
            return $('<span class="help-block"></span>');
        }
    }
    Forms.getHelpSpan = getHelpSpan;
})(Forms || (Forms = {}));
var Forms;
(function (Forms) {
    var SimpleFormConfig = (function () {
        function SimpleFormConfig() {
            this.name = 'form';
            this.method = 'post';
            // the name of the attribute in the scope which is the data to be editted
            this.entity = 'entity';
            // the name of the full schema
            this.schemaName = 'schema';
            // set to 'view' or 'create' for different modes
            this.mode = 'edit';
            // the definition of the form
            this.data = {};
            this.json = undefined;
            // the scope
            this.scope = null;
            // the name to look up in the scope for the configuration data
            this.scopeName = null;
            this.properties = [];
            this.action = '';
            this.formclass = 'hawtio-form form-horizontal no-bottom-margin';
            this.controlgroupclass = 'control-group';
            this.controlclass = 'controls';
            this.labelclass = 'control-label';
            this.showtypes = 'false';
            this.onsubmit = 'onSubmit';
        }
        SimpleFormConfig.prototype.getMode = function () {
            return this.mode || "edit";
        };

        SimpleFormConfig.prototype.getEntity = function () {
            return this.entity || "entity";
        };

        SimpleFormConfig.prototype.isReadOnly = function () {
            return this.getMode() === "view";
        };
        return SimpleFormConfig;
    })();
    Forms.SimpleFormConfig = SimpleFormConfig;

    var SimpleForm = (function () {
        function SimpleForm(workspace, $compile) {
            var _this = this;
            this.workspace = workspace;
            this.$compile = $compile;
            this.restrict = 'A';
            this.scope = true;
            this.replace = true;
            this.transclude = true;
            this.attributeName = 'simpleForm';
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        SimpleForm.prototype.isReadOnly = function () {
            return false;
        };

        SimpleForm.prototype.doLink = function (scope, element, attrs) {
            var config = new SimpleFormConfig;

            var fullSchemaName = attrs["schema"];
            var fullSchema = fullSchemaName ? scope[fullSchemaName] : null;

            var compiledNode = null;
            var childScope = null;
            var tabs = null;
            var fieldset = null;
            var schema = null;
            var configScopeName = attrs[this.attributeName] || attrs["data"];

            var firstControl = null;

            var simple = this;
            scope.$watch(configScopeName, onWidgetDataChange);

            function onWidgetDataChange(scopeData) {
                if (scopeData) {
                    onScopeData(scopeData);
                }
            }

            function onScopeData(scopeData) {
                config = Forms.configure(config, scopeData, attrs);
                config.schemaName = fullSchemaName;
                config.scopeName = configScopeName;
                config.scope = scope;

                var entityName = config.getEntity();

                if (angular.isDefined(config.json)) {
                    config.data = $.parseJSON(config.json);
                } else {
                    config.data = scopeData;
                }

                var form = simple.createForm(config);
                fieldset = form.find('fieldset');
                schema = config.data;
                tabs = {
                    elements: {},
                    locations: {},
                    use: false
                };

                if (schema && angular.isDefined(schema.tabs)) {
                    tabs.use = true;
                    tabs['div'] = $('<div class="tabbable hawtio-form-tabs"></div>');

                    angular.forEach(schema.tabs, function (value, key) {
                        tabs.elements[key] = $('<div class="tab-pane" title="' + key + '"></div>');
                        tabs['div'].append(tabs.elements[key]);
                        value.forEach(function (val) {
                            tabs.locations[val] = key;
                        });
                    });

                    if (!tabs.locations['*']) {
                        tabs.locations['*'] = Object.extended(schema.tabs).keys()[0];
                    }
                }

                if (!tabs.use) {
                    fieldset.append('<div class="spacer"></div>');
                }

                if (schema) {
                    // if we're using tabs lets reorder the properties...
                    if (tabs.use) {
                        var tabKeyToIdPropObject = {};
                        angular.forEach(schema.properties, function (property, id) {
                            var tabkey = findTabOrderValue(id);
                            var array = tabKeyToIdPropObject[tabkey];
                            if (!array) {
                                array = [];
                                tabKeyToIdPropObject[tabkey] = array;
                            }
                            array.push({ id: id, property: property });
                        });

                        // now lets iterate through each tab...
                        angular.forEach(schema.tabs, function (value, key) {
                            value.forEach(function (val) {
                                var array = tabKeyToIdPropObject[val];
                                if (array) {
                                    angular.forEach(array, function (obj) {
                                        var id = obj.id;
                                        var property = obj.property;
                                        if (id && property) {
                                            addProperty(id, property);
                                        }
                                    });
                                }
                            });
                        });
                    } else {
                        angular.forEach(schema.properties, function (property, id) {
                            addProperty(id, property);
                        });
                    }
                }

                if (tabs.use) {
                    var tabDiv = tabs['div'];
                    var tabCount = Object.keys(tabs.elements).length;
                    if (tabCount < 2) {
                        // if we only have 1 tab lets extract the div contents of the tab
                        angular.forEach(tabDiv.children().children(), function (control) {
                            fieldset.append(control);
                        });
                    } else {
                        fieldset.append(tabDiv);
                    }
                }

                var findFunction = function (scope, func) {
                    if (angular.isDefined(scope[func]) && angular.isFunction(scope[func])) {
                        return scope;
                    }
                    if (angular.isDefined(scope.$parent) && scope.$parent !== null) {
                        return findFunction(scope.$parent, func);
                    } else {
                        return null;
                    }
                };

                var onSubmitFunc = config.onsubmit.replace('(', '').replace(')', '');
                var onSubmit = maybeGet(findFunction(scope, onSubmitFunc), onSubmitFunc);

                if (onSubmit === null) {
                    onSubmit = function (json, form) {
                        notification('error', 'No submit handler defined for form ' + form.get(0).name);
                    };
                }

                if (angular.isDefined(onSubmit)) {
                    form.submit(function () {
                        Forms.log.debug("child scope: ", childScope);
                        Forms.log.debug("form name: ", config);
                        if (childScope[config.name].$invalid) {
                            return false;
                        }
                        var entity = scope[entityName];
                        onSubmit(entity, form);
                        return false;
                    });
                }

                fieldset.append('<input type="submit" style="position: absolute; left: -9999px; width: 1px; height: 1px;">');

                // now lets try default an autofocus element onto the first item if we don't find any elements with an auto-focus
                var autoFocus = form.find("*[autofocus]");
                if (!autoFocus || !autoFocus.length) {
                    if (firstControl) {
                        console.log("No autofocus element, so lets add one!");
                        var input = firstControl.find("input").first() || firstControl.find("select").first();
                        if (input) {
                            input.attr("autofocus", "true");
                        }
                    }
                }

                if (compiledNode) {
                    $(compiledNode).remove();
                }
                if (childScope) {
                    childScope.$destroy();
                }
                childScope = scope.$new(false);

                compiledNode = simple.$compile(form)(childScope);

                // now lets expose the form object to the outer scope
                var formsScopeProperty = "forms";
                var forms = scope[formsScopeProperty];
                if (!forms) {
                    forms = {};
                    scope[formsScopeProperty] = forms;
                }
                var formName = config.name;
                if (formName) {
                    var formObject = childScope[formName];
                    if (formObject) {
                        forms[formName] = formObject;
                    }
                    var formScope = formName += "$scope";
                    forms[formScope] = childScope;
                }
                $(element).append(compiledNode);
            }

            function findTabKey(id) {
                var tabkey = tabs.locations[id];
                if (!tabkey) {
                    // lets try find a tab key using regular expressions
                    angular.forEach(tabs.locations, function (value, key) {
                        if (!tabkey && key !== "*" && id.match(key)) {
                            tabkey = value;
                        }
                    });
                }
                if (!tabkey) {
                    tabkey = tabs.locations['*'];
                }
                return tabkey;
            }

            function findTabOrderValue(id) {
                var answer = null;
                angular.forEach(schema.tabs, function (value, key) {
                    value.forEach(function (val) {
                        if (!answer && val !== "*" && id.match(val)) {
                            answer = val;
                        }
                    });
                });
                if (!answer) {
                    answer = '*';
                }
                return answer;
            }

            function addProperty(id, property, ignorePrefixInLabel) {
                if (typeof ignorePrefixInLabel === "undefined") { ignorePrefixInLabel = property.ignorePrefixInLabel; }
                // TODO should also support getting inputs from the template cache, maybe
                // for type="template"
                var propTypeName = property.type;

                // make sure we detect string as string
                if ("java.lang.String" === propTypeName) {
                    propTypeName = "string";
                }
                var propSchema = Forms.lookupDefinition(propTypeName, schema);
                if (!propSchema) {
                    propSchema = Forms.lookupDefinition(propTypeName, fullSchema);
                }

                // lets ignore fields marked as hidden from the generated form
                if (property.hidden) {
                    return;
                }
                var nestedProperties = null;
                if (!propSchema && "object" === propTypeName && property.properties) {
                    // if we've no type name but have nested properties on an object type use those
                    nestedProperties = property.properties;
                } else if (propSchema && Forms.isObjectType(propSchema)) {
                    // otherwise use the nested properties from the related schema type
                    //console.log("type name " + propTypeName + " has nested object type " + JSON.stringify(propSchema, null, "  "));
                    nestedProperties = propSchema.properties;
                }
                if (nestedProperties) {
                    angular.forEach(nestedProperties, function (childProp, childId) {
                        var newId = id + "." + childId;
                        addProperty(newId, childProp, property.ignorePrefixInLabel);
                    });
                } else {
                    var input = Forms.createWidget(propTypeName, property, schema, config, id, ignorePrefixInLabel, configScopeName);

                    if (tabs.use) {
                        var tabkey = findTabKey(id);
                        tabs.elements[tabkey].append(input);
                    } else {
                        fieldset.append(input);
                    }
                    if (!firstControl) {
                        firstControl = input;
                    }
                }
            }

            function maybeGet(scope, func) {
                if (scope !== null) {
                    return scope[func];
                }
                return null;
            }
        };

        SimpleForm.prototype.createForm = function (config) {
            var form = $('<form class="' + config.formclass + '" novalidate><fieldset></fieldset></form>');
            form.attr('name', config.name);
            form.attr('action', config.action);
            form.attr('method', config.method);
            form.find('fieldset').append(this.getLegend(config));
            return form;
        };

        SimpleForm.prototype.getLegend = function (config) {
            var description = Core.pathGet(config, "data.description");
            if (description) {
                return '<legend>' + description + '</legend>';
            }
            return '';
        };
        return SimpleForm;
    })();
    Forms.SimpleForm = SimpleForm;
})(Forms || (Forms = {}));
var Forms;
(function (Forms) {
    function FormTestController($scope, workspace) {
        $scope.editing = false;

        $scope.html = "text/html";
        $scope.javascript = "javascript";

        $scope.basicFormEx1Entity = {
            'key': 'Some key',
            'value': 'Some value'
        };
        $scope.basicFormEx1EntityString = angular.toJson($scope.basicFormEx1Entity, true);

        $scope.basicFormEx1Result = '';

        $scope.toggleEdit = function () {
            $scope.editing = !$scope.editing;
        };

        $scope.view = function () {
            if (!$scope.editing) {
                return "view";
            }
            return "edit";
        };

        $scope.basicFormEx1 = '<div simple-form name="some-form" action="#/forms/test" method="post" data="basicFormEx1SchemaObject" entity="basicFormEx1Entity" onSubmit="callThis()"></div>';

        $scope.toObject = function (str) {
            return angular.fromJson(str.replace("'", "\""));
        };

        $scope.fromObject = function (str) {
            return angular.toJson($scope[str], true);
        };

        //TODO - I totally did this backwards :-/
        $scope.basicFormEx1Schema = '' + '{\n' + '   "properties": {\n' + '     "key": {\n' + '       "description": "Argument key",\n' + '       "type": "java.lang.String"\n' + '     },\n' + '     "value": {\n' + '       "description": "Argument Value",\n' + '       "type": "java.lang.String"\n' + '     },\n' + '     "longArg": {\n' + '       "description": "Long argument",\n' + '       "type": "Long",\n' + '       "minimum": "5",\n' + '       "maximum": "10"\n' + '     },\n' + '     "intArg": {\n' + '       "description": "Int argument",\n' + '       "type": "Integer"\n' + '     },\n' + '     "objectArg": {\n' + '       "description": "some object",\n' + '       "type": "object"\n' + '     },\n' + '     "booleanArg": {\n' + '       "description": "Some boolean value",\n' + '       "type": "java.lang.Boolean"\n' + '     }\n' + '   },\n' + '   "description": "Show some stuff in a form",\n' + '   "type": "java.lang.String",\n' + '   "tabs": {\n' + '     "Tab One": ["key", "value"],\n' + '     "Tab Two": ["*"],\n' + '     "Tab Three": ["booleanArg"]\n' + '   }\n' + '}';

        $scope.basicFormEx1SchemaObject = $scope.toObject($scope.basicFormEx1Schema);

        $scope.updateSchema = function () {
            $scope.basicFormEx1SchemaObject = $scope.toObject($scope.basicFormEx1Schema);
        };

        $scope.updateEntity = function () {
            $scope.basicFormEx1Entity = angular.fromJson($scope.basicFormEx1EntityString);
        };

        $scope.hawtioResetEx = '<a class="btn" href="" hawtio-reset="some-form"><i class="icon-refresh"></i> Clear</a>';

        $scope.hawtioSubmitEx = '      <a class="btn" href="" hawtio-submit="some-form"><i class="icon-save"></i> Save</a>';

        $scope.callThis = function (json, form) {
            $scope.basicFormEx1Result = angular.toJson(json, true);
            notification('success', 'Form "' + form.get(0).name + '" submitted...');
            Core.$apply($scope);
        };

        $scope.config = {
            name: 'form-with-config-object',
            action: "/some/url",
            method: "post",
            data: 'setVMOption',
            showtypes: 'false'
        };

        $scope.cheese = {
            key: "keyABC",
            value: "valueDEF",
            intArg: 999
        };

        $scope.onCancel = function (form) {
            notification('success', 'Cancel clicked on form "' + form.get(0).name + '"');
        };

        $scope.onSubmit = function (json, form) {
            notification('success', 'Form "' + form.get(0).name + '" submitted... (well not really), data:' + JSON.stringify(json));
        };

        $scope.derp = function (json, form) {
            notification('error', 'derp with json ' + JSON.stringify(json));
        };

        $scope.inputTableSchema = {
            properties: {
                'id': {
                    description: 'Object ID',
                    type: 'java.lang.String'
                }
            },
            description: 'Some objects'
        };

        $scope.inputTableData = [
            { id: "object1", name: 'foo' },
            { id: "object2", name: 'bar' }
        ];

        $scope.inputTableConfig = {
            data: 'inputTableData',
            displayFooter: false,
            showFilter: false,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'ID'
                },
                {
                    field: 'name',
                    displayName: 'Name'
                }
            ]
        };
    }
    Forms.FormTestController = FormTestController;
})(Forms || (Forms = {}));
/**
* @module Forms
*/
var Forms;
(function (Forms) {
    /**
    * Create a DOM widget tree for the given set of form configuration data.
    *
    * This will include either the standard AngularJS widgets or custom widgets
    * @method createWidget
    * @param {String} propTypeName
    * @param {any} property
    * @param {any} schema
    * @param {any} config
    * @param {String} id
    * @param {Boolean ignorePrefixInLabel
    * @param {String} configScoepName
    * @param {Boolean} wrapInGroup
    */
    function createWidget(propTypeName, property, schema, config, id, ignorePrefixInLabel, configScopeName, wrapInGroup) {
        if (typeof wrapInGroup === "undefined") { wrapInGroup = true; }
        var input = null;
        var group = null;

        function copyElementAttributes(element, propertyName) {
            var propertyAttributes = property[propertyName];
            if (propertyAttributes) {
                angular.forEach(propertyAttributes, function (value, key) {
                    if (angular.isString(value)) {
                        element.attr(key, value);
                    }
                });
            }
        }
        function copyAttributes() {
            copyElementAttributes(input, "input-attributes");
            angular.forEach(property, function (value, key) {
                if (angular.isString(value) && key.indexOf("$") < 0 && key !== "type") {
                    var html = Core.escapeHtml(value);
                    input.attr(key, html);
                }
            });
        }

        // lets try to create standard widget markup by default
        // as they work better than the hawtio wrappers when inside forms...
        var options = {
            valueConverter: null
        };
        var safeId = Forms.safeIdentifier(id);

        var inputMarkup = createStandardWidgetMarkup(propTypeName, property, schema, config, options, safeId);

        // Note if for whatever reason we need to go back to the old way of using hawtio directives for standard
        // angularjs directives, just clear inputMarker to null here ;)
        // inputMarkup = null;
        if (inputMarkup) {
            input = $(inputMarkup);

            copyAttributes();

            id = safeId;

            var modelName = config.model || Core.pathGet(property, ["input-attributes", "ng-model"]);
            if (!modelName) {
                modelName = config.getEntity() + "." + id;
            }
            input.attr("ng-model", modelName);

            input.attr('name', id);

            try  {
                if (config.isReadOnly()) {
                    input.attr('readonly', 'true');
                }
            } catch (e) {
                // ignore missing read only function
            }
            var title = property.tooltip || property.label;
            if (title) {
                input.attr('title', title);
            }

            // allow the prefix to be trimmed from the label if enabled
            var defaultLabel = id;
            if (ignorePrefixInLabel || property.ignorePrefixInLabel) {
                var idx = id.lastIndexOf('.');
                if (idx > 0) {
                    defaultLabel = id.substring(idx + 1);
                }
            }

            // figure out which things to not wrap in a group and label etc...
            if (input.attr("type") !== "hidden" && wrapInGroup) {
                group = this.getControlGroup(config, config, id);
                var labelElement = Forms.getLabel(config, config, property.title || property.label || humanizeValue(defaultLabel));
                if (title) {
                    labelElement.attr('title', title);
                }
                group.append(labelElement);
                copyElementAttributes(labelElement, "label-attributes");

                var controlDiv = Forms.getControlDiv(config);
                controlDiv.append(input);
                controlDiv.append(Forms.getHelpSpan(config, config, id));

                group.append(controlDiv);

                // allow control level directives, such as ng-show / ng-hide
                copyElementAttributes(controlDiv, "control-attributes");
                copyElementAttributes(group, "control-group-attributes");

                var scope = config.scope;
                if (scope && modelName) {
                    var onModelChange = function (newValue) {
                        scope.$emit("hawtio.form.modelChange", modelName, newValue);
                    };
                    var fn = onModelChange;

                    // allow custom converters
                    var converterFn = options.valueConverter;
                    if (converterFn) {
                        fn = function () {
                            converterFn(scope, modelName);
                            var newValue = Core.pathGet(scope, modelName);
                            onModelChange(newValue);
                        };
                    }
                    scope.$watch(modelName, fn);
                }
            }
        } else {
            input = $('<div></div>');
            input.attr(Forms.normalize(propTypeName, property, schema), '');

            copyAttributes();

            input.attr('entity', config.getEntity());
            input.attr('mode', config.getMode());

            var fullSchemaName = config.schemaName;
            if (fullSchemaName) {
                input.attr('schema', fullSchemaName);
            }

            if (configScopeName) {
                input.attr('data', configScopeName);
            }

            if (ignorePrefixInLabel || property.ignorePrefixInLabel) {
                input.attr('ignore-prefix-in-label', true);
            }
            input.attr('name', id);
        }

        var label = property.label;
        if (label) {
            input.attr('title', label);
        }

        // TODO check for id in the schema["required"] array too!
        // as required can be specified either via either of these approaches
        /*
        var schema = {
        required: ["foo", "bar"],
        properties: {
        something: {
        required: true,
        type: "string"
        }
        }
        }
        */
        if (property.required) {
            // don't mark checkboxes as required
            if (input[0].localName === "input" && input.attr("type") === "checkbox") {
                // lets not set required on a checkbox, it doesn't make any sense ;)
            } else {
                input.attr('required', 'true');
            }
        }
        return group ? group : input;
    }
    Forms.createWidget = createWidget;

    /**
    * Lets try create the standard angular JS widgets markup
    * @method createStandardWidgetMarkup
    * @param {String} propTypeName
    * @param {any} property
    * @param {any} schema
    * @param {any} config
    * @param {any} options
    * @param {String} id
    */
    function createStandardWidgetMarkup(propTypeName, property, schema, config, options, id) {
        // lets try use standard widgets first...
        var type = Forms.resolveTypeNameAlias(propTypeName, schema);
        if (!type) {
            return '<input type="text"/>';
        }
        var custom = Core.pathGet(property, ["formTemplate"]);
        if (custom) {
            return null;
        }
        var inputElement = Core.pathGet(property, ["input-element"]);
        if (inputElement) {
            return "<" + inputElement + "></" + inputElement + ">";
        }
        var enumValues = Core.pathGet(property, ["enum"]);
        if (enumValues) {
            var required = true;
            var valuesScopeName = null;
            var attributes = "";
            if (enumValues) {
                // calculate from input attributes...
                var scope = config.scope;
                var data = config.data;
                if (data && scope) {
                    // this is a big ugly - would be nice to expose this a bit easier...
                    // maybe nested objects should expose the model easily...
                    var fullSchema = scope[config.schemaName];
                    var model = angular.isString(data) ? scope[data] : data;

                    // now we need to keep walking the model to find the enum values
                    var paths = id.split(".");
                    var property = null;
                    angular.forEach(paths, function (path) {
                        property = Core.pathGet(model, ["properties", path]);
                        var typeName = Core.pathGet(property, ["type"]);
                        var alias = Forms.lookupDefinition(typeName, fullSchema);
                        if (alias) {
                            model = alias;
                        }
                    });
                    var values = Core.pathGet(property, ["enum"]);
                    valuesScopeName = "$values_" + id.replace(/\./g, "_");
                    scope[valuesScopeName] = values;
                }
            }
            if (valuesScopeName) {
                attributes += ' ng-options="value for value in ' + valuesScopeName + '"';
            }
            var defaultOption = required ? "" : '<option value=""></option>';
            return '<select' + attributes + '>' + defaultOption + '</select>';
        }

        if (angular.isArray(type)) {
            // TODO union of tabbed forms such as Marshal / Unmarshal in camel...
            return null;
        }
        if (!angular.isString(type)) {
            return null;
        }
        var defaultValueConverter = null;
        var defaultValue = property.default;
        if (defaultValue) {
            // lets add a default value
            defaultValueConverter = function (scope, modelName) {
                var value = Core.pathGet(scope, modelName);
                if (!value) {
                    Core.pathSet(scope, modelName, property.default);
                }
            };
            options.valueConverter = defaultValueConverter;
        }

        function getModelValueOrDefault(scope, modelName) {
            var value = Core.pathGet(scope, modelName);
            if (!value) {
                var defaultValue = property.default;
                if (defaultValue) {
                    value = defaultValue;
                    Core.pathSet(scope, modelName, value);
                }
            }
            return value;
        }

        switch (type.toLowerCase()) {
            case "int":
            case "integer":
            case "long":
            case "short":
            case "java.lang.integer":
            case "java.lang.long":
            case "float":
            case "double":
            case "java.lang.float":
            case "java.lang.double":
                // lets add a value conversion watcher...
                options.valueConverter = function (scope, modelName) {
                    var value = getModelValueOrDefault(scope, modelName);
                    if (value && angular.isString(value)) {
                        var numberValue = Number(value);
                        Core.pathSet(scope, modelName, numberValue);
                    }
                };
                return '<input type="number"/>';

            case "array":
            case "java.lang.array":
            case "java.lang.iterable":
            case "java.util.list":
            case "java.util.collection":
            case "java.util.iterator":
            case "java.util.set":
            case "object[]":
                // TODO hack for now - objects should not really use the table, thats only really for arrays...
                /*
                case "object":
                case "java.lang.object":
                */
                //return "hawtio-form-array";
                return null;

            case "boolean":
            case "bool":
            case "java.lang.boolean":
                // lets add a value conversion watcher...
                options.valueConverter = function (scope, modelName) {
                    var value = getModelValueOrDefault(scope, modelName);
                    if (value && "true" === value) {
                        //console.log("coercing String to boolean for " + modelName);
                        Core.pathSet(scope, modelName, true);
                    }
                };
                return '<input type="checkbox"/>';

            case "password":
                return '<input type="password"/>';

            case "hidden":
                return '<input type="hidden"/>';
            default:
                // lets check if this name is an alias to a definition in the schema
                return '<input type="text"/>';
        }
    }
    Forms.createStandardWidgetMarkup = createStandardWidgetMarkup;

    function normalize(type, property, schema) {
        type = Forms.resolveTypeNameAlias(type, schema);
        if (!type) {
            return "hawtio-form-text";
        }
        var custom = Core.pathGet(property, ["formTemplate"]);
        if (custom) {
            return "hawtio-form-custom";
        }
        var enumValues = Core.pathGet(property, ["enum"]);
        if (enumValues) {
            // TODO could use different kinds of radio / combo box
            return "hawtio-form-select";
        }

        if (angular.isArray(type)) {
            // TODO union of tabbed forms such as Marshal / Unmarshal in camel...
            return null;
        }
        if (!angular.isString(type)) {
            try  {
                console.log("Unsupported JSON schema type value " + JSON.stringify(type));
            } catch (e) {
                console.log("Unsupported JSON schema type value " + type);
            }
            return null;
        }
        switch (type.toLowerCase()) {
            case "int":
            case "integer":
            case "long":
            case "short":
            case "java.lang.integer":
            case "java.lang.long":
            case "float":
            case "double":
            case "java.lang.float":
            case "java.lang.double":
                return "hawtio-form-number";

            case "array":
            case "java.lang.array":
            case "java.lang.iterable":
            case "java.util.list":
            case "java.util.collection":
            case "java.util.iterator":
            case "java.util.set":
            case "object[]":
                // TODO hack for now - objects should not really use the table, thats only really for arrays...
                /*
                case "object":
                case "java.lang.object":
                */
                var items = property.items;
                if (items) {
                    var typeName = items.type;
                    if (typeName && typeName === "string") {
                        return "hawtio-form-string-array";
                    }
                }
                return "hawtio-form-array";
            case "boolean":
            case "bool":
            case "java.lang.boolean":
                return "hawtio-form-checkbox";
            case "password":
                return "hawtio-form-password";
            case "hidden":
                return "hawtio-form-hidden";
            default:
                // lets check if this name is an alias to a definition in the schema
                return "hawtio-form-text";
        }
    }
    Forms.normalize = normalize;
})(Forms || (Forms = {}));
var Forms;
(function (Forms) {
    var InputTableConfig = (function () {
        function InputTableConfig() {
            this.name = 'form';
            this.method = 'post';
            // the name of the attribute in the scope which is the data to be editted
            this.entity = 'entity';
            // the name of the attribute in the scope which is the table configuration
            this.tableConfig = 'tableConfig';
            // set to 'view' or 'create' for different modes
            this.mode = 'edit';
            // the definition of the form
            this.data = {};
            this.json = undefined;
            this.properties = [];
            this.action = '';
            this.tableclass = 'table table-striped inputTable';
            this.controlgroupclass = 'control-group';
            this.controlclass = 'controls pull-right';
            this.labelclass = 'control-label';
            this.showtypes = 'true';
            this.removeicon = 'icon-remove';
            this.editicon = 'icon-edit';
            this.addicon = 'icon-plus';
            this.removetext = 'Remove';
            this.edittext = 'Edit';
            this.addtext = 'Add';
            this.onadd = 'onadd';
            this.onedit = 'onedit';
            this.onremove = 'onRemove';
        }
        // TODO - add toggles to turn off add or edit buttons
        InputTableConfig.prototype.getTableConfig = function () {
            return this.tableConfig || "tableConfig";
        };
        return InputTableConfig;
    })();
    Forms.InputTableConfig = InputTableConfig;

    var InputTable = (function () {
        function InputTable(workspace, $compile) {
            var _this = this;
            this.workspace = workspace;
            this.$compile = $compile;
            this.restrict = 'A';
            this.scope = true;
            this.replace = true;
            this.transclude = true;
            this.attributeName = 'hawtioInputTable';
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        InputTable.prototype.doLink = function (scope, element, attrs) {
            var _this = this;
            var config = new InputTableConfig;

            var configName = attrs[this.attributeName];
            var tableConfig = Core.pathGet(scope, configName);
            config = Forms.configure(config, tableConfig, attrs);

            var entityName = attrs["entity"] || config.data || "entity";
            var propertyName = attrs["property"] || "arrayData";
            var entityPath = entityName + "." + propertyName;

            // TODO better name?
            var tableName = config["title"] || entityName;

            if (angular.isDefined(config.json)) {
                config.data = $.parseJSON(config.json);
            } else {
                config.data = scope[config.data];
            }

            scope.selectedItems = [];

            var div = $("<div></div>");

            // TODO lets ensure we have some default columns in the column configuration?
            var tableConfig = Core.pathGet(scope, configName);
            if (!tableConfig) {
                console.log("No table configuration for table " + tableName);
            } else {
                tableConfig["selectedItems"] = scope.selectedItems;
            }

            var table = this.createTable(config, configName);

            var group = this.getControlGroup(config, {}, "");
            var controlDiv = this.getControlDiv(config);
            controlDiv.addClass('btn-group');
            group.append(controlDiv);

            function updateData(action) {
                var data = Core.pathGet(scope, entityPath);

                // lets coerce the data to an array if its empty or an object
                if (!data) {
                    data = [];
                }
                if (!angular.isArray(data) && data) {
                    data = [data];
                }
                data = action(data);
                Core.pathSet(scope, entityPath, data);

                // TODO for some reason this doesn't notify the underlying hawtio-datatable that the table has changed
                // so lets force it with a notify...
                scope.$emit("hawtio.datatable." + entityPath, data);
                Core.$apply(scope);
            }

            function removeSelected(data) {
                angular.forEach(scope.selectedItems, function (selected) {
                    var id = selected["_id"];
                    if (angular.isArray(data)) {
                        data = data.remove(function (value) {
                            return Object.equal(value, selected);
                        });
                        delete selected["_id"];
                        data = data.remove(function (value) {
                            return Object.equal(value, selected);
                        });
                    } else {
                        delete selected["_id"];
                        if (id) {
                            delete data[id];
                        } else {
                            // lets iterate for the value
                            var found = false;
                            angular.forEach(data, function (value, key) {
                                if (!found && (Object.equal(value, selected))) {
                                    console.log("Found row to delete! " + key);
                                    delete data[key];
                                    found = true;
                                }
                            });
                            if (!found) {
                                console.log("Could not find " + JSON.stringify(selected) + " in " + JSON.stringify(data));
                            }
                        }
                    }
                });
                return data;
            }

            var add = null;
            var edit = null;
            var remove = null;
            var addDialog = null;
            var editDialog = null;
            var readOnly = attrs["readonly"];
            if (!readOnly) {
                var property = null;
                var dataName = attrs["data"];
                var dataModel = dataName ? Core.pathGet(scope, dataName) : null;
                var schemaName = attrs["schema"] || dataName;
                var schema = schemaName ? Core.pathGet(scope, schemaName) : null;
                if (propertyName && dataModel) {
                    property = Core.pathGet(dataModel, ["properties", propertyName]);
                }

                add = this.getAddButton(config);

                scope.addDialogOptions = {
                    backdropFade: true,
                    dialogFade: true
                };
                scope.showAddDialog = false;

                scope.openAddDialog = function () {
                    // lets lazily create the add dialog
                    scope.addEntity = {};
                    scope.addFormConfig = Forms.findArrayItemsSchema(property, schema);

                    var childDataModelName = "addFormConfig";
                    if (!addDialog) {
                        var title = "Add " + tableName;
                        addDialog = $('<div modal="showAddDialog" close="closeAddDialog()" options="addDialogOptions">\n' + '<div class="modal-header"><h4>' + title + '</h4></div>\n' + '<div class="modal-body"><div simple-form="addFormConfig" entity="addEntity" data="' + childDataModelName + '" schema="' + schemaName + '"></div></div>\n' + '<div class="modal-footer">' + '<button class="btn btn-primary add" type="button" ng-click="addAndCloseDialog()">Add</button>' + '<button class="btn btn-warning cancel" type="button" ng-click="closeAddDialog()">Cancel</button>' + '</div></div>');
                        div.append(addDialog);
                        _this.$compile(addDialog)(scope);
                    }
                    scope.showAddDialog = true;
                    Core.$apply(scope);
                };

                scope.closeAddDialog = function () {
                    scope.showAddDialog = false;
                    scope.addEntity = {};
                };

                scope.addAndCloseDialog = function () {
                    var newData = scope.addEntity;
                    Forms.log.info("About to add the new entity " + JSON.stringify(newData));
                    if (newData) {
                        updateData(function (data) {
                            // TODO deal with non arrays
                            data.push(newData);
                            return data;
                        });
                    }
                    scope.closeAddDialog();
                };

                edit = this.getEditButton(config);

                scope.editDialogOptions = {
                    backdropFade: true,
                    dialogFade: true
                };
                scope.showEditDialog = false;

                scope.openEditDialog = function () {
                    var selected = scope.selectedItems;

                    // lets make a deep copy for the value being edited
                    var editObject = {};
                    if (selected && selected.length) {
                        angular.copy(selected[0], editObject);
                    }
                    scope.editEntity = editObject;
                    scope.editFormConfig = Forms.findArrayItemsSchema(property, schema);

                    // lets lazily create the edit dialog
                    if (!editDialog) {
                        var title = "Edit " + tableName;
                        editDialog = $('<div modal="showEditDialog" close="closeEditDialog()" options="editDialogOptions">\n' + '<div class="modal-header"><h4>' + title + '</h4></div>\n' + '<div class="modal-body"><div simple-form="editFormConfig" entity="editEntity"></div></div>\n' + '<div class="modal-footer">' + '<button class="btn btn-primary save" type="button" ng-click="editAndCloseDialog()">Save</button>' + '<button class="btn btn-warning cancel" type="button" ng-click="closeEditDialog()">Cancel</button>' + '</div></div>');
                        div.append(editDialog);
                        _this.$compile(editDialog)(scope);
                    }
                    scope.showEditDialog = true;
                    Core.$apply(scope);
                };

                scope.closeEditDialog = function () {
                    scope.showEditDialog = false;
                    scope.editEntity = {};
                };

                scope.editAndCloseDialog = function () {
                    var newData = scope.editEntity;
                    console.log("About to edit the new entity " + JSON.stringify(newData));
                    if (newData) {
                        updateData(function (data) {
                            data = removeSelected(data);

                            // TODO deal with non arrays
                            data.push(newData);
                            return data;
                        });
                    }
                    scope.closeEditDialog();
                };

                remove = this.getRemoveButton(config);
            }

            var findFunction = function (scope, func) {
                if (angular.isDefined(scope[func]) && angular.isFunction(scope[func])) {
                    return scope;
                }
                if (angular.isDefined(scope.$parent) && scope.$parent !== null) {
                    return findFunction(scope.$parent, func);
                } else {
                    return null;
                }
            };

            function maybeGet(scope, func) {
                if (scope !== null) {
                    return scope[func];
                }
                return null;
            }

            var onRemoveFunc = config.onremove.replace('(', '').replace(')', '');
            var onEditFunc = config.onedit.replace('(', '').replace(')', '');
            var onAddFunc = config.onadd.replace('(', '').replace(')', '');

            var onRemove = maybeGet(findFunction(scope, onRemoveFunc), onRemoveFunc);
            var onEdit = maybeGet(findFunction(scope, onEditFunc), onEditFunc);
            var onAdd = maybeGet(findFunction(scope, onAddFunc), onAddFunc);

            if (onRemove === null) {
                onRemove = function () {
                    updateData(function (data) {
                        return removeSelected(data);
                    });
                };
            }
            if (onEdit === null) {
                onEdit = function () {
                    scope.openEditDialog();
                };
            }
            if (onAdd === null) {
                onAdd = function (form) {
                    scope.openAddDialog();
                };
            }
            if (add) {
                add.click(function (event) {
                    onAdd();
                    return false;
                });
                controlDiv.append(add);
            }
            if (edit) {
                edit.click(function (event) {
                    onEdit();
                    return false;
                });
                controlDiv.append(edit);
            }
            if (remove) {
                remove.click(function (event) {
                    onRemove();
                    return false;
                });
                controlDiv.append(remove);
            }

            $(div).append(group);
            $(div).append(table);
            $(element).append(div);

            // compile the template
            this.$compile(div)(scope);
        };

        InputTable.prototype.getAddButton = function (config) {
            return $('<button type="button" class="btn add"><i class="' + config.addicon + '"></i> ' + config.addtext + '</button>');
        };

        InputTable.prototype.getEditButton = function (config) {
            return $('<button type="button" class="btn edit" ng-disabled="!selectedItems.length"><i class="' + config.editicon + '"></i> ' + config.edittext + '</button>');
        };

        InputTable.prototype.getRemoveButton = function (config) {
            return $('<button type="remove" class="btn remove" ng-disabled="!selectedItems.length"><i class="' + config.removeicon + '"></i> ' + config.removetext + '</button>');
        };

        InputTable.prototype.createTable = function (config, tableConfig) {
            //var tableType = "hawtio-datatable";
            var tableType = "hawtio-simple-table";
            var table = $('<div class="' + config.tableclass + '" ' + tableType + '="' + tableConfig + '">');

            //table.find('fieldset').append(this.getLegend(config));
            return table;
        };

        InputTable.prototype.getLegend = function (config) {
            var description = Core.pathGet(config, "data.description");
            if (description) {
                return '<legend>' + config.data.description + '</legend>';
            }
            return '';
        };

        InputTable.prototype.getControlGroup = function (config, arg, id) {
            var rc = $('<div class="' + config.controlgroupclass + '"></div>');
            if (angular.isDefined(arg.description)) {
                rc.attr('title', arg.description);
            }
            return rc;
        };

        InputTable.prototype.getControlDiv = function (config) {
            return $('<div class="' + config.controlclass + '"></div>');
        };

        InputTable.prototype.getHelpSpan = function (config, arg, id) {
            var rc = $('<span class="help-block"></span>');
            if (angular.isDefined(arg.type) && config.showtypes !== 'false') {
                rc.append('Type: ' + arg.type);
            }
            return rc;
        };
        return InputTable;
    })();
    Forms.InputTable = InputTable;
})(Forms || (Forms = {}));
/**
* @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 (angular.isArray(tree)) {
            tree.forEach(function (folder) {
                Tree.sanitize(folder);
            });
        }
        var title = tree['title'];
        if (title) {
            tree['title'] = title.unescapeHTML(true).escapeHTML();
        }
        if (tree.children) {
            Tree.sanitize(tree.children);
        }
    }
    Tree.sanitize = sanitize;

    angular.module(Tree.pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).directive('hawtioTree', 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;
            }

            // 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);
            });

            // 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);
            });

            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);
                }
                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);
            }
        };
    }).run(function (helpRegistry) {
        helpRegistry.addDevDoc(Tree.pluginName, 'app/tree/doc/developer.md');
    });

    hawtioPluginLoader.addModule(Tree.pluginName);
})(Tree || (Tree = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function JobSchedulerController($scope, workspace, jolokia) {
        $scope.refresh = loadTable;

        $scope.jobs = [];
        $scope.deleteJobsDialog = new UI.Dialog();

        $scope.gridOptions = {
            selectedItems: [],
            data: 'jobs',
            displayFooter: false,
            showFilter: false,
            showColumnMenu: true,
            enableColumnResize: true,
            enableColumnReordering: true,
            filterOptions: {
                filterText: ''
            },
            selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            maintainColumnRatios: false,
            columnDefs: [
                {
                    field: 'jobId',
                    displayName: 'Job ID',
                    width: '25%'
                },
                {
                    field: 'cronEntry',
                    displayName: 'Cron Entry',
                    width: '10%'
                },
                {
                    field: 'delay',
                    displayName: 'Delay',
                    width: '5%'
                },
                {
                    field: 'repeat',
                    displayName: 'repeat',
                    width: '5%'
                },
                {
                    field: 'period',
                    displayName: 'period',
                    width: '5%'
                },
                {
                    field: 'start',
                    displayName: 'Start',
                    width: '25%'
                },
                {
                    field: 'next',
                    displayName: 'Next',
                    width: '25%'
                }
            ]
        };

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;

            // lets defer execution as we may not have the selection just yet
            setTimeout(loadTable, 50);
        });

        function loadTable() {
            var selection = workspace.selection;
            if (selection) {
                var mbean = selection.objectName;
                if (mbean) {
                    jolokia.request({ type: 'read', mbean: mbean, attribute: "AllJobs" }, onSuccess(populateTable));
                }
            }
            Core.$apply($scope);
        }

        function populateTable(response) {
            var data = response.value;
            if (!angular.isArray(data)) {
                $scope.jobs = [];
                angular.forEach(data, function (value, idx) {
                    $scope.jobs.push(value);
                });
            } else {
                $scope.jobs = data;
            }
            Core.$apply($scope);
        }

        $scope.deleteJobs = function () {
            var selection = workspace.selection;
            var mbean = selection.objectName;
            if (mbean && selection) {
                var selectedItems = $scope.gridOptions.selectedItems;
                $scope.message = "Deleted " + Core.maybePlural(selectedItems.length, "job");
                var operation = "removeJob(java.lang.String)";
                angular.forEach(selectedItems, function (item, idx) {
                    var id = item.jobId;
                    if (id) {
                        var callback = (idx + 1 < selectedItems.length) ? intermediateResult : operationSuccess;
                        jolokia.execute(mbean, operation, id, onSuccess(callback));
                    }
                });
            }
        };

        function intermediateResult() {
        }

        function operationSuccess() {
            $scope.gridOptions.selectedItems.splice(0);
            notification("success", $scope.message);
            setTimeout(loadTable, 50);
        }
    }
    ActiveMQ.JobSchedulerController = JobSchedulerController;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function DestinationController($scope, workspace, jolokia) {
        $scope.workspace = workspace;
        $scope.message = "";
        $scope.queueType = 'true';

        $scope.deleteDialog = false;
        $scope.purgeDialog = false;

        updateQueueType();

        function updateQueueType() {
            $scope.destinationTypeName = $scope.queueType ? "Queue" : "Topic";
        }

        $scope.$watch('queueType', function () {
            updateQueueType();
        });

        $scope.$watch('workspace.selection', function () {
            workspace.moveIfViewInvalid();
        });

        function operationSuccess() {
            $scope.destinationName = "";
            $scope.workspace.operationCounter += 1;
            Core.$apply($scope);
            notification("success", $scope.message);
            $scope.workspace.loadTree();
        }

        function deleteSuccess() {
            // lets set the selection to the parent
            workspace.removeAndSelectParentNode();
            $scope.workspace.operationCounter += 1;
            Core.$apply($scope);
            notification("success", $scope.message);
            $scope.workspace.loadTree();
        }

        function getBrokerMBean(jolokia) {
            var mbean = null;
            var selection = workspace.selection;
            if (selection && ActiveMQ.isBroker(workspace) && selection.objectName) {
                return selection.objectName;
            }
            var folderNames = selection.folderNames;

            //if (selection && jolokia && folderNames && folderNames.length > 1) {
            var parent = selection ? selection.parent : null;
            if (selection && parent && jolokia && folderNames && folderNames.length > 1) {
                mbean = parent.objectName;

                // we might be a destination, so lets try one more parent
                if (!mbean && parent) {
                    mbean = parent.parent.objectName;
                }
                if (!mbean) {
                    mbean = "" + folderNames[0] + ":BrokerName=" + folderNames[1] + ",Type=Broker";
                }
            }
            return mbean;
        }

        $scope.createDestination = function (name, isQueue) {
            var mbean = getBrokerMBean(jolokia);
            if (mbean) {
                var operation;
                if (isQueue) {
                    operation = "addQueue(java.lang.String)";
                    $scope.message = "Created queue " + name;
                } else {
                    operation = "addTopic(java.lang.String)";
                    $scope.message = "Created topic " + name;
                }
                if (mbean) {
                    jolokia.execute(mbean, operation, name, onSuccess(operationSuccess));
                } else {
                    notification("error", "Could not find the Broker MBean!");
                }
            }
        };

        $scope.deleteDestination = function () {
            var mbean = getBrokerMBean(jolokia);
            var selection = workspace.selection;
            var entries = selection.entries;
            if (mbean && selection && jolokia && entries) {
                var domain = selection.domain;
                var name = entries["Destination"] || entries["destinationName"] || selection.title;
                name = name.unescapeHTML();
                var isQueue = "Topic" !== (entries["Type"] || entries["destinationType"]);
                var operation;
                if (isQueue) {
                    operation = "removeQueue(java.lang.String)";
                    $scope.message = "Deleted queue " + name;
                } else {
                    operation = "removeTopic(java.lang.String)";
                    $scope.message = "Deleted topic " + name;
                }
                jolokia.execute(mbean, operation, name, onSuccess(deleteSuccess));
            }
        };

        $scope.purgeDestination = function () {
            var mbean = workspace.getSelectedMBeanName();
            var selection = workspace.selection;
            var entries = selection.entries;
            if (mbean && selection && jolokia && entries) {
                var name = entries["Destination"] || entries["destinationName"] || selection.title;
                name = name.unescapeHTML();
                var operation = "purge()";
                $scope.message = "Purged queue " + name;
                jolokia.execute(mbean, operation, onSuccess(operationSuccess));
            }
        };

        $scope.name = function () {
            var selection = workspace.selection;
            if (selection) {
                return selection.title;
            }
            return null;
        };
    }
    ActiveMQ.DestinationController = DestinationController;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    ActiveMQ.log = Logger.get("activemq");
    ActiveMQ.jmxDomain = 'org.apache.activemq';

    function getSelectionQueuesFolder(workspace) {
        function findQueuesFolder(node) {
            if (node) {
                if (node.title === "Queues" || node.title === "Queue") {
                    return node;
                }
                var parent = node.parent;
                if (parent) {
                    return findQueuesFolder(parent);
                }
            }
            return null;
        }

        var selection = workspace.selection;
        if (selection) {
            return findQueuesFolder(selection);
        }
        return null;
    }
    ActiveMQ.getSelectionQueuesFolder = getSelectionQueuesFolder;

    function getSelectionTopicsFolder(workspace) {
        function findTopicsFolder(node) {
            var answer = null;
            if (node) {
                if (node.title === "Topics" || node.title === "Topic") {
                    answer = node;
                }

                if (answer === null) {
                    angular.forEach(node.children, function (child) {
                        if (child.title === "Topics" || child.title === "Topic") {
                            answer = child;
                        }
                    });
                }
            }
            return answer;
        }

        var selection = workspace.selection;
        if (selection) {
            return findTopicsFolder(selection);
        }
        return null;
    }
    ActiveMQ.getSelectionTopicsFolder = getSelectionTopicsFolder;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function TreeHeaderController($scope) {
        $scope.expandAll = function () {
            Tree.expandAll("#activemqtree");
        };

        $scope.contractAll = function () {
            Tree.contractAll("#activemqtree");
        };
    }
    ActiveMQ.TreeHeaderController = TreeHeaderController;

    function TreeController($scope, $location, workspace, localStorage) {
        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        $scope.$watch('workspace.tree', function () {
            reloadTree();
        });

        $scope.$on('jmxTreeUpdated', function () {
            reloadTree();
        });

        function reloadTree() {
            ActiveMQ.log.debug("workspace tree has changed, lets reload the activemq tree");

            var children = [];
            var tree = workspace.tree;
            if (tree) {
                var domainName = "org.apache.activemq";
                var folder = tree.get(domainName);
                if (folder) {
                    children = folder.children;
                }
                if (children.length) {
                    var firstChild = children[0];

                    // the children could be AMQ 5.7 style broker name folder with the actual MBean in the children
                    // along with folders for the Queues etc...
                    if (!firstChild.typeName && firstChild.children.length < 4) {
                        // lets avoid the top level folder
                        var answer = [];
                        angular.forEach(children, function (child) {
                            answer = answer.concat(child.children);
                        });
                        children = answer;
                    }
                }

                // filter out advisory topics
                children.forEach(function (broker) {
                    var grandChildren = broker.children;
                    if (grandChildren) {
                        Tree.sanitize(grandChildren);
                        var idx = grandChildren.findIndex(function (n) {
                            return n.title === "Topic";
                        });
                        if (idx > 0) {
                            var old = grandChildren[idx];

                            // we need to store all topics the first time on the workspace
                            // so we have access to them later if the user changes the filter in the preferences
                            var key = "ActiveMQ-allTopics-" + broker.title;
                            var allTopics = workspace.mapData[key];
                            if (angular.isUndefined(allTopics)) {
                                var allTopics = old.children.clone();
                                workspace.mapData[key] = allTopics;
                            }

                            var filter = Core.parseBooleanValue(localStorage["activemqFilterAdvisoryTopics"]);
                            if (filter) {
                                if (old && old.children) {
                                    var filteredTopics = old.children.filter(function (c) {
                                        return !c.title.startsWith("ActiveMQ.Advisory");
                                    });
                                    old.children = filteredTopics;
                                }
                            } else if (allTopics) {
                                old.children = allTopics;
                            }
                        }
                    }
                });

                var treeElement = $("#activemqtree");
                Jmx.enableTree($scope, $location, workspace, treeElement, children, true);

                // lets do this asynchronously to avoid Error: $digest already in progress
                setTimeout(updateSelectionFromURL, 50);
            }
        }

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURLAndAutoSelect($location, $("#activemqtree"), function (first) {
                // use function to auto select the queue folder on the 1st broker
                var queues = first.getChildren()[0];
                if (queues && queues.data.title === 'Queue') {
                    first = queues;
                    first.expand(true);
                    return first;
                }
                return null;
            }, true);
        }
    }
    ActiveMQ.TreeController = TreeController;
})(ActiveMQ || (ActiveMQ = {}));
/**
* @module ActiveMQ
*/
var ActiveMQ;
(function (ActiveMQ) {
    function PreferencesController($scope, localStorage, userDetails, $rootScope) {
        Core.initPreferenceScope($scope, localStorage, {
            'activemqUserName': {
                'value': userDetails.username
            },
            'activemqPassword': {
                'value': userDetails.password
            },
            'activemqBrowseBytesMessages': {
                'value': 1,
                'converter': parseInt,
                'formatter': function (value) {
                    return "" + value;
                }
            },
            'activemqFilterAdvisoryTopics': {
                'value': false,
                'converter': Core.parseBooleanValue,
                'post': function (newValue) {
                    $rootScope.$broadcast('jmxTreeUpdated');
                }
            }
        });
    }
    ActiveMQ.PreferencesController = PreferencesController;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function BrokerDiagramController($scope, $compile, $location, localStorage, jolokia, workspace) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        var isFmc = Fabric.isFMCContainer(workspace);
        $scope.isFmc = isFmc;

        $scope.selectedNode = null;

        var defaultFlags = {
            panel: true,
            popup: false,
            label: true,
            group: false,
            profile: false,
            slave: false,
            broker: isFmc,
            network: true,
            container: false,
            queue: true,
            topic: true,
            consumer: true,
            producer: true
        };

        $scope.viewSettings = {};

        $scope.shapeSize = {
            broker: 20,
            queue: 14,
            topic: 14
        };

        var redrawGraph = Core.throttled(doRedrawGraph, 1000);

        var graphBuilder = new ForceGraph.GraphBuilder();

        Core.bindModelToSearchParam($scope, $location, "searchFilter", "q", "");

        angular.forEach(defaultFlags, function (defaultValue, key) {
            var modelName = "viewSettings." + key;

            // bind model values to search params...
            function currentValue() {
                var answer = $location.search()[paramName] || defaultValue;
                return answer === "false" ? false : answer;
            }

            var paramName = key;
            var value = currentValue();
            Core.pathSet($scope, modelName, value);

            $scope.$watch(modelName, function () {
                var current = Core.pathGet($scope, modelName);
                var old = currentValue();
                if (current !== old) {
                    var defaultValue = defaultFlags[key];
                    if (current !== defaultValue) {
                        if (!current) {
                            current = "false";
                        }
                        $location.search(paramName, current);
                    } else {
                        $location.search(paramName, null);
                    }
                }
                redrawGraph();
            });
        });

        $scope.connectToBroker = function () {
            var selectedNode = $scope.selectedNode;
            if (selectedNode) {
                var container = selectedNode["brokerContainer"] || selectedNode;
                connectToBroker(container, selectedNode["brokerName"]);
            }
        };

        function connectToBroker(container, brokerName, postfix) {
            if (typeof postfix === "undefined") { postfix = null; }
            if (isFmc && container.jolokia !== jolokia) {
                Fabric.connectToBroker($scope, container, postfix);
            } else {
                var view = "/jmx/attributes?tab=activemq";
                if (!postfix) {
                    if (brokerName) {
                        // lets default to the broker view
                        postfix = "nid=root-org.apache.activemq-Broker-" + brokerName;
                    }
                }
                if (postfix) {
                    view += "&" + postfix;
                }
                ActiveMQ.log.info("Opening view " + view);
                var path = url("/#" + view);
                window.open(path, '_destination');
                window.focus();
                //$location.path(view);
            }
        }

        $scope.connectToDestination = function () {
            var selectedNode = $scope.selectedNode;
            if (selectedNode) {
                var container = selectedNode["brokerContainer"] || selectedNode;
                var brokerName = selectedNode["brokerName"];
                var destinationType = selectedNode["destinationType"] || selectedNode["typeLabel"];
                var destinationName = selectedNode["destinationName"];
                var postfix = null;
                if (brokerName && destinationType && destinationName) {
                    postfix = "nid=root-org.apache.activemq-Broker-" + brokerName + "-" + destinationType + "-" + destinationName;
                }
                connectToBroker(container, brokerName, postfix);
            }
        };

        $scope.$on('$destroy', function (event) {
            stopOldJolokia();
        });

        function stopOldJolokia() {
            var oldJolokia = $scope.selectedNodeJolokia;
            if (oldJolokia && oldJolokia !== jolokia) {
                oldJolokia.stop();
            }
        }

        $scope.$watch("selectedNode", function (newValue, oldValue) {
            // lets cancel any previously registered thingy
            if ($scope.unregisterFn) {
                $scope.unregisterFn();
                $scope.unregisterFn = null;
            }
            var node = $scope.selectedNode;
            if (node) {
                var mbean = node.objectName;
                var brokerContainer = node.brokerContainer || {};
                var nodeJolokia = node.jolokia || brokerContainer.jolokia || jolokia;
                if (nodeJolokia !== $scope.selectedNodeJolokia) {
                    stopOldJolokia();
                    $scope.selectedNodeJolokia = nodeJolokia;
                    if (nodeJolokia !== jolokia) {
                        var rate = Core.parseIntValue(localStorage['updateRate'] || "2000", "update rate");
                        if (rate) {
                            nodeJolokia.start(rate);
                        }
                    }
                }
                var dummyResponse = { value: node.panelProperties || {} };
                if (mbean && nodeJolokia) {
                    $scope.unregisterFn = Core.register(nodeJolokia, $scope, {
                        type: 'read', mbean: mbean
                    }, onSuccess(renderNodeAttributes, { error: function (response) {
                            // probably we've got a wrong mbean name?
                            // so lets render at least
                            renderNodeAttributes(dummyResponse);
                            Core.defaultJolokiaErrorHandler(response);
                        } }));
                } else {
                    renderNodeAttributes(dummyResponse);
                }
            }
        });

        function getDestinationTypeName(attributes) {
            var prefix = attributes["DestinationTemporary"] ? "Temporary " : "";
            return prefix + (attributes["DestinationTopic"] ? "Topic" : "Queue");
        }

        var ignoreNodeAttributes = [
            "Broker", "BrokerId", "BrokerName", "Connection",
            "DestinationName", "DestinationQueue", "DestinationTemporary", "DestinationTopic"
        ];

        var ignoreNodeAttributesByType = {
            producer: ["Producer", "ProducerId"],
            queue: ["Name", "MessageGroups", "MessageGroupType", "Subscriptions"],
            topic: ["Name", "Subscriptions"],
            broker: ["DataDirectory", "DurableTopicSubscriptions", "DynamicDestinationProducers", "InactiveDurableToppicSubscribers"]
        };

        var brokerShowProperties = [
            "AverageMessageSize", "BrokerId", "JobSchedulerStorePercentUsage",
            "Slave", "MemoryPercentUsage", "StorePercentUsage", "TempPercentUsage"];
        var onlyShowAttributesByType = {
            broker: brokerShowProperties,
            brokerSlave: brokerShowProperties
        };

        function renderNodeAttributes(response) {
            var properties = [];
            if (response) {
                var value = response.value || {};
                $scope.selectedNodeAttributes = value;
                var selectedNode = $scope.selectedNode || {};
                var brokerContainer = selectedNode['brokerContainer'] || {};
                var nodeType = selectedNode["type"];
                var brokerName = selectedNode["brokerName"];
                var containerId = selectedNode["container"] || brokerContainer["container"];
                var group = selectedNode["group"] || brokerContainer["group"];
                var jolokiaUrl = selectedNode["jolokiaUrl"] || brokerContainer["jolokiaUrl"];
                var profile = selectedNode["profile"] || brokerContainer["profile"];
                var version = selectedNode["version"] || brokerContainer["version"];

                var isBroker = nodeType && nodeType.startsWith("broker");
                var ignoreKeys = ignoreNodeAttributes.concat(ignoreNodeAttributesByType[nodeType] || []);
                var onlyShowKeys = onlyShowAttributesByType[nodeType];

                angular.forEach(value, function (v, k) {
                    if (onlyShowKeys ? onlyShowKeys.indexOf(k) >= 0 : ignoreKeys.indexOf(k) < 0) {
                        var formattedValue = Core.humanizeValueHtml(v);
                        properties.push({ key: humanizeValue(k), value: formattedValue });
                    }
                });
                properties = properties.sortBy("key");

                var brokerProperty = null;
                if (brokerName) {
                    var brokerHtml = '<a target="broker" ng-click="connectToBroker()">' + '<img title="Apache ActiveMQ" src="img/icons/messagebroker.svg"> ' + brokerName + '</a>';
                    if (version && profile) {
                        var brokerLink = Fabric.brokerConfigLink(workspace, jolokia, localStorage, version, profile, brokerName);
                        if (brokerLink) {
                            brokerHtml += ' <a title="configuration settings" target="brokerConfig" href="' + brokerLink + '"><i class="icon-tasks"></i></a>';
                        }
                    }
                    var html = $compile(brokerHtml)($scope);
                    brokerProperty = { key: "Broker", value: html };
                    if (!isBroker) {
                        properties.splice(0, 0, brokerProperty);
                    }
                }

                if (containerId) {
                    var containerModel = "selectedNode" + (selectedNode['brokerContainer'] ? ".brokerContainer" : "");
                    properties.splice(0, 0, { key: "Container", value: $compile('<div fabric-container-link="' + containerModel + '"></div>')($scope) });
                }

                var destinationName = value["DestinationName"] || selectedNode["destinationName"];
                if (destinationName && (nodeType !== "queue" && nodeType !== "topic")) {
                    var destinationTypeName = getDestinationTypeName(value);
                    var html = createDestinationLink(destinationName, destinationTypeName);
                    properties.splice(0, 0, { key: destinationTypeName, value: html });
                }

                var typeLabel = selectedNode["typeLabel"];
                var name = selectedNode["name"] || selectedNode["id"] || selectedNode['objectName'];
                if (typeLabel) {
                    var html = name;
                    if (nodeType === "queue" || nodeType === "topic") {
                        html = createDestinationLink(name, nodeType);
                    }
                    var typeProperty = { key: typeLabel, value: html };
                    if (isBroker && brokerProperty) {
                        typeProperty = brokerProperty;
                    }
                    properties.splice(0, 0, typeProperty);
                }
            }
            $scope.selectedNodeProperties = properties;
            Core.$apply($scope);
        }

        /**
        * Generates the HTML for a link to the destination
        */
        function createDestinationLink(destinationName, destinationType) {
            if (typeof destinationType === "undefined") { destinationType = "queue"; }
            return $compile('<a target="destination" title="' + destinationName + '" ng-click="connectToDestination()">' + destinationName + '</a>')($scope);
        }

        $scope.$watch("searchFilter", function (newValue, oldValue) {
            redrawGraph();
        });

        if (isFmc) {
            Core.register(jolokia, $scope, { type: 'exec', mbean: Fabric.mqManagerMBean, operation: "loadBrokerStatus()" }, onSuccess(onBrokerData));
        } else {
            // lets just use the current stuff from the workspace
            $scope.$watch('workspace.tree', function () {
                redrawGraph();
            });

            $scope.$on('jmxTreeUpdated', function () {
                redrawGraph();
            });
        }

        function onBrokerData(response) {
            if (response) {
                var responseJson = angular.toJson(response.value);
                if ($scope.responseJson === responseJson) {
                    return;
                }

                $scope.responseJson = responseJson;

                $scope.brokers = response.value;
                doRedrawGraph();
            }
        }

        function redrawFabricBrokers() {
            var containersToDelete = $scope.activeContainers || {};
            $scope.activeContainers = {};

            angular.forEach($scope.brokers, function (brokerStatus) {
                // only query master brokers which are provisioned correctly
                brokerStatus.validContainer = brokerStatus.alive && brokerStatus.master && brokerStatus.provisionStatus === "success";

                // don't use type field so we can use it for the node types..
                renameTypeProperty(brokerStatus);

                //log.info("Broker status: " + angular.toJson(brokerStatus, true));
                var groupId = brokerStatus.group;
                var profileId = brokerStatus.profile;
                var brokerId = brokerStatus.brokerName;
                var containerId = brokerStatus.container;
                var versionId = brokerStatus.version || "1.0";

                var group = getOrAddNode("group", groupId, brokerStatus, function () {
                    return {
                        /*
                        navUrl: ,
                        image: {
                        url: "/hawtio/img/icons/osgi/bundle.png",
                        width: 32,
                        height:32
                        },
                        */
                        typeLabel: "Broker Group",
                        popup: {
                            title: "Broker Group: " + groupId,
                            content: "<p>" + groupId + "</p>"
                        }
                    };
                });

                var profile = getOrAddNode("profile", profileId, brokerStatus, function () {
                    return {
                        typeLabel: "Profile",
                        popup: {
                            title: "Profile: " + profileId,
                            content: "<p>" + profileId + "</p>"
                        }
                    };
                });

                // TODO do we need to create a physical broker node per container and logical broker maybe?
                var container = null;
                if (containerId) {
                    container = getOrAddNode("container", containerId, brokerStatus, function () {
                        return {
                            containerId: containerId,
                            typeLabel: "Container",
                            popup: {
                                title: "Container: " + containerId,
                                content: "<p>" + containerId + " version: " + versionId + "</p>"
                            }
                        };
                    });
                }

                var master = brokerStatus.master;
                var broker = getOrAddBroker(master, brokerId, groupId, containerId, container, brokerStatus);
                if (container && container.validContainer) {
                    var key = container.containerId;
                    $scope.activeContainers[key] = container;
                    delete containersToDelete[key];
                }

                // add the links...
                if ($scope.viewSettings.group) {
                    if ($scope.viewSettings.profile) {
                        addLink(group, profile, "group");
                        addLink(profile, broker, "broker");
                    } else {
                        addLink(group, broker, "group");
                    }
                } else {
                    if ($scope.viewSettings.profile) {
                        addLink(profile, broker, "broker");
                    }
                }

                if (container) {
                    if ((master || $scope.viewSettings.slave) && $scope.viewSettings.container) {
                        addLink(broker, container, "container");
                        container.destinationLinkNode = container;
                    } else {
                        container.destinationLinkNode = broker;
                    }
                }
            });
            redrawActiveContainers();
        }

        function redrawLocalBroker() {
            var container = {
                jolokia: jolokia
            };
            var containerId = "local";
            $scope.activeContainers = {
                containerId: container
            };

            if ($scope.viewSettings.broker) {
                jolokia.search("org.apache.activemq:type=Broker,brokerName=*", onSuccess(function (response) {
                    angular.forEach(response, function (objectName) {
                        var details = Core.parseMBean(objectName);
                        if (details) {
                            var properties = details['attributes'];
                            ActiveMQ.log.info("Got broker: " + objectName + " on container: " + containerId + " properties: " + angular.toJson(properties, true));
                            if (properties) {
                                var master = true;
                                var brokerId = properties["brokerName"] || "unknown";
                                var groupId = "";
                                var broker = getOrAddBroker(master, brokerId, groupId, containerId, container, properties);
                            }
                        }
                    });
                    redrawActiveContainers();
                }));
            } else {
                redrawActiveContainers();
            }
        }

        function redrawActiveContainers() {
            // TODO delete any nodes from dead containers in containersToDelete
            angular.forEach($scope.activeContainers, function (container, id) {
                var containerJolokia = container.jolokia;
                if (containerJolokia) {
                    onContainerJolokia(containerJolokia, container, id);
                } else {
                    Fabric.containerJolokia(jolokia, id, function (containerJolokia) {
                        return onContainerJolokia(containerJolokia, container, id);
                    });
                }
            });
            $scope.graph = graphBuilder.buildGraph();
            Core.$apply($scope);
        }

        function doRedrawGraph() {
            graphBuilder = new ForceGraph.GraphBuilder();
            if (isFmc) {
                redrawFabricBrokers();
            } else {
                redrawLocalBroker();
            }
        }

        function brokerNameMarkup(brokerName) {
            return brokerName ? "<p></p>broker: " + brokerName + "</p>" : "";
        }

        function matchesDestinationName(destinationName, typeName) {
            if (destinationName) {
                var selection = workspace.selection;
                if (selection && selection.domain === ActiveMQ.jmxDomain) {
                    var type = selection.entries["destinationType"];
                    if (type) {
                        if ((type === "Queue" && typeName === "topic") || (type === "Topic" && typeName === "queue")) {
                            return false;
                        }
                    }
                    var destName = selection.entries["destinationName"];
                    if (destName) {
                        if (destName !== destinationName)
                            return false;
                    }
                }
                ActiveMQ.log.info("selection: " + selection);

                // TODO if the current selection is a destination...
                return !$scope.searchFilter || destinationName.indexOf($scope.searchFilter) >= 0;
            }
            return false;
        }

        function onContainerJolokia(containerJolokia, container, id) {
            if (containerJolokia) {
                container.jolokia = containerJolokia;

                function getOrAddDestination(properties) {
                    var typeName = properties.destType;
                    var brokerName = properties.brokerName;
                    var destinationName = properties.destinationName;
                    if (!matchesDestinationName(destinationName, typeName)) {
                        return null;
                    }

                    // should we be filtering this destination out
                    var hideFlag = "topic" === typeName ? $scope.viewSettings.topic : $scope.viewSettings.queue;
                    if (!hideFlag) {
                        return null;
                    }
                    var destination = getOrAddNode(typeName, destinationName, properties, function () {
                        var destinationTypeName = properties.destinationType || "Queue";
                        var objectName = "";
                        if (brokerName) {
                            // lets ignore temp topic stuff as there's no mbean for these
                            if (!destinationName.startsWith("ActiveMQ.Advisory.TempQueue_ActiveMQ.Advisory.TempTopic")) {
                                objectName = "org.apache.activemq:type=Broker,brokerName=" + brokerName + ",destinationType=" + destinationTypeName + ",destinationName=" + destinationName;
                            }
                        }
                        var answer = {
                            typeLabel: destinationTypeName,
                            brokerContainer: container,
                            objectName: objectName,
                            jolokia: containerJolokia,
                            popup: {
                                title: destinationTypeName + ": " + destinationName,
                                content: brokerNameMarkup(properties.brokerName)
                            }
                        };
                        if (!brokerName) {
                            containerJolokia.search("org.apache.activemq:destinationType=" + destinationTypeName + ",destinationName=" + destinationName + ",*", onSuccess(function (response) {
                                ActiveMQ.log.info("Found destination mbean: " + response);
                                if (response && response.length) {
                                    answer.objectName = response[0];
                                }
                            }));
                        }
                        return answer;
                    });
                    if (destination && $scope.viewSettings.broker && brokerName) {
                        addLinkIds(brokerNodeId(brokerName), destination["id"], "destination");
                    }
                    return destination;
                }

                // find networks
                var brokerId = container.brokerName;
                if (brokerId && $scope.viewSettings.network && $scope.viewSettings.broker) {
                    containerJolokia.request({ type: "read", mbean: "org.apache.activemq:connector=networkConnectors,*" }, onSuccess(function (response) {
                        angular.forEach(response.value, function (properties, objectName) {
                            var details = Core.parseMBean(objectName);
                            var attributes = details['attributes'];
                            if (properties) {
                                configureDestinationProperties(properties);
                                var remoteBrokerId = properties.RemoteBrokerName;
                                if (remoteBrokerId) {
                                    addLinkIds(brokerNodeId(brokerId), brokerNodeId(remoteBrokerId), "network");
                                }
                            }
                        });
                        graphModelUpdated();
                    }));
                }

                // find consumers
                if ($scope.viewSettings.consumer) {
                    containerJolokia.search("org.apache.activemq:endpoint=Consumer,*", onSuccess(function (response) {
                        angular.forEach(response, function (objectName) {
                            //log.info("Got consumer: " + objectName + " on container: " + id);
                            var details = Core.parseMBean(objectName);
                            if (details) {
                                var properties = details['attributes'];
                                if (properties) {
                                    configureDestinationProperties(properties);
                                    var consumerId = properties.consumerId;
                                    if (consumerId) {
                                        var destination = getOrAddDestination(properties);
                                        if (destination) {
                                            addLink(container.destinationLinkNode, destination, "destination");
                                            var consumer = getOrAddNode("consumer", consumerId, properties, function () {
                                                return {
                                                    typeLabel: "Consumer",
                                                    brokerContainer: container,
                                                    objectName: objectName,
                                                    jolokia: containerJolokia,
                                                    popup: {
                                                        title: "Consumer: " + consumerId,
                                                        content: "<p>client: " + (properties.clientId || "") + "</p> " + brokerNameMarkup(properties.brokerName)
                                                    }
                                                };
                                            });
                                            addLink(destination, consumer, "consumer");
                                        }
                                    }
                                }
                            }
                        });
                        graphModelUpdated();
                    }));
                }

                // find producers
                if ($scope.viewSettings.producer) {
                    containerJolokia.search("org.apache.activemq:endpoint=Producer,*", onSuccess(function (response) {
                        angular.forEach(response, function (objectName) {
                            var details = Core.parseMBean(objectName);
                            if (details) {
                                var properties = details['attributes'];
                                if (properties) {
                                    configureDestinationProperties(properties);
                                    var producerId = properties.producerId;
                                    if (producerId) {
                                        var destination = getOrAddDestination(properties);
                                        if (destination) {
                                            addLink(container.destinationLinkNode, destination, "destination");
                                            var producer = getOrAddNode("producer", producerId, properties, function () {
                                                return {
                                                    typeLabel: "Producer",
                                                    brokerContainer: container,
                                                    objectName: objectName,
                                                    jolokia: containerJolokia,
                                                    popup: {
                                                        title: "Producer: " + producerId,
                                                        content: "<p>client: " + (properties.clientId || "") + "</p> " + brokerNameMarkup(properties.brokerName)
                                                    }
                                                };
                                            });
                                            addLink(producer, destination, "producer");
                                        }
                                        graphModelUpdated();
                                    }
                                }
                            }
                        });
                        graphModelUpdated();
                    }));
                }

                // find dynamic producers
                if ($scope.viewSettings.producer) {
                    containerJolokia.request({ type: "read", mbean: "org.apache.activemq:endpoint=dynamicProducer,*" }, onSuccess(function (response) {
                        angular.forEach(response.value, function (mbeanValues, objectName) {
                            var details = Core.parseMBean(objectName);
                            var attributes = details['attributes'];
                            var properties = {};
                            angular.forEach(attributes, function (value, key) {
                                properties[key] = value;
                            });
                            angular.forEach(mbeanValues, function (value, key) {
                                properties[key] = value;
                            });
                            configureDestinationProperties(properties);
                            properties['destinationName'] = properties['DestinationName'];
                            var producerId = properties["producerId"] || properties["ProducerId"];
                            if (properties["DestinationTemporary"] || properties["DestinationTopc"]) {
                                properties["destType"] = "topic";
                            }
                            var destination = getOrAddDestination(properties);
                            if (producerId && destination) {
                                addLink(container.destinationLinkNode, destination, "destination");
                                var producer = getOrAddNode("producer", producerId, properties, function () {
                                    return {
                                        typeLabel: "Producer (Dynamic)",
                                        brokerContainer: container,
                                        objectName: objectName,
                                        jolokia: containerJolokia,
                                        popup: {
                                            title: "Producer (Dynamic): " + producerId,
                                            content: "<p>client: " + (properties['ClientId'] || "") + "</p> " + brokerNameMarkup(properties['brokerName'])
                                        }
                                    };
                                });
                                addLink(producer, destination, "producer");
                            }
                        });
                        graphModelUpdated();
                    }));
                }
            }
        }

        function graphModelUpdated() {
            $scope.graph = graphBuilder.buildGraph();
            Core.$apply($scope);
        }

        function getOrAddBroker(master, brokerId, groupId, containerId, container, brokerStatus) {
            var broker = null;
            var brokerFlag = master ? $scope.viewSettings.broker : $scope.viewSettings.slave;
            if (brokerFlag) {
                broker = getOrAddNode("broker", brokerId + (master ? "" : ":slave"), brokerStatus, function () {
                    return {
                        type: master ? "broker" : "brokerSlave",
                        typeLabel: master ? "Broker" : "Slave Broker",
                        popup: {
                            title: (master ? "Master" : "Slave") + " Broker: " + brokerId,
                            content: "<p>Container: " + containerId + "</p> <p>Group: " + groupId + "</p>"
                        }
                    };
                });
                if (master) {
                    if (!broker['objectName']) {
                        // lets try guess the mbean name
                        broker['objectName'] = "org.apache.activemq:type=Broker,brokerName=" + brokerId;
                        ActiveMQ.log.info("Guessed broker mbean: " + broker['objectName']);
                    }
                    if (!broker['brokerContainer'] && container) {
                        broker['brokerContainer'] = container;
                    }
                }
            }
            return broker;
        }

        function getOrAddNode(typeName, id, properties, createFn) {
            var node = null;
            if (id) {
                var nodeId = typeName + ":" + id;
                node = graphBuilder.getNode(nodeId);
                if (!node) {
                    var nodeValues = createFn();
                    node = angular.copy(properties);
                    angular.forEach(nodeValues, function (value, key) {
                        return node[key] = value;
                    });

                    node['id'] = nodeId;
                    if (!node['type']) {
                        node['type'] = typeName;
                    }
                    if (!node['name']) {
                        node['name'] = id;
                    }
                    if (node) {
                        var size = $scope.shapeSize[typeName];
                        if (size && !node['size']) {
                            node['size'] = size;
                        }
                        if (!node['summary']) {
                            node['summary'] = node['popup'] || "";
                        }
                        if (!$scope.viewSettings.popup) {
                            delete node['popup'];
                        }
                        if (!$scope.viewSettings.label) {
                            delete node['name'];
                        }

                        // lets not add nodes which are defined as being disabled
                        var enabled = $scope.viewSettings[typeName];
                        if (enabled || !angular.isDefined(enabled)) {
                            //log.info("Adding node " + nodeId + " of type + " + typeName);
                            graphBuilder.addNode(node);
                        } else {
                            //log.info("Ignoring node " + nodeId + " of type + " + typeName);
                        }
                    }
                }
            }
            return node;
        }

        function addLink(object1, object2, linkType) {
            if (object1 && object2) {
                addLinkIds(object1.id, object2.id, linkType);
            }
        }

        function addLinkIds(id1, id2, linkType) {
            if (id1 && id2) {
                graphBuilder.addLink(id1, id2, linkType);
            }
        }

        function brokerNodeId(brokerId) {
            return brokerId ? "broker:" + brokerId : null;
        }

        /**
        * Avoid the JMX type property clashing with the ForceGraph type property; used for associating css classes with nodes on the graph
        *
        * @param properties
        */
        function renameTypeProperty(properties) {
            properties.mbeanType = properties['type'];
            delete properties['type'];
        }

        function configureDestinationProperties(properties) {
            renameTypeProperty(properties);
            var destinationType = properties.destinationType || "Queue";
            var typeName = destinationType.toLowerCase();
            properties.isQueue = !typeName.startsWith("t");
            properties['destType'] = typeName;
        }
    }
    ActiveMQ.BrokerDiagramController = BrokerDiagramController;
})(ActiveMQ || (ActiveMQ = {}));
/**
* @module ActiveMQ
* @main ActiveMQ
*/
var ActiveMQ;
(function (ActiveMQ) {
    var pluginName = 'activemq';

    angular.module(pluginName, ['bootstrap', 'ngResource', 'ui.bootstrap.dialog', 'hawtioCore', 'camel', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/activemq/browseQueue', { templateUrl: 'app/activemq/html/browseQueue.html' }).when('/activemq/diagram', { templateUrl: 'app/activemq/html/brokerDiagram.html', reloadOnSearch: false }).when('/activemq/createDestination', { templateUrl: 'app/activemq/html/createDestination.html' }).when('/activemq/createQueue', { templateUrl: 'app/activemq/html/createQueue.html' }).when('/activemq/createTopic', { templateUrl: 'app/activemq/html/createTopic.html' }).when('/activemq/deleteQueue', { templateUrl: 'app/activemq/html/deleteQueue.html' }).when('/activemq/deleteTopic', { templateUrl: 'app/activemq/html/deleteTopic.html' }).when('/activemq/sendMessage', { templateUrl: 'app/camel/html/sendMessage.html' }).when('/activemq/durableSubscribers', { templateUrl: 'app/activemq/html/durableSubscribers.html' }).when('/activemq/jobs', { templateUrl: 'app/activemq/html/jobs.html' });
    }).run(function ($location, workspace, viewRegistry, helpRegistry, preferencesRegistry) {
        viewRegistry['activemq'] = 'app/activemq/html/layoutActiveMQTree.html';
        helpRegistry.addUserDoc('activemq', 'app/activemq/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("org.apache.activemq");
        });

        preferencesRegistry.addTab("ActiveMQ", "app/activemq/html/preferences.html", function () {
            return workspace.treeContainsDomainAndProperties("org.apache.activemq");
        });

        workspace.addTreePostProcessor(postProcessTree);

        // register default attribute views
        var attributes = workspace.attributeColumnDefs;
        attributes[ActiveMQ.jmxDomain + "/Broker/folder"] = [
            { field: 'BrokerName', displayName: 'Name', width: "**" },
            { field: 'TotalProducerCount', displayName: 'Producer #' },
            { field: 'TotalConsumerCount', displayName: 'Consumer #' },
            { field: 'StorePercentUsage', displayName: 'Store %' },
            { field: 'TempPercentUsage', displayName: 'Temp %' },
            { field: 'MemoryPercentUsage', displayName: 'Memory %' },
            { field: 'TotalEnqueueCount', displayName: 'Enqueue #' },
            { field: 'TotalDequeueCount', displayName: 'Dequeue #' }
        ];
        attributes[ActiveMQ.jmxDomain + "/Queue/folder"] = [
            { field: 'Name', displayName: 'Name', width: "***" },
            { field: 'QueueSize', displayName: 'Queue Size' },
            { field: 'ProducerCount', displayName: 'Producer #' },
            { field: 'ConsumerCount', displayName: 'Consumer #' },
            { field: 'EnqueueCount', displayName: 'Enqueue #' },
            { field: 'DequeueCount', displayName: 'Dequeue #' },
            { field: 'MemoryPercentUsage', displayName: 'Memory %' },
            { field: 'DispatchCount', displayName: 'Dispatch #', visible: false }
        ];
        attributes[ActiveMQ.jmxDomain + "/Topic/folder"] = [
            { field: 'Name', displayName: 'Name', width: "****" },
            { field: 'ProducerCount', displayName: 'Producer #' },
            { field: 'ConsumerCount', displayName: 'Consumer #' },
            { field: 'EnqueueCount', displayName: 'Enqueue #' },
            { field: 'DequeueCount', displayName: 'Dequeue #' },
            { field: 'MemoryPercentUsage', displayName: 'Memory %' },
            { field: 'DispatchCount', displayName: 'Dispatch #', visible: false }
        ];
        attributes[ActiveMQ.jmxDomain + "/Consumer/folder"] = [
            { field: 'ConnectionId', displayName: 'Name', width: "**" },
            { field: 'PrefetchSize', displayName: 'Prefetch Size' },
            { field: 'Priority', displayName: 'Priority' },
            { field: 'DispatchedQueueSize', displayName: 'Dispatched Queue #' },
            { field: 'SlowConsumer', displayName: 'Slow ?' },
            { field: 'Retroactive', displayName: 'Retroactive' },
            { field: 'Selector', displayName: 'Selector' }
        ];
        attributes[ActiveMQ.jmxDomain + "/networkConnectors/folder"] = [
            { field: 'Name', displayName: 'Name', width: "**" },
            { field: 'UserName', displayName: 'User Name' },
            { field: 'PrefetchSize', displayName: 'Prefetch Size' },
            { field: 'ConduitSubscriptions', displayName: 'Conduit Subscriptions?' },
            { field: 'Duplex', displayName: 'Duplex' },
            { field: 'DynamicOnly', displayName: 'Dynamic Only' }
        ];
        attributes[ActiveMQ.jmxDomain + "/PersistenceAdapter/folder"] = [
            { field: 'IndexDirectory', displayName: 'Index Directory', width: "**" },
            { field: 'LogDirectory', displayName: 'Log Directory', width: "**" }
        ];

        workspace.topLevelTabs.push({
            id: "activemq",
            content: "ActiveMQ",
            title: "Manage your ActiveMQ message brokers",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("org.apache.activemq");
            },
            href: function () {
                return "#/jmx/attributes?tab=activemq";
            },
            isActive: function () {
                return workspace.isTopTabActive("activemq");
            }
        });

        // add sub level tabs
        workspace.subLevelTabs.push({
            content: '<i class="icon-envelope"></i> Browse',
            title: "Browse the messages on the queue",
            isValid: function (workspace) {
                return isQueue(workspace);
            },
            href: function () {
                return "#/activemq/browseQueue";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-pencil"></i> Send',
            title: "Send a message to this destination",
            isValid: function (workspace) {
                return isQueue(workspace) || isTopic(workspace);
            },
            href: function () {
                return "#/activemq/sendMessage";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-picture"></i> Diagram',
            title: "View a diagram of the producers, destinations and consumers",
            isValid: function (workspace) {
                return workspace.isTopTabActive("activemq") || workspace.selectionHasDomain(ActiveMQ.jmxDomain);
            },
            href: function () {
                return "#/activemq/diagram";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-plus"></i> Create',
            title: "Create a new destination",
            isValid: function (workspace) {
                return isBroker(workspace);
            },
            href: function () {
                return "#/activemq/createDestination";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-plus"></i> Create',
            title: "Create a new queue",
            isValid: function (workspace) {
                return isQueuesFolder(workspace);
            },
            href: function () {
                return "#/activemq/createQueue";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-plus"></i> Create',
            title: "Create a new topic",
            isValid: function (workspace) {
                return isTopicsFolder(workspace);
            },
            href: function () {
                return "#/activemq/createTopic";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-remove"></i> Delete Topic',
            title: "Delete this topic",
            isValid: function (workspace) {
                return isTopic(workspace);
            },
            href: function () {
                return "#/activemq/deleteTopic";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-remove"></i> Delete',
            title: "Delete or purge this queue",
            isValid: function (workspace) {
                return isQueue(workspace);
            },
            href: function () {
                return "#/activemq/deleteQueue";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-list"></i> Durable Subscribers',
            title: "Manage durable subscribers",
            isValid: function (workspace) {
                return isBroker(workspace);
            },
            href: function () {
                return "#/activemq/durableSubscribers";
            }
        });

        workspace.subLevelTabs.push({
            content: '<i class="icon-list"></i> Jobs',
            title: "Manage jobs",
            isValid: function (workspace) {
                return isJobScheduler(workspace);
            },
            href: function () {
                return "#/activemq/jobs";
            }
        });

        function postProcessTree(tree) {
            var activemq = tree.get("org.apache.activemq");
            setConsumerType(activemq);

            // lets move queue and topic as first children within brokers
            if (activemq) {
                angular.forEach(activemq.children, function (broker) {
                    angular.forEach(broker.children, function (child) {
                        // lets move Topic/Queue to the front.
                        var grandChildren = child.children;
                        if (grandChildren) {
                            var names = ["Topic", "Queue"];
                            angular.forEach(names, function (name) {
                                var idx = grandChildren.findIndex(function (n) {
                                    return n.title === name;
                                });
                                if (idx > 0) {
                                    var old = grandChildren[idx];
                                    grandChildren.splice(idx, 1);
                                    grandChildren.splice(0, 0, old);
                                }
                            });
                        }
                    });
                });
            }
        }

        function setConsumerType(node) {
            if (node) {
                var parent = node.parent;
                var entries = node.entries;
                if (parent && !parent.typeName && entries) {
                    var endpoint = entries["endpoint"];
                    if (endpoint === "Consumer" || endpoint === "Producer") {
                        //console.log("Setting the typeName on " + parent.title + " to " + endpoint);
                        parent.typeName = endpoint;
                    }
                    var connectorName = entries["connectorName"];
                    if (connectorName && !node.icon) {
                        // lets default a connector icon
                        node.icon = url("/img/icons/activemq/connector.png");
                    }
                }
                angular.forEach(node.children, function (child) {
                    return setConsumerType(child);
                });
            }
        }
    });

    hawtioPluginLoader.addModule(pluginName);

    function isQueue(workspace) {
        //return workspace.selectionHasDomainAndType(jmxDomain, 'Queue');
        return workspace.hasDomainAndProperties(ActiveMQ.jmxDomain, { 'destinationType': 'Queue' }, 4) || workspace.selectionHasDomainAndType(ActiveMQ.jmxDomain, 'Queue');
    }
    ActiveMQ.isQueue = isQueue;

    function isTopic(workspace) {
        //return workspace.selectionHasDomainAndType(jmxDomain, 'Topic');
        return workspace.hasDomainAndProperties(ActiveMQ.jmxDomain, { 'destinationType': 'Topic' }, 4) || workspace.selectionHasDomainAndType(ActiveMQ.jmxDomain, 'Topic');
    }
    ActiveMQ.isTopic = isTopic;

    function isQueuesFolder(workspace) {
        return workspace.selectionHasDomainAndLastFolderName(ActiveMQ.jmxDomain, 'Queue');
    }
    ActiveMQ.isQueuesFolder = isQueuesFolder;

    function isTopicsFolder(workspace) {
        return workspace.selectionHasDomainAndLastFolderName(ActiveMQ.jmxDomain, 'Topic');
    }
    ActiveMQ.isTopicsFolder = isTopicsFolder;

    function isJobScheduler(workspace) {
        return workspace.hasDomainAndProperties(ActiveMQ.jmxDomain, { 'service': 'JobScheduler' }, 4);
    }
    ActiveMQ.isJobScheduler = isJobScheduler;

    function isBroker(workspace) {
        if (workspace.selectionHasDomainAndType(ActiveMQ.jmxDomain, 'Broker')) {
            var parent = Core.pathGet(workspace, ["selection", "parent"]);
            return !(parent && parent.ancestorHasType('Broker'));
        }
        return false;
    }
    ActiveMQ.isBroker = isBroker;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function DurableSubscriberController($scope, workspace, jolokia) {
        $scope.refresh = loadTable;

        $scope.durableSubscribers = [];

        $scope.tempData = [];

        $scope.createSubscriberDialog = new UI.Dialog();
        $scope.deleteSubscriberDialog = new UI.Dialog();
        $scope.showSubscriberDialog = new UI.Dialog();

        $scope.topicName = '';
        $scope.clientId = '';
        $scope.subscriberName = '';
        $scope.subSelector = '';

        $scope.gridOptions = {
            selectedItems: [],
            data: 'durableSubscribers',
            displayFooter: false,
            showFilter: false,
            showColumnMenu: true,
            enableCellSelection: false,
            enableColumnResize: true,
            enableColumnReordering: true,
            selectWithCheckboxOnly: false,
            showSelectionCheckbox: false,
            multiSelect: false,
            displaySelectionCheckbox: false,
            filterOptions: {
                filterText: ''
            },
            maintainColumnRatios: false,
            columnDefs: [
                {
                    field: 'destinationName',
                    displayName: 'Topic',
                    width: '30%'
                },
                {
                    field: 'clientId',
                    displayName: 'Client ID',
                    width: '30%'
                },
                {
                    field: 'consumerId',
                    displayName: 'Consumer ID',
                    cellTemplate: '<div class="ngCellText"><span ng-hide="row.entity.status != \'Offline\'">{{row.entity.consumerId}}</span><a ng-show="row.entity.status != \'Offline\'" ng-click="openSubscriberDialog(row)">{{row.entity.consumerId}}</a></div>',
                    width: '30%'
                },
                {
                    field: 'status',
                    displayName: 'Status',
                    width: '10%'
                }
            ]
        };

        $scope.doCreateSubscriber = function (clientId, subscriberName, topicName, subSelector) {
            $scope.createSubscriberDialog.close();
            $scope.clientId = clientId;
            $scope.subscriberName = subscriberName;
            $scope.topicName = topicName;
            $scope.subSelector = subSelector;
            if (Core.isBlank($scope.subSelector)) {
                $scope.subSelector = null;
            }
            var mbean = getBrokerMBean(jolokia);
            if (mbean) {
                jolokia.execute(mbean, "createDurableSubscriber(java.lang.String, java.lang.String, java.lang.String, java.lang.String)", $scope.clientId, $scope.subscriberName, $scope.topicName, $scope.subSelector, onSuccess(function () {
                    notification('success', "Created durable subscriber " + clientId);
                    $scope.clientId = '';
                    $scope.subscriberName = '';
                    $scope.topicName = '';
                    $scope.subSelector = '';
                    loadTable();
                }));
            } else {
                notification("error", "Could not find the Broker MBean!");
            }
        };

        $scope.deleteSubscribers = function () {
            var mbean = $scope.gridOptions.selectedItems[0]._id;
            jolokia.execute(mbean, "destroy()", onSuccess(function () {
                $scope.showSubscriberDialog.close();
                notification('success', "Deleted durable subscriber");
                loadTable();
                $scope.gridOptions.selectedItems = [];
            }));
        };

        $scope.openSubscriberDialog = function (subscriber) {
            jolokia.request({ type: "read", mbean: subscriber.entity._id }, onSuccess(function (response) {
                $scope.showSubscriberDialog.subscriber = response.value;
                $scope.showSubscriberDialog.subscriber.Status = subscriber.entity.status;
                console.log("Subscriber is now " + $scope.showSubscriberDialog.subscriber);
                Core.$apply($scope);

                // now lets start opening the dialog
                setTimeout(function () {
                    $scope.showSubscriberDialog.open();
                    Core.$apply($scope);
                }, 100);
            }));
        };

        $scope.topicNames = function (completionText) {
            var topicsFolder = ActiveMQ.getSelectionTopicsFolder(workspace);
            return (topicsFolder) ? topicsFolder.children.map(function (n) {
                return n.title;
            }) : [];
        };

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;

            // lets defer execution as we may not have the selection just yet
            setTimeout(loadTable, 50);
        });

        function loadTable() {
            var mbean = getBrokerMBean(jolokia);
            if (mbean) {
                $scope.durableSubscribers = [];
                jolokia.request({ type: "read", mbean: mbean, attribute: ["DurableTopicSubscribers"] }, onSuccess(function (response) {
                    return populateTable(response, "DurableTopicSubscribers", "Active");
                }));
                jolokia.request({ type: "read", mbean: mbean, attribute: ["InactiveDurableTopicSubscribers"] }, onSuccess(function (response) {
                    return populateTable(response, "InactiveDurableTopicSubscribers", "Offline");
                }));
            }
        }

        function populateTable(response, attr, status) {
            var data = response.value;
            ActiveMQ.log.debug("Got data: ", data);
            $scope.durableSubscribers.push.apply($scope.durableSubscribers, data[attr].map(function (o) {
                var objectName = o["objectName"];
                var entries = Core.objectNameProperties(objectName);
                if (!('objectName' in o)) {
                    if ('canonicalName' in o) {
                        objectName = o['canonicalName'];
                    }
                    entries = Object.extended(o['keyPropertyList']).clone();
                }

                entries["_id"] = objectName;
                entries["status"] = status;
                return entries;
            }));

            Core.$apply($scope);
        }

        function getBrokerMBean(jolokia) {
            var mbean = null;
            var selection = workspace.selection;
            if (selection && ActiveMQ.isBroker(workspace) && selection.objectName) {
                return selection.objectName;
            }
            var folderNames = selection.folderNames;

            //if (selection && jolokia && folderNames && folderNames.length > 1) {
            var parent = selection ? selection.parent : null;
            if (selection && parent && jolokia && folderNames && folderNames.length > 1) {
                mbean = parent.objectName;

                // we might be a destination, so lets try one more parent
                if (!mbean && parent) {
                    mbean = parent.parent.objectName;
                }
                if (!mbean) {
                    mbean = "" + folderNames[0] + ":BrokerName=" + folderNames[1] + ",Type=Broker";
                }
            }
            return mbean;
        }
    }
    ActiveMQ.DurableSubscriberController = DurableSubscriberController;
})(ActiveMQ || (ActiveMQ = {}));
var ActiveMQ;
(function (ActiveMQ) {
    function BrowseQueueController($scope, workspace, jolokia, localStorage) {
        $scope.searchText = '';

        $scope.messages = [];
        $scope.headers = {};
        $scope.mode = 'text';

        $scope.deleteDialog = false;
        $scope.moveDialog = false;

        $scope.gridOptions = {
            selectedItems: [],
            data: 'messages',
            displayFooter: false,
            showFilter: false,
            showColumnMenu: true,
            enableColumnResize: true,
            enableColumnReordering: true,
            filterOptions: {
                filterText: ''
            },
            selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            maintainColumnRatios: false,
            columnDefs: [
                {
                    field: 'JMSMessageID',
                    displayName: 'Message ID',
                    cellTemplate: '<div class="ngCellText"><a ng-click="openMessageDialog(row)">{{row.entity.JMSMessageID}}</a></div>',
                    // for ng-grid
                    width: '34%'
                },
                {
                    field: 'JMSType',
                    displayName: 'Type',
                    width: '10%'
                },
                {
                    field: 'JMSPriority',
                    displayName: 'Priority',
                    width: '7%'
                },
                {
                    field: 'JMSTimestamp',
                    displayName: 'Timestamp',
                    width: '19%'
                },
                {
                    field: 'JMSExpiration',
                    displayName: 'Expires',
                    width: '10%'
                },
                {
                    field: 'JMSReplyTo',
                    displayName: 'Reply To',
                    width: '10%'
                },
                {
                    field: 'JMSCorrelationID',
                    displayName: 'Correlation ID',
                    width: '10%'
                }
            ]
        };

        $scope.showMessageDetails = false;

        var ignoreColumns = ["PropertiesText", "BodyPreview", "Text"];
        var flattenColumns = [
            "BooleanProperties", "ByteProperties", "ShortProperties", "IntProperties", "LongProperties", "FloatProperties",
            "DoubleProperties", "StringProperties"];

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;

            // lets defer execution as we may not have the selection just yet
            setTimeout(loadTable, 50);
        });

        $scope.openMessageDialog = function (message) {
            var idx = Core.pathGet(message, ["rowIndex"]);
            $scope.selectRowIndex(idx);
            if ($scope.row) {
                $scope.mode = CodeEditor.detectTextFormat($scope.row.Text);
                $scope.showMessageDetails = true;
            }
        };

        $scope.refresh = loadTable;

        $scope.selectRowIndex = function (idx) {
            $scope.rowIndex = idx;
            var selected = $scope.gridOptions.selectedItems;
            selected.splice(0, selected.length);
            if (idx >= 0 && idx < $scope.messages.length) {
                $scope.row = $scope.messages[idx];
                if ($scope.row) {
                    selected.push($scope.row);
                }
            } else {
                $scope.row = null;
            }
        };

        $scope.moveMessages = function () {
            var selection = workspace.selection;
            var mbean = selection.objectName;
            if (mbean && selection) {
                var selectedItems = $scope.gridOptions.selectedItems;
                $scope.message = "Moved " + Core.maybePlural(selectedItems.length, "message" + " to " + $scope.queueName);
                var operation = "moveMessageTo(java.lang.String, java.lang.String)";
                angular.forEach(selectedItems, function (item, idx) {
                    var id = item.JMSMessageID;
                    if (id) {
                        var callback = (idx + 1 < selectedItems.length) ? intermediateResult : moveSuccess;
                        jolokia.execute(mbean, operation, id, $scope.queueName, onSuccess(callback));
                    }
                });
            }
        };

        $scope.deleteMessages = function () {
            var selection = workspace.selection;
            var mbean = selection.objectName;
            if (mbean && selection) {
                var selectedItems = $scope.gridOptions.selectedItems;
                $scope.message = "Deleted " + Core.maybePlural(selectedItems.length, "message");
                var operation = "removeMessage(java.lang.String)";
                angular.forEach(selectedItems, function (item, idx) {
                    var id = item.JMSMessageID;
                    if (id) {
                        var callback = (idx + 1 < selectedItems.length) ? intermediateResult : operationSuccess;
                        jolokia.execute(mbean, operation, id, onSuccess(callback));
                    }
                });
            }
        };

        $scope.retryMessages = function () {
            var selection = workspace.selection;
            var mbean = selection.objectName;
            if (mbean && selection) {
                var selectedItems = $scope.gridOptions.selectedItems;
                $scope.message = "Retry " + Core.maybePlural(selectedItems.length, "message");
                var operation = "retryMessage(java.lang.String)";
                angular.forEach(selectedItems, function (item, idx) {
                    var id = item.JMSMessageID;
                    if (id) {
                        var callback = (idx + 1 < selectedItems.length) ? intermediateResult : operationSuccess;
                        jolokia.execute(mbean, operation, id, onSuccess(callback));
                    }
                });
            }
        };

        $scope.queueNames = function (completionText) {
            var queuesFolder = ActiveMQ.getSelectionQueuesFolder(workspace);
            return (queuesFolder) ? queuesFolder.children.map(function (n) {
                return n.title;
            }) : [];
        };

        function populateTable(response) {
            var data = response.value;
            if (!angular.isArray(data)) {
                $scope.messages = [];
                angular.forEach(data, function (value, idx) {
                    $scope.messages.push(value);
                });
            } else {
                $scope.messages = data;
            }
            angular.forEach($scope.messages, function (message) {
                message.headerHtml = createHeaderHtml(message);
                message.bodyText = createBodyText(message);
            });
            Core.$apply($scope);
        }

        /*
        * For some reason using ng-repeat in the modal dialog doesn't work so lets
        * just create the HTML in code :)
        */
        function createBodyText(message) {
            if (message.Text) {
                var body = message.Text;
                var lenTxt = "" + body.length;
                message.textMode = "text (" + lenTxt + " chars)";
                return body;
            } else if (message.BodyPreview) {
                var code = Core.parseIntValue(localStorage["activemqBrowseBytesMessages"] || "1", "browse bytes messages");
                var body;

                message.textMode = "bytes (turned off)";
                if (code != 99) {
                    var bytesArr = [];
                    var textArr = [];
                    message.BodyPreview.forEach(function (b) {
                        if (code === 1 || code === 2) {
                            // text
                            textArr.push(String.fromCharCode(b));
                        }
                        if (code === 1 || code === 4) {
                            // hex and must be 2 digit so they space out evenly
                            var s = b.toString(16);
                            if (s.length === 1) {
                                s = "0" + s;
                            }
                            bytesArr.push(s);
                        } else {
                            // just show as is without spacing out, as that is usually more used for hex than decimal
                            var s = b.toString(10);
                            bytesArr.push(s);
                        }
                    });

                    var bytesData = bytesArr.join(" ");
                    var textData = textArr.join("");

                    if (code === 1 || code === 2) {
                        // bytes and text
                        var len = message.BodyPreview.length;
                        var lenTxt = "" + textArr.length;
                        body = "bytes:\n" + bytesData + "\n\ntext:\n" + textData;
                        message.textMode = "bytes (" + len + " bytes) and text (" + lenTxt + " chars)";
                    } else {
                        // bytes only
                        var len = message.BodyPreview.length;
                        body = bytesData;
                        message.textMode = "bytes (" + len + " bytes)";
                    }
                }
                return body;
            } else {
                message.textMode = "unsupported";
                return "Unsupported message body type which cannot be displayed by hawtio";
            }
        }

        /*
        * For some reason using ng-repeat in the modal dialog doesn't work so lets
        * just create the HTML in code :)
        */
        function createHeaderHtml(message) {
            var headers = createHeaders(message);
            var properties = createProperties(message);
            var headerKeys = Object.extended(headers).keys();

            function sort(a, b) {
                if (a > b)
                    return 1;
                if (a < b)
                    return -1;
                return 0;
            }

            var propertiesKeys = Object.extended(properties).keys().sort(sort);

            var jmsHeaders = headerKeys.filter(function (key) {
                return key.startsWith("JMS");
            }).sort(sort);

            var remaining = headerKeys.subtract(jmsHeaders, propertiesKeys).sort(sort);

            var buffer = [];

            function appendHeader(key) {
                var value = headers[key];
                if (value === null) {
                    value = '';
                }

                buffer.push('<tr><td class="propertyName"><span class="green">Header</span> - ' + key + '</td><td class="property-value">' + value + '</td></tr>');
            }

            function appendProperty(key) {
                var value = properties[key];
                if (value === null) {
                    value = '';
                }

                buffer.push('<tr><td class="propertyName">' + key + '</td><td class="property-value">' + value + '</td></tr>');
            }

            jmsHeaders.forEach(appendHeader);
            remaining.forEach(appendHeader);
            propertiesKeys.forEach(appendProperty);
            return buffer.join("\n");
        }

        function createHeaders(row) {
            ActiveMQ.log.debug("headers: ", row);
            var answer = {};
            angular.forEach(row, function (value, key) {
                if (!ignoreColumns.any(key) && !flattenColumns.any(key)) {
                    answer[Core.escapeHtml(key)] = Core.escapeHtml(value);
                }
            });
            return answer;
        }

        function createProperties(row) {
            ActiveMQ.log.debug("properties: ", row);
            var answer = {};
            angular.forEach(row, function (value, key) {
                if (!ignoreColumns.any(key) && flattenColumns.any(key)) {
                    angular.forEach(value, function (v2, k2) {
                        answer['<span class="green">' + key.replace('Properties', ' Property') + '</span> - ' + Core.escapeHtml(k2)] = Core.escapeHtml(v2);
                    });
                }
            });
            return answer;
        }

        function loadTable() {
            var selection = workspace.selection;
            if (selection) {
                var mbean = selection.objectName;
                if (mbean) {
                    $scope.dlq = false;
                    jolokia.getAttribute(mbean, "DLQ", onSuccess(onDlq, { silent: true }));
                    jolokia.request({ type: 'exec', mbean: mbean, operation: 'browse()' }, onSuccess(populateTable));
                }
            }
        }

        function onDlq(response) {
            $scope.dlq = response;
            Core.$apply($scope);
        }

        function intermediateResult() {
        }

        function operationSuccess() {
            $scope.messageDialog = false;
            $scope.gridOptions.selectedItems.splice(0);
            notification("success", $scope.message);
            setTimeout(loadTable, 50);
        }

        function moveSuccess() {
            operationSuccess();
            workspace.loadTree();
        }
    }
    ActiveMQ.BrowseQueueController = BrowseQueueController;
})(ActiveMQ || (ActiveMQ = {}));
/**
* @module Site
*/
var Site;
(function (Site) {
    function IndexController($scope, $location) {
        $scope.slideInterval = 5000;
    }
    Site.IndexController = IndexController;
})(Site || (Site = {}));
/**
* @module Site
*/
var Site;
(function (Site) {
    function PageController($scope, $routeParams, $location, $compile, $http, fileExtensionTypeRegistry) {
        var log = Logger.get("Site");
        var pageId = $routeParams["page"];
        if (!pageId) {
            pageId = "README.md";
            /*
            $location.path("/site/doc/index.md");
            return;
            */
        }

        if (!pageId.startsWith("/") && pageId.indexOf(":/") < 0 && pageId.indexOf("app/site/") < 0) {
            // lets assume the page is relative to app/site/
            pageId = "app/site/" + pageId;
        }
        $scope.pageId = pageId;
        $scope.pageFolder = pageId.substring(0, pageId.lastIndexOf('/') + 1);

        log.info("Loading page '" + $scope.pageId + "'");

        $scope.getContents = function (filename, cb) {
            var fullPath = $scope.pageFolder + filename;
            log.info("Loading the contents of: " + fullPath);
            $http.get(fullPath).success(cb).error(function () {
                return cb(" ");
            });
        };

        $http.get($scope.pageId).success(onResults);

        function onResults(contents, status, headers, config) {
            $scope.contents = contents;
            $scope.html = contents;

            var format = Wiki.fileFormat($scope.pageId, fileExtensionTypeRegistry) || "markdown";
            if ("markdown" === format) {
                // lets convert it to HTML
                $scope.html = contents ? marked(contents) : "";
            } else if (format && format.startsWith("html")) {
                $scope.html = contents;
            } else {
                // TODO?
            }
            $compile($scope.html)($scope);
            Core.$apply($scope);
        }
    }
    Site.PageController = PageController;
})(Site || (Site = {}));
/**
* @module Site
*/
var Site;
(function (Site) {
    Site.sitePluginEnabled = false;

    function isSiteNavBarValid() {
        return Site.sitePluginEnabled;
    }
    Site.isSiteNavBarValid = isSiteNavBarValid;
})(Site || (Site = {}));
/**
* @module Site
* @main Site
*/
var Site;
(function (Site) {
    var pluginName = 'site';

    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'datatable', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/site', { templateUrl: 'app/site/html/index.html' }).when('/site/', { templateUrl: 'app/site/html/index.html' }).when('/site/book/*page', { templateUrl: 'app/site/html/book.html', reloadOnSearch: false }).when('/site/*page', { templateUrl: 'app/site/html/page.html' });
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry[pluginName] = layoutFull;

        workspace.topLevelTabs.push({
            id: "site",
            content: "Site",
            title: "View the documentation for Hawtio",
            isValid: function (workspace) {
                return false;
            },
            href: function () {
                return "#/site";
            }
        });
        /*
        helpRegistry.addUserDoc('log', 'app/log/doc/help.md', () => {
        return workspace.treeContainsDomainAndProperties('org.fusesource.insight', {type: 'LogQuery'});
        });
        
        */
    });

    hawtioPluginLoader.addModule(pluginName);
})(Site || (Site = {}));
var Tomcat;
(function (Tomcat) {
    function TomcatController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | tomcatIconClass}}"></i></div>';
        var urlTemplate = '<div class="ngCellText" title="{{row.getProperty(col.field)}}">' + '<a ng-href="{{row.getProperty(col.field)}}" target="_blank">{{row.getProperty(col.field)}}</a>' + '</div>';

        $scope.uninstallDialog = new UI.Dialog();

        $scope.httpPort;
        $scope.httpScheme = "http";

        $scope.webapps = [];
        $scope.selected = [];

        var columnDefsTomcat5 = [
            {
                field: 'state',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'path',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'url',
                displayName: 'Url',
                cellTemplate: urlTemplate,
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'startTime',
                displayName: 'Start Time',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        var columnDefsTomcat6 = [
            {
                field: 'stateName',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'path',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'url',
                displayName: 'Url',
                cellTemplate: urlTemplate,
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'startTime',
                displayName: 'Start Time',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        var columnDefsTomcat7 = [
            {
                field: 'stateName',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'path',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'displayName',
                displayName: 'Display Name',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'url',
                displayName: 'Url',
                cellTemplate: urlTemplate,
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'startTime',
                displayName: 'Start Time',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'webapps',
            displayFooter: true,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            filterOptions: {
                filterText: ''
            }
        };

        /*        function extractHttpPort(response) {
        var obj = response;
        if (obj) {
        angular.forEach(obj, function (key, value) {
        var mbean = key;
        jolokia.request({type: "read", mbean: mbean, attribute: ["port", "scheme", "protocol"]}, onSuccess(onHttpPort));
        });
        }
        }
        
        function onHttpPort(response) {
        // we only need the HTTP protocol
        var obj = response.value;
        if (obj && obj.protocol && obj.protocol.toString().startsWith("HTTP")) {
        $scope.httpPort = obj.port;
        $scope.httpScheme = obj.scheme;
        }
        }
        */
        function render(response) {
            response = Tomcat.filerTomcatOrCatalina(response);

            $scope.webapps = [];
            $scope.mbeanIndex = {};
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    var mbean = obj.mbean;

                    // compute the url for the webapp, and we want to use http as scheme
                    var hostname = Core.extractTargetUrl($location, $scope.httpScheme, $scope.httpPort);
                    obj.url = hostname + obj['path'];

                    if (mbean) {
                        // format the start time as readable date format
                        obj.startTime = Tomcat.millisToDateFormat(obj.startTime);

                        var idx = $scope.mbeanIndex[mbean];
                        if (angular.isDefined(idx)) {
                            $scope.webapps[mbean] = obj;
                        } else {
                            $scope.mbeanIndex[mbean] = $scope.webapps.length;
                            $scope.webapps.push(obj);
                        }

                        // ensure web page is updated
                        Core.$apply($scope);
                    }
                }
            }

            angular.forEach(response, function (value, key) {
                var mbean = value;
                if (Tomcat.isTomcat5($scope.tomcatServerVersion)) {
                    jolokia.request({
                        type: "read", mbean: mbean,
                        attribute: ["path", "state", "startTime"] }, onSuccess(onAttributes));
                } else if (Tomcat.isTomcat6($scope.tomcatServerVersion)) {
                    jolokia.request({
                        type: "read", mbean: mbean,
                        attribute: ["path", "stateName", "startTime"] }, onSuccess(onAttributes));
                } else {
                    jolokia.request({
                        type: "read", mbean: mbean,
                        attribute: ["displayName", "path", "stateName", "startTime"] }, onSuccess(onAttributes));
                }
            });
            Core.$apply($scope);
        }
        ;

        // function to control the web applications
        $scope.controlWebApps = function (op) {
            // grab id of mbean names to control
            var mbeanNames = $scope.selected.map(function (b) {
                return b.mbean;
            });
            if (!angular.isArray(mbeanNames)) {
                mbeanNames = [mbeanNames];
            }

            // execute operation on each mbean
            var lastIndex = (mbeanNames.length || 1) - 1;
            angular.forEach(mbeanNames, function (mbean, idx) {
                var onResponse = (idx >= lastIndex) ? $scope.onLastResponse : $scope.onResponse;
                jolokia.request({
                    type: 'exec',
                    mbean: mbean,
                    operation: op,
                    arguments: null
                }, onSuccess(onResponse, { error: onResponse }));
            });
        };

        $scope.stop = function () {
            $scope.controlWebApps('stop');
        };

        $scope.start = function () {
            $scope.controlWebApps('start');
        };

        $scope.reload = function () {
            $scope.controlWebApps('reload');
        };

        $scope.uninstall = function () {
            $scope.controlWebApps('destroy');
            $scope.uninstallDialog.close();
        };

        // function to trigger reloading page
        $scope.onLastResponse = function (response) {
            $scope.onResponse(response);

            // we only want to force updating the data on the last response
            loadData();
        };

        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
        };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading tomcat webapp data...");

            // must load connectors first, before showing applications, so we do this call synchronously
            var connectors = jolokia.search("Catalina:type=Connector,*");
            if (connectors) {
                var found = false;
                angular.forEach(connectors, function (key, value) {
                    var mbean = key;
                    if (!found) {
                        var data = jolokia.request({ type: "read", mbean: mbean, attribute: ["port", "scheme", "protocol"] });
                        if (data && data.value && data.value.protocol && data.value.protocol.toString().toLowerCase().startsWith("http")) {
                            found = true;
                            $scope.httpPort = data.value.port;
                            $scope.httpScheme = data.value.scheme;
                        }
                    }
                });
            }
            jolokia.search("*:j2eeType=WebModule,*", onSuccess(render));
        }

        // grab server information once
        $scope.tomcatServerVersion = "";

        var servers = jolokia.search("*:type=Server");
        servers = Tomcat.filerTomcatOrCatalina(servers);
        if (servers && servers.length === 1) {
            $scope.tomcatServerVersion = jolokia.getAttribute(servers[0], "serverInfo");
        } else {
            console.log("Cannot find Tomcat server or there was more than one server. response is: " + servers);
        }

        // the columns shown in the applications view depends on the Tomcat version in use
        if (Tomcat.isTomcat5($scope.tomcatServerVersion)) {
            console.log("Using Tomcat 5");
            $scope.gridOptions.columnDefs = columnDefsTomcat5;
        } else if (Tomcat.isTomcat6($scope.tomcatServerVersion)) {
            console.log("Using Tomcat 6");
            $scope.gridOptions.columnDefs = columnDefsTomcat6;
        } else {
            console.log("Using Tomcat 7");
            $scope.gridOptions.columnDefs = columnDefsTomcat7;
        }
    }
    Tomcat.TomcatController = TomcatController;
})(Tomcat || (Tomcat = {}));
var Tomcat;
(function (Tomcat) {
    function filerTomcatOrCatalina(response) {
        if (response) {
            // Tomcat can have mbean server names with Catalina or Tomcat
            response = response.filter(function (name) {
                return name.startsWith("Catalina") || name.startsWith("Tomcat");
            });
        }
        return response;
    }
    Tomcat.filerTomcatOrCatalina = filerTomcatOrCatalina;
    ;

    function iconClass(state) {
        if (state) {
            switch (state.toString().toLowerCase()) {
                case '1':
                    return "green icon-play-circle";
                case 'started':
                    return "green icon-play-circle";
                case '0':
                    return "orange icon-off";
                case 'stopped':
                    return "orange icon-off";
            }
        }

        // Tomcat 5 uses 0 for stopped
        if (angular.isNumber(state)) {
            if (state.toString() === '0') {
                return "orange icon-off";
            }
        }

        return "icon-question-sign";
    }
    Tomcat.iconClass = iconClass;

    function millisToDateFormat(time) {
        if (time) {
            var date = new Date(time);
            return date.toLocaleDateString() + " " + date.toLocaleTimeString();
        } else {
            return "";
        }
    }
    Tomcat.millisToDateFormat = millisToDateFormat;

    function isTomcat5(name) {
        return name.toString().indexOf("Apache Tomcat/5") !== -1;
    }
    Tomcat.isTomcat5 = isTomcat5;

    function isTomcat6(name) {
        return name.toString().indexOf("Apache Tomcat/6") !== -1;
    }
    Tomcat.isTomcat6 = isTomcat6;
})(Tomcat || (Tomcat = {}));
var Tomcat;
(function (Tomcat) {
    function ConnectorsController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | tomcatIconClass}}"></i></div>';

        $scope.connectors = [];
        $scope.selected = [];

        var columnDefs = [
            {
                field: 'stateName',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'port',
                displayName: 'Port',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'scheme',
                displayName: 'Scheme',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'protocol',
                displayName: 'Protocol',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'secure',
                displayName: 'Secure',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'connectionLinger',
                displayName: 'Connection Linger',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'connectionTimeout',
                displayName: 'Connection Timeout',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'keepAliveTimeout',
                displayName: 'Keep Alive Timeout',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'minSpareThreads',
                displayName: 'Minimum Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'maxThreads',
                displayName: 'Maximum Threads',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'connectors',
            displayFooter: true,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        function render(response) {
            response = Tomcat.filerTomcatOrCatalina(response);

            $scope.connectors = [];
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    $scope.connectors.push(obj);
                    Core.$apply($scope);
                }
            }

            // create structure for each response
            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({
                    type: "read", mbean: mbean, attribute: [
                        "scheme", "port", "protocol", "secure",
                        "connectionLinger", "connectionTimeout", "keepAliveTimeout", "minSpareThreads", "maxThreads", "stateName"] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        // function to control the connectors
        $scope.controlConnector = function (op) {
            // grab id of mbean names to control
            var ids = $scope.selected.map(function (b) {
                return b.mbean;
            });
            if (!angular.isArray(ids)) {
                ids = [ids];
            }

            // execute operation on each mbean
            ids.forEach(function (id) {
                jolokia.request({
                    type: 'exec',
                    mbean: id,
                    operation: op,
                    arguments: null
                }, onSuccess($scope.onResponse, { error: $scope.onResponse }));
            });
        };

        $scope.stop = function () {
            $scope.controlConnector('stop');
        };

        $scope.start = function () {
            $scope.controlConnector('start');
        };

        $scope.destroy = function () {
            $scope.controlConnector('destroy');
        };

        // function to trigger reloading page
        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
            loadData();
        };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading tomcat connector data...");
            jolokia.search("Catalina:type=Connector,*", onSuccess(render));
        }
    }
    Tomcat.ConnectorsController = ConnectorsController;
})(Tomcat || (Tomcat = {}));
var Tomcat;
(function (Tomcat) {
    function SessionsController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | tomcatIconClass}}"></i></div>';

        $scope.sessions = [];
        $scope.search = "";

        var columnDefs = [
            {
                field: 'stateName',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'path',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'activeSessions',
                displayName: 'Active Sessions',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'expiredSessions',
                displayName: 'Expired Sessions',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'rejectedSessions',
                displayName: 'Rejected Sessions',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'maxActive',
                displayName: 'Max Active Sessions',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'maxActiveSessions',
                displayName: 'Max Active Sessions Allowed',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'maxInactiveInterval',
                displayName: 'Max Inactive Interval',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'sessionCounter',
                displayName: 'Session Counter',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'sessionCreateRate',
                displayName: 'Session Create Rate',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'sessionExpireRate',
                displayName: 'Session Expire Rate',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'sessions',
            displayFooter: false,
            displaySelectionCheckbox: false,
            canSelectRows: false,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        function render(response) {
            response = Tomcat.filerTomcatOrCatalina(response);

            $scope.sessions = [];

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    var mbean = obj.mbean;
                    if (mbean) {
                        // the context path is part of the mbean name
                        // grab the 2nd part of the mbean that has context=/name
                        var context = mbean.toString().split(",")[1];
                        if (context) {
                            if (context.toString().indexOf("path=") !== -1) {
                                // and remove the leading path=/ from the name (Tomcat 5 or 6)
                                obj.path = context.toString().substr(5);
                            } else {
                                // and remove the leading context=/ from the name (Tomcat 7)
                                obj.path = context.toString().substr(9);
                            }
                        } else {
                            obj.path = "";
                        }

                        $scope.sessions.push(obj);
                        Core.$apply($scope);
                    }
                }
            }

            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading tomcat session data...");
            jolokia.search("*:type=Manager,*", onSuccess(render));
        }
    }
    Tomcat.SessionsController = SessionsController;
})(Tomcat || (Tomcat = {}));
/**
* @module Tomcat
* @main Tomcat
*/
var Tomcat;
(function (Tomcat) {
    var pluginName = 'tomcat';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ui.bootstrap.dialog', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/tomcat/server', { templateUrl: 'app/tomcat/html/server.html' }).when('/tomcat/applications', { templateUrl: 'app/tomcat/html/applications.html' }).when('/tomcat/connectors', { templateUrl: 'app/tomcat/html/connectors.html' }).when('/tomcat/sessions', { templateUrl: 'app/tomcat/html/sessions.html' });
    }).filter('tomcatIconClass', function () {
        return Tomcat.iconClass;
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['tomcat'] = "app/tomcat/html/layoutTomcatTabs.html";
        helpRegistry.addUserDoc('tomcat', 'app/tomcat/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("Tomcat") || workspace.treeContainsDomainAndProperties("Catalina");
        });

        workspace.topLevelTabs.push({
            id: "tomcat",
            content: "Tomcat",
            title: "Manage your Tomcat container",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("Tomcat") || workspace.treeContainsDomainAndProperties("Catalina");
            },
            href: function () {
                return "#/tomcat/applications";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("tomcat");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Tomcat || (Tomcat = {}));
var Camin;
(function (Camin) {
    var Gantt = (function () {
        function Gantt() {
            this.resources = [];
            this.tasks = [];
            this.links = [];
        }
        Gantt.prototype.resource = function (data) {
            var resource = new Resource(data, this.resources.length);
            this.resources.push(resource);
            return resource;
        };

        Gantt.prototype.task = function (resource, start, stop, data) {
            var task = resource.task(start, stop, data);
            this.tasks.push(task);
            return task;
        };

        Gantt.prototype.link = function (start, taskA, stop, taskB, data) {
            var link = new Link(start, taskA, stop, taskB, data);
            this.links.push(link);
            return link;
        };

        Gantt.prototype.layout = function () {
            for (var i = 0; i < this.resources.length; i++) {
                this.resources[i].layout();
                this.start = this.start ? Math.min(this.start, this.resources[i].start) : this.resources[i].start;
                this.stop = this.stop ? Math.max(this.stop, this.resources[i].stop) : this.resources[i].stop;
            }
            for (var i = 0; i < this.links.length; i++) {
                this.start = this.start ? Math.min(this.start, this.links[i].start) : this.links[i].start;
                this.stop = this.stop ? Math.max(this.stop, this.links[i].stop) : this.links[i].stop;
            }
        };

        Gantt.prototype.taskByData = function (data) {
            for (var i = 0; i < this.tasks.length; i++) {
                if (this.tasks[i].data === data) {
                    return this.tasks[i];
                }
            }
            return undefined;
        };
        return Gantt;
    })();
    Camin.Gantt = Gantt;

    var Resource = (function () {
        function Resource(data, index) {
            this.tasks = [];
            this.index = index;
            this.data = data;
        }
        Resource.prototype.task = function (start, stop, data) {
            var task = new Task(start, stop, data, this);
            this.tasks.push(task);
            return task;
        };

        Resource.prototype.layout = function () {
            this.tasks.sort(function (ta, tb) {
                return ta.start - tb.start;
            });
            var bands = [];
            for (var i = 0; i < this.tasks.length; i++) {
                this.start = this.start ? Math.min(this.start, this.tasks[i].start) : this.tasks[i].start;
                this.stop = this.stop ? Math.max(this.stop, this.tasks[i].stop) : this.tasks[i].stop;
                for (var j = 0; j < bands.length; j++) {
                    if (bands[j] < this.tasks[i].start) {
                        bands[j] = this.tasks[i].stop;
                        this.tasks[i].index = j;
                        break;
                    }
                }
                if (!this.tasks[i].index) {
                    var index = bands.length;
                    this.tasks[i].index = index;
                    bands[index] = this.tasks[i].stop;
                }
            }
            for (var i = 0; i < this.tasks.length; i++) {
                this.tasks[i].max = bands.length;
            }
        };
        return Resource;
    })();
    Camin.Resource = Resource;

    var Task = (function () {
        function Task(start, stop, data, resource) {
            this.start = start;
            this.stop = stop;
            this.data = data;
            this.resource = resource;
        }
        return Task;
    })();
    Camin.Task = Task;

    var Link = (function () {
        function Link(start, taskA, stop, taskB, data) {
            this.start = start;
            this.stop = stop;
            this.taskA = taskA;
            this.taskB = taskB;
            this.data = data;
        }
        return Link;
    })();
    Camin.Link = Link;
})(Camin || (Camin = {}));
var Camin;
(function (Camin) {
    var Sequence = (function () {
        function Sequence() {
            this.endpoints = [];
            this.execs = [];
            this.calls = [];
        }
        Sequence.prototype.endpoint = function (url, routeId, contextId, host) {
            for (var i = 0; i < this.endpoints.length; i++) {
                if (this.endpoints[i].url === url && this.endpoints[i].routeId === routeId && this.endpoints[i].contextId === contextId && this.endpoints[i].host === host) {
                    return this.endpoints[i];
                }
            }
            var endpoint = new Endpoint(url, routeId, contextId, host);
            this.endpoints.push(endpoint);
            return endpoint;
        };

        Sequence.prototype.exec = function (exchangeId, endpoint, start, stop) {
            var exec = new Execution(exchangeId, endpoint, start, stop);
            this.execs.push(exec);
            return exec;
        };

        Sequence.prototype.call = function (callId, execA, execB, start, stop) {
            var call = new Call(callId, execA, execB, start, stop);
            this.calls.push(call);
            return call;
        };

        Sequence.prototype.start = function () {
            var start;
            for (var i = 0; i < this.execs.length; i++) {
                start = start ? Math.min(start, this.execs[i].start) : this.execs[i].start;
            }
            for (var i = 0; i < this.calls.length; i++) {
                start = start ? Math.min(start, this.calls[i].start) : this.calls[i].start;
            }
            return start;
        };

        Sequence.prototype.stop = function () {
            var stop;
            for (var i = 0; i < this.execs.length; i++) {
                stop = stop ? Math.max(stop, this.execs[i].stop) : this.execs[i].stop;
            }
            for (var i = 0; i < this.calls.length; i++) {
                stop = stop ? Math.max(stop, this.calls[i].stop) : this.calls[i].stop;
            }
            return stop;
        };
        return Sequence;
    })();
    Camin.Sequence = Sequence;

    var Endpoint = (function () {
        function Endpoint(url, routeId, contextId, host) {
            this.url = url;
            this.routeId = routeId;
            this.contextId = contextId;
            this.host = host;
        }
        return Endpoint;
    })();
    Camin.Endpoint = Endpoint;

    var Execution = (function () {
        function Execution(exchangeId, endpoint, start, stop) {
            this.exchangeId = exchangeId;
            this.endpoint = endpoint;
            this.start = start;
            this.stop = stop;
        }
        return Execution;
    })();
    Camin.Execution = Execution;

    var Call = (function () {
        function Call(callId, execA, execB, start, stop) {
            this.callId = callId;
            this.execA = execA;
            this.execB = execB;
            this.start = start;
            this.stop = stop;
        }
        return Call;
    })();
    Camin.Call = Call;
})(Camin || (Camin = {}));
/**
* Camel Insight Plugin
*
* @module Camin
* @main Camin
*/
var Camin;
(function (Camin) {
    var pluginName = 'camin';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/camin', { templateUrl: 'app/camin/html/camin.html' }).when('/camin/:exchangeId', { templateUrl: 'app/camin/html/camin.html' });
    }).run(function (workspace, viewRegistry, helpRegistry) {
        viewRegistry["camin"] = "app/camin/html/layoutCamin.html";
        helpRegistry.addUserDoc('camin', 'app/camin/doc/help.md', function () {
            return Fabric.hasFabric(workspace);
        });

        workspace.topLevelTabs.push({
            id: "camin",
            content: "Camel",
            title: "Insight into Camel",
            isValid: function (workspace) {
                return Fabric.hasFabric(workspace);
            },
            href: function () {
                return "#/camin";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("camin");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Camin || (Camin = {}));
var Camin;
(function (Camin) {
    function Controller($scope, jolokia, localStorage, $routeParams) {
        $scope.query = "";
        $scope.result = "";
        $scope.breadcrumbs = [];

        $scope.onQueryChange = function () {
            $scope.result = "Querying exchanges related to " + $scope.query;
            $scope.breadcrumbs = [$scope.query];
            request();
        };

        var request = function () {
            var queryStr = "exchange.id:\"" + $scope.breadcrumbs.join("\" or exchange.id:\"") + "\" or " + "exchange.in.headers.ExtendedBreadcrumb:\"" + $scope.breadcrumbs.join("\" or exchange.in.headers.ExtendedBreadcrumb:\"") + "\" or " + "exchange.out.headers.ExtendedBreadcrumb:\"" + $scope.breadcrumbs.join("\" or exchange.out.headers.ExtendedBreadcrumb:\"") + "\"";
            var query = {
                "query": { "query_string": { "query": queryStr } },
                "fields": ["exchange.id", "exchange.in.headers.ExtendedBreadcrumb", "exchange.out.headers.ExtendedBreadcrumb"],
                "from": 0,
                "size": 1000
            };
            var jreq = {
                type: 'exec',
                mbean: 'org.elasticsearch:service=restjmx',
                operation: 'exec',
                arguments: ['POST', '/_all/camel/_search', angular.toJson(query)] };
            jolokia.request(jreq, {
                method: 'POST',
                error: function (response) {
                    $scope.result = $scope.result + "<br/>" + "Error: " + angular.toJson(response);
                },
                success: function (response) {
                    var data = jQuery.parseJSON(response.value);
                    var oldsize = $scope.breadcrumbs.length;
                    for (var i = 0; i < data['hits']['hits'].length; i++) {
                        var fields = data['hits']['hits'][i].fields;
                        var concat = function (breadcrumbs) {
                            if (breadcrumbs) {
                                if (typeof breadcrumbs === 'string') {
                                    breadcrumbs = [breadcrumbs];
                                }
                                for (var j = 0; j < breadcrumbs.length; j++) {
                                    var id = breadcrumbs[j];
                                    if ($scope.breadcrumbs.indexOf(id) < 0) {
                                        $scope.breadcrumbs.push(id);
                                    }
                                }
                            }
                        };
                        concat(fields["exchange.in.headers.ExtendedBreadcrumb"]);
                        concat(fields["exchange.out.headers.ExtendedBreadcrumb"]);
                    }
                    $scope.result = $scope.result + "<br/>" + "Found " + data.hits.total + " ids";
                    if (oldsize != $scope.breadcrumbs.length) {
                        request();
                    } else {
                        var ids = [];
                        for (var i = 0; i < data['hits']['hits'].length; i++) {
                            var id = data['hits']['hits'][i].fields["exchange.id"];
                            if (ids.indexOf(id) < 0) {
                                ids.push(id);
                            }
                        }
                        var queryStr = "exchange.id:\"" + ids.join("\" or exchange.id:\"") + "\"";
                        $scope.result = $scope.result + "<br/>" + query;
                        var query = {
                            "query": { "query_string": { "query": queryStr } },
                            "from": 0,
                            "size": 1000,
                            "sort": ["timestamp"]
                        };
                        var jreq = {
                            type: 'exec',
                            mbean: 'org.elasticsearch:service=restjmx',
                            operation: 'exec',
                            arguments: ['POST', '/_all/camel/_search', angular.toJson(query)] };
                        jolokia.request(jreq, {
                            method: 'POST',
                            error: function (response) {
                                $scope.result = $scope.result + "<br/>" + "Error: " + angular.toJson(response);
                            },
                            success: function (response) {
                                var data = jQuery.parseJSON(response.value);
                                $scope.result = $scope.result + "<br/>" + "Found " + data['hits']['total'] + " exchanges";
                                var events = [];
                                for (var i = 0; i < data['hits']['hits'].length; i++) {
                                    var e = data['hits']['hits'][i]._source;
                                    events.push(e);
                                }
                                draw(events);
                            }
                        });
                    }
                } });
        };

        var isoDate = function (date) {
            var timestamp, struct, minutesOffset = 0;
            var numericKeys = [1, 4, 5, 6, 7, 10, 11];

            // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string
            // before falling back to any implementation-specific date parsing, so that’s what we do, even if native
            // implementations could be faster
            //              1 YYYY                2 MM       3 DD           4 HH    5 mm       6 ss        7 msec        8 Z 9 ±    10 tzHH    11 tzmm
            if ((struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date))) {
                for (var i = 0, k; (k = numericKeys[i]); ++i) {
                    struct[k] = +struct[k] || 0;
                }

                // allow undefined days and months
                struct[2] = (+struct[2] || 1) - 1;
                struct[3] = +struct[3] || 1;

                if (struct[8] !== 'Z' && struct[9] !== undefined) {
                    minutesOffset = struct[10] * 60 + struct[11];

                    if (struct[9] === '+') {
                        minutesOffset = 0 - minutesOffset;
                    }
                }

                timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]);
            } else {
                timestamp = Date.parse(date);
            }

            return timestamp;
        };

        var buildSequence = function (events) {
            var sequence = new Camin.Sequence();
            var exchangeToExec = {};

            // Sort events
            events = events.sort(function (a, b) {
                return isoDate(a.timestamp) - isoDate(b.timestamp);
            });

            for (var i = 0; i < events.length; i++) {
                if (events[i].event === 'Created') {
                    var evtCreated = events[i];
                    var evtCompleted = null;
                    for (var j = 0; j < events.length; j++) {
                        if (events[j].event === 'Completed' && evtCreated.exchange.id === events[j].exchange.id) {
                            evtCompleted = events[j];
                            break;
                        }
                    }
                    if (evtCompleted === null) {
                        console.log('Could not find matching Completed exchange for ' + evtCreated.exchange.id);
                        continue;
                    }

                    // We use the completed event here because the created event may miss the routeId information
                    var endpoint = sequence.endpoint(evtCompleted.exchange.fromEndpoint, evtCompleted.exchange.routeId, evtCompleted.exchange.contextId, evtCompleted.host);
                    var exec = sequence.exec(evtCreated.exchange.id, endpoint, isoDate(evtCreated.timestamp), isoDate(evtCompleted.timestamp));
                    exchangeToExec[evtCreated.exchange.id] = exec;
                }
            }

            // Extract calls
            var calls = {};
            for (var i = 0; i < events.length; i++) {
                if (events[i].event === 'Sending' && events[i].exchange.in && events[i].exchange.in.headers) {
                    var callId = events[i].exchange.in.headers.AuditCallId;
                    if (callId && calls[callId] === undefined) {
                        var evtSending = events[i];
                        var evtSent = null;
                        var evtCreated = null;
                        for (var j = 0; j < events.length; j++) {
                            if (events[j].event === 'Sent' && evtSending.exchange.id === events[j].exchange.id && events[j].exchange.in.headers.AuditCallId === callId) {
                                evtSent = events[j];
                                break;
                            }
                        }
                        for (var j = 0; j < events.length; j++) {
                            if (events[j].event === 'Created' && evtSending.exchange.id !== events[j].exchange.id && events[j].exchange.in.headers.AuditCallId === callId) {
                                evtCreated = events[j];
                                break;
                            }
                        }
                        var execA = exchangeToExec[evtSending.exchange.id];
                        var execB = evtCreated ? exchangeToExec[evtCreated.exchange.id] : null;
                        if (evtSent !== null && evtCreated !== null && execA !== null && execB != null) {
                            var call = sequence.call(callId, execA, execB, isoDate(evtSending.timestamp), isoDate(evtSent.timestamp));
                            calls[callId] = call;
                        } else {
                            console.log("Could not find Execution for exchange " + evtSending.exchange.id);
                        }
                    }
                }
            }
            return sequence;
        };

        var buildDiagram = function (sequence) {
            var diagram = new Camin.Diagram();
            var actors = {};
            var signals = [];
            var base = sequence.start();
            for (var i = 0; i < sequence.endpoints.length; i++) {
                var actor = diagram.actor("ep" + i);
                var ep = sequence.endpoints[i];
                var key = ep.url + "|" + ep.routeId + "|" + ep.contextId + "|" + ep.host;
                actors[key] = actor;
            }
            for (var i = 0; i < sequence.calls.length; i++) {
                var call = sequence.calls[i];
                if (call.execB) {
                    var epA = call.execA.endpoint;
                    var keyA = epA.url + "|" + epA.routeId + "|" + epA.contextId + "|" + epA.host;
                    var epB = call.execB.endpoint;
                    var keyB = epB.url + "|" + epB.routeId + "|" + epB.contextId + "|" + epB.host;
                    var actorA = actors[keyA];
                    var actorB = actors[keyB];
                    var start1 = call.start - base;
                    var stop1 = call.execB.start - base;
                    var start2 = call.execB.stop - base;
                    var stop2 = call.stop - base;
                    signals.push({
                        actorA: actorA,
                        actorB: actorB,
                        message: start1 + "ms - " + stop1 + "ms",
                        timestamp: start1 });
                    signals.push({
                        actorA: actorB,
                        actorB: actorA,
                        message: start2 + "ms - " + stop2 + "ms",
                        timestamp: start2 });
                }
            }
            signals = signals.sort(function (a, b) {
                return a.timestamp - b.timestamp;
            });
            for (var i = 0; i < signals.length; i++) {
                diagram.signal(signals[i].actorA, signals[i].actorB, signals[i].message);
            }
            return diagram;
        };

        var buildGantt = function (sequence) {
            var gantt = new Camin.Gantt();
            for (var i = 0; i < sequence.endpoints.length; i++) {
                var endpoint = sequence.endpoints[i];
                var resource = gantt.resource(endpoint);
                for (var j = 0; j < sequence.execs.length; j++) {
                    var exec = sequence.execs[j];
                    if (exec.endpoint === endpoint) {
                        gantt.task(resource, exec.start, exec.stop, exec);
                    }
                }
            }
            for (var i = 0; i < sequence.calls.length; i++) {
                var call = sequence.calls[i];
                if (call.execB) {
                    var taskA = gantt.taskByData(call.execA);
                    var taskB = gantt.taskByData(call.execB);
                    gantt.link(call.start, taskA, call.stop, taskB, call);
                }
            }
            gantt.layout();
            return gantt;
        };

        var eventTypeValue = { "Created": 0, "Sending": 1, "Sent": 2, "Completed": 3 };

        var draw = function (events) {
            $scope.definition = "";

            events = events.sort(function (a, b) {
                return isoDate(a.timestamp) - isoDate(b.timestamp);
            });
            console.log(events);

            var sequence = buildSequence(events);
            console.log(sequence);

            var gantt = buildGantt(sequence);
            console.log(gantt);
            $('#gantt').html('');
            drawGantt('#gantt', gantt);

            var diagram = buildDiagram(sequence);
            console.log(diagram);
            $('#diagram').html('');
            drawDiagram('#diagram', diagram);
        };

        var drawDiagram = function (container, diagram) {
            var arrow_size = 10;
            var margin = 10;
            var actor_width = 100;
            var actor_margin = 30;
            var actor_height = 40;
            var signal_height = 30;
            var actor_font = 20;
            var signal_font = 14;
            var width = diagram.actors.length * (actor_width + actor_margin * 2);
            var height = (diagram.signals.length + 1) * signal_height + actor_height * 2 + margin * 2;

            var svg = d3.select(container).append('svg').attr('width', width + 2 * margin).attr('height', height + 2 * margin);
            var g = svg.append('g').attr('text-anchor', 'middle');
            for (var i = 0; i < diagram.actors.length; i++) {
                var actor = diagram.actors[i];
                var gu = g.append('g').attr('transform', 'translate(' + (i * (actor_width + actor_margin * 2) + actor_margin) + ',' + actor_height + ')');
                gu.append('rect').attr('width', actor_width).attr('height', actor_height).attr('stroke', '#000').attr('stroke-width', '2').attr('fill', '#FFFFFF');
                gu.append('text').attr('x', actor_width / 2).attr('y', actor_height / 2).attr('stroke-width', '0').attr('dominant-baseline', 'middle').attr('font-size', actor_font).text(actor.name);

                g.append('line').attr('x1', i * (actor_width + actor_margin * 2) + actor_width / 2 + actor_margin).attr('y1', actor_height * 2).attr('x2', i * (actor_width + actor_margin * 2) + actor_width / 2 + actor_margin).attr('y2', height - actor_height).attr('stroke', '#000').attr('stroke-width', '2');

                var gu = g.append('g').attr('transform', 'translate(' + (i * (actor_width + actor_margin * 2) + actor_margin) + ',' + (height - actor_height) + ')');
                gu.append('rect').attr('width', actor_width).attr('height', actor_height).attr('stroke', '#000').attr('stroke-width', '2').attr('fill', 'white');
                gu.append('text').attr('x', actor_width / 2).attr('y', actor_height / 2).attr('stroke-width', '0').attr('dominant-baseline', 'middle').attr('font-size', actor_font).text(actor.name);
            }
            for (var i = 0; i < diagram.signals.length; i++) {
                var x;
                var y;
                var length;
                var direction;
                var text;

                x = diagram.signals[i].actorA.index * (actor_width + actor_margin * 2) + actor_width / 2 + actor_margin;
                y = (i + 1) * signal_height + actor_height * 2;
                length = Math.abs(diagram.signals[i].actorA.index - diagram.signals[i].actorB.index) * (actor_width + actor_margin * 2);
                direction = diagram.signals[i].actorB.index > diagram.signals[i].actorA.index ? +1 : -1;
                text = diagram.signals[i].message;

                var gu = g.append('g').attr('transform', 'translate(' + x + ',' + y + ')').attr('stroke-width', '2');
                gu.append('rect').attr('x', Math.min(3, length * direction + 3)).attr('y', '-16').attr('width', Math.abs((length - 6) * direction)).attr('height', '19').attr('stroke', 'white').attr('stroke-width', '0').attr('fill', 'white');
                gu.append('line').attr('x1', 0).attr('y1', 0).attr('x2', length * direction).attr('y2', 0).attr('stroke', '#000').attr('stroke-width', '2');
                gu.append('line').attr('x1', length * direction - arrow_size * direction).attr('y1', -arrow_size).attr('x2', length * direction).attr('y2', 0).attr('stroke', '#000').attr('stroke-width', '2');
                gu.append('line').attr('x1', length * direction).attr('y1', 0).attr('x2', length * direction - arrow_size * direction).attr('y2', arrow_size).attr('stroke', '#000').attr('stroke-width', '2');
                gu.append('text').attr('x', length * direction / 2).attr('y', -8).attr('stroke-width', '0').attr('dominant-baseline', 'middle').attr('font-size', signal_font).text(text);
            }
        };

        var drawGantt = function (container, gantt) {
            var lineHeight = 35;
            var lineMargin = 3;
            var arrowWidth = 4;

            var width = 800;
            var height = lineHeight * gantt.resources.length;
            var margin = {
                top: 20,
                right: 40,
                bottom: 20,
                left: 250
            };

            var begin = gantt.start;
            var end = gantt.stop;

            var x = d3.scale.linear().domain([begin - (end - begin) * 0.1, end + (end - begin) * 0.1]).range([0, width]);
            var yt = function (t) {
                return t.resource.index * lineHeight + lineMargin + t.index * (lineHeight - 2 * lineMargin) / (t.max + 1);
            };
            var ht = function (t) {
                return 2 * (lineHeight - 2 * lineMargin) / (t.max + 1);
            };

            var svg = d3.select(container).append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom);

            var text = svg.append('g').attr('width', width).attr('height', height).attr('transform', 'translate(0,' + margin.top + ')').selectAll('text').data(gantt.resources).enter();
            text.append('text').attr('x', 0).attr('y', function (r) {
                return r.index * lineHeight + lineHeight / 2;
            }).attr('dy', '-0.2em').attr('text-anchor', 'start').text(function (r) {
                var endpoint = r.data;
                var text = endpoint.url;
                if (text.indexOf("Endpoint[") == 0) {
                    text = text.substring(9, text.length - 1);
                }
                return text;
            });
            text.append('text').attr('x', 0).attr('y', function (r) {
                return r.index * lineHeight + lineHeight / 2;
            }).attr('dy', '0.8em').attr('text-anchor', 'start').text(function (r) {
                var endpoint = r.data;
                return endpoint.host + "/" + endpoint.contextId + "/" + endpoint.routeId;
            });

            var g = svg.append('g').attr('width', width).attr('height', height).attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            g.append('g').attr('width', width).attr('height', height).selectAll('rect').data(gantt.tasks).enter().append('rect').attr('rx', lineMargin * 2).attr('ry', lineMargin * 2).attr('x', function (t) {
                return x(t.start);
            }).attr('y', yt).attr('height', ht).attr('width', function (t) {
                return x(t.stop) - x(t.start);
            }).attr('stroke', '#000000').attr('stroke-width', '2').attr('fill', function (t) {
                return d3.hsl(Math.random() * 360, 0.8, 0.8).toString();
            });

            var lines = g.append('g').attr('width', width).attr('height', height).attr('stroke', '#404040').attr('stroke-width', '2').selectAll('line').data(gantt.links).enter();
            lines.append('line').attr('x1', function (l) {
                return x(l.start);
            }).attr('y1', function (l) {
                return yt(l.taskA) + ht(l.taskA);
            }).attr('x2', function (l) {
                return x(l.start);
            }).attr('y2', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.start);
            }).attr('y1', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            }).attr('x2', function (l) {
                return x(l.taskB.start);
            }).attr('y2', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.taskB.start);
            }).attr('y1', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            }).attr('x2', function (l) {
                return x(l.taskB.start) - arrowWidth;
            }).attr('y2', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2 - arrowWidth;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.taskB.start);
            }).attr('y1', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            }).attr('x2', function (l) {
                return x(l.taskB.start) - arrowWidth;
            }).attr('y2', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2 + arrowWidth;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.taskB.stop);
            }).attr('y1', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            }).attr('x2', function (l) {
                return x(l.stop);
            }).attr('y2', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.stop);
            }).attr('y1', function (l) {
                return yt(l.taskB) + ht(l.taskB) / 2;
            }).attr('x2', function (l) {
                return x(l.stop);
            }).attr('y2', function (l) {
                return yt(l.taskA) + ht(l.taskA);
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.stop);
            }).attr('y1', function (l) {
                return yt(l.taskA) + ht(l.taskA);
            }).attr('x2', function (l) {
                return x(l.stop) - arrowWidth;
            }).attr('y2', function (l) {
                return yt(l.taskA) + ht(l.taskA) + arrowWidth;
            });
            lines.append('line').attr('x1', function (l) {
                return x(l.stop);
            }).attr('y1', function (l) {
                return yt(l.taskA) + ht(l.taskA);
            }).attr('x2', function (l) {
                return x(l.stop) + arrowWidth;
            }).attr('y2', function (l) {
                return yt(l.taskA) + ht(l.taskA) + arrowWidth;
            });
        };

        if ($routeParams["exchangeId"]) {
            $scope.query = $routeParams["exchangeId"];
            $scope.onQueryChange();
        }
    }
    Camin.Controller = Controller;
})(Camin || (Camin = {}));
var Camin;
(function (Camin) {
    var Diagram = (function () {
        function Diagram() {
            this.actors = [];
            this.signals = [];
        }
        Diagram.prototype.actor = function (name) {
            for (var i = 0; i < this.actors.length; i++) {
                if (this.actors[i].name === name) {
                    return this.actors[i];
                }
            }
            var actor = new Actor(name, this.actors.length);
            this.actors.push(actor);
            return actor;
        };

        Diagram.prototype.signal = function (actorA, actorB, message) {
            var signal = new Signal(actorA, actorB, message);
            this.signals.push(signal);
            return signal;
        };
        return Diagram;
    })();
    Camin.Diagram = Diagram;

    var Actor = (function () {
        function Actor(name, index) {
            this.name = name;
            this.index = index;
        }
        return Actor;
    })();
    Camin.Actor = Actor;

    var Signal = (function () {
        function Signal(actorA, actorB, message) {
            this.actorA = actorA;
            this.actorB = actorB;
            this.message = message;
        }
        Signal.prototype.isSelf = function () {
            return this.actorA.index === this.actorB.index;
        };
        return Signal;
    })();
    Camin.Signal = Signal;
})(Camin || (Camin = {}));
/**
* @module Jmx
* @main Jmx
*/
var Jmx;
(function (Jmx) {
    var pluginName = 'jmx';

    Jmx.currentProcessId = '';

    angular.module(pluginName, ['bootstrap', 'ui.bootstrap', 'ui.bootstrap.modal', 'ngResource', 'datatable', 'hawtioCore', 'hawtio-ui', 'hawtioRbac']).config(function ($routeProvider) {
        $routeProvider.when('/jmx/attributes', { templateUrl: 'app/jmx/html/attributes.html' }).when('/jmx/operations', { templateUrl: 'app/jmx/html/operations.html' }).when('/jmx/charts', { templateUrl: 'app/jmx/html/charts.html' }).when('/jmx/chartEdit', { templateUrl: 'app/jmx/html/chartEdit.html' }).when('/jmx/help/:tabName', { templateUrl: 'app/core/html/help.html' }).when('/jmx/widget/donut', { templateUrl: 'app/jmx/html/donutChart.html' }).when('/jmx/widget/area', { templateUrl: 'app/jmx/html/areaChart.html' });
    }).factory('jmxTreeLazyLoadRegistry', function () {
        return Jmx.lazyLoaders;
    }).factory('jmxWidgetTypes', function () {
        return Jmx.jmxWidgetTypes;
    }).factory('jmxWidgets', function () {
        return Jmx.jmxWidgets;
    }).run(function ($location, workspace, viewRegistry, layoutTree, jolokia, pageTitle, helpRegistry) {
        viewRegistry['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) {
                    // ignore
                }
                if (Jmx.currentProcessId && Jmx.currentProcessId.has("@")) {
                    Jmx.currentProcessId = "pid:" + Jmx.currentProcessId.split("@")[0];
                }
            }
            return Jmx.currentProcessId;
        });

        workspace.topLevelTabs.push({
            id: "jmx",
            content: "JMX",
            title: "View the JMX MBeans in this process",
            isValid: function (workspace) {
                return workspace.hasMBeans();
            },
            href: function () {
                return "#/jmx/attributes";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("jmx");
            }
        });

        workspace.subLevelTabs.push({
            content: '<i class="icon-list"></i> Attributes',
            title: "View the attribute values on your selection",
            isValid: function (workspace) {
                return true;
            },
            href: function () {
                return "#/jmx/attributes";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-leaf"></i> Operations',
            title: "Execute operations on your selection",
            isValid: function (workspace) {
                return true;
            },
            href: function () {
                return "#/jmx/operations";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-bar-chart"></i> Chart',
            title: "View a chart of the metrics on your selection",
            isValid: function (workspace) {
                return true;
            },
            href: function () {
                return "#/jmx/charts";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-cog"></i> Edit Chart',
            title: "Edit the chart configuration",
            isValid: function (workspace) {
                return workspace.isLinkActive("jmx/chart");
            },
            href: function () {
                return "#/jmx/chartEdit";
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function TreeHeaderController($scope) {
        $scope.expandAll = function () {
            Tree.expandAll("#jmxtree");
        };

        $scope.contractAll = function () {
            Tree.contractAll("#jmxtree");
        };
    }
    Jmx.TreeHeaderController = TreeHeaderController;

    function MBeansController($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);
            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.MBeansController = MBeansController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function ChartController($scope, $element, $location, workspace, localStorage, jolokiaUrl, jolokiaParams) {
        $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) {
                // ignore
            }
            try  {
                $scope.dereg();
            } catch (error) {
                // ignore
            }
            $scope.reset();
        });

        $scope.errorMessage = function () {
            if ($scope.updateRate === 0) {
                return "updateRate";
            }

            if ($scope.metrics.length === 0) {
                return "metrics";
            }
        };

        var doRender = Core.throttled(render, 200);

        $scope.deregRouteChange = $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            doRender();
        });
        $scope.dereg = $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;
            doRender();
        });

        doRender();

        function render() {
            var node = workspace.selection;
            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;
            }

            // 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 = toSearchArgumentArray(search["att"]);

            if (mbean) {
                // TODO make generic as we can cache them; they rarely ever change
                // lets get the attributes for this mbean
                // we need to escape the mbean path for list
                var listKey = encodeMBeanPath(mbean);

                //console.log("Looking up mbeankey: " + listKey);
                var meta = $scope.jolokia.list(listKey);
                if (meta) {
                    var attributes = meta.attr;
                    if (attributes) {
                        var foundNames = [];
                        for (var key in attributes) {
                            var value = attributes[key];
                            if (value) {
                                var typeName = value['type'];
                                if (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;
                            }
                        }
                        angular.forEach(foundNames, function (key) {
                            var metric = $scope.jolokiaContext.metric({
                                type: 'read',
                                mbean: mbean,
                                attribute: key
                            }, 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 = 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;
                            }
                        }
                    });

                    // lets create the metrics
                    attributeNames.forEach(function (key) {
                        angular.forEach(mbeans, function (mbean, name) {
                            var attributeTitle = 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");
                }
            }

            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) {
                        Jmx.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.reset();
            }

            Core.$apply($scope);
        }
        ;
    }
    Jmx.ChartController = ChartController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function ChartEditController($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 Object.size(value);
            } 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"];
            } 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);
        });

        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;

                        // we need to escape the mbean path for list
                        var listKey = escapeMBeanPath(mbean);

                        //var listKey = encodeMBeanPath(mbean);
                        jolokia.list(listKey, onSuccess(function (meta) {
                            var attributes = meta.attr;
                            if (attributes) {
                                for (var key in attributes) {
                                    var value = attributes[key];
                                    if (value) {
                                        var typeName = value['type'];
                                        if (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 = toSearchArgumentArray(search["att"]);
                                    var elementNames = 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", Object.size($scope.metrics));
                                    $("#mbeans").attr("size", Object.size($scope.mbeans));
                                    Core.$apply($scope);
                                }
                            }
                        }));
                    }
                });
            }
        }
    }
    Jmx.ChartEditController = ChartEditController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    // IOperationControllerScope
    function OperationController($scope, workspace, jolokia, $timeout) {
        $scope.item = $scope.selectedOperation;
        $scope.title = $scope.item.humanReadable;
        $scope.desc = $scope.item.desc;
        $scope.operationResult = '';
        $scope.executeIcon = "icon-ok";
        $scope.mode = "text";
        $scope.entity = {};
        $scope.formConfig = {
            properties: {},
            description: $scope.objectName + "::" + $scope.item.name
        };

        $scope.item.args.forEach(function (arg) {
            $scope.formConfig.properties[arg.name] = {
                type: arg.type,
                tooltip: arg.desc,
                help: "Type: " + arg.type
            };
        });

        $timeout(function () {
            $("html, body").animate({ scrollTop: 0 }, "medium");
        }, 250);

        var sanitize = function (args) {
            if (args) {
                args.forEach(function (arg) {
                    switch (arg.type) {
                        case "int":
                        case "long":
                            arg.formType = "number";
                            break;
                        default:
                            arg.formType = "text";
                    }
                });
            }

            return args;
        };

        $scope.args = sanitize($scope.item.args);

        $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 = "icon-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 (json, form) {
            Jmx.log.debug("onSubmit: json:", json, " form: ", form);
            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(onSuccess($scope.handleResponse, {
                error: function (response) {
                    $scope.executeIcon = "icon-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 = "icon-spinner icon-spin";
            var fn = jolokia.execute;
            fn.apply(jolokia, args);
        };
    }
    Jmx.OperationController = OperationController;

    function OperationsController($scope, workspace, jolokia, rbacACLMBean, $templateCache) {
        $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 = Core.throttled(function () {
            var node = workspace.selection;
            if (!node) {
                return;
            }

            $scope.objectName = node.objectName;
            if (!$scope.objectName) {
                return;
            }

            jolokia.request({
                type: 'list',
                path: escapeMBeanPath($scope.objectName)
            }, onSuccess(render));
        }, 500);

        function getArgs(args) {
            return "(" + args.map(function (arg) {
                return arg.type;
            }).join() + ")";
        }

        function sanitize(value) {
            for (var item in value) {
                item = "" + item;
                value[item].name = item;
                value[item].humanReadable = humanizeValue(item);
            }
            return value;
        }

        $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)) {
                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.selection || workspace.moveIfViewInvalid()) {
                return;
            }
            fetch();
        });

        function fetchPermissions(objectName, operations) {
            var map = {};
            map[objectName] = [];

            angular.forEach(operations, function (value, key) {
                map[objectName].push(value.name);
            });

            jolokia.request({
                type: 'exec',
                mbean: rbacACLMBean,
                operation: 'canInvoke(java.util.Map)',
                arguments: [map]
            }, onSuccess(function (response) {
                var map = response.value;
                angular.forEach(map[objectName], function (value, key) {
                    operations[key]['canInvoke'] = value['CanInvoke'];
                });

                //log.debug("Got operations: ", $scope.operations);
                Core.$apply($scope);
            }, {
                error: function (response) {
                    // silently ignore
                    Core.$apply($scope);
                }
            }));
        }

        function render(response) {
            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 (Core.isBlank(rbacACLMBean) || $scope.isOperationsEmpty()) {
                Core.$apply($scope);
                return;
            } else {
                fetchPermissions($scope.objectName, $scope.operations);
            }
        }
    }
    Jmx.OperationsController = OperationsController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function DonutChartController($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
                });
            });
        } 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;
                });
            }

            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, onSuccess($scope.render));
    }
    Jmx.DonutChartController = DonutChartController;
})(Jmx || (Jmx = {}));
/**
* @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: "icon-smile",
            route: "/jmx/widget/donut",
            size_x: 2,
            size_y: 2,
            title: "Add Donut chart to Dashboard"
        },
        {
            type: "area",
            icon: "icon-bar-chart",
            route: "/jmx/widget/area",
            size_x: 4,
            size_y: 2,
            title: "Add Area chart to Dashboard"
        }
    ];

    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 Jmx
*/
var Jmx;
(function (Jmx) {
    Jmx.propertiesColumnDefs = [
        {
            field: 'name',
            displayName: 'Property',
            width: "27%",
            cellTemplate: '<div class="ngCellText" title="{{row.entity.attrDesc}}" ' + 'data-placement="bottom"><div ng-show="!inDashboard" class="inline" compile="row.entity.getDashboardWidgets()"></div>{{row.entity.name}}</div>' },
        {
            field: 'value',
            displayName: 'Value',
            width: "70%",
            cellTemplate: '<div class="ngCellText" ng-click="row.entity.onViewAttribute()" title="{{row.entity.tooltip}}" ng-bind-html-unsafe="row.entity.summary"></div>'
        }
    ];

    Jmx.foldersColumnDefs = [
        {
            displayName: 'Name',
            cellTemplate: '<div class="ngCellText"><a href="{{row.entity.folderHref(row)}}"><i class="{{row.entity.folderIconClass(row)}}"></i> {{row.getProperty("title")}}</a></div>'
        }
    ];

    function AttributesController($scope, $element, $location, workspace, jolokia, jmxWidgets, jmxWidgetTypes, $templateCache) {
        $scope.searchText = '';
        $scope.selectedItems = [];

        $scope.lastKey = null;
        $scope.attributesInfoCache = {};

        $scope.entity = {};
        $scope.attributeSchema = {};
        $scope.gridData = [];
        $scope.attributes = "";

        $scope.$watch('gridData.length', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (newValue > 0) {
                    $scope.attributes = $templateCache.get('gridTemplate');
                } else {
                    $scope.attributes = "";
                }
            }
        });

        var attributeSchemaBasic = {
            properties: {
                'key': {
                    description: 'Key',
                    tooltip: 'Attribute key',
                    type: 'string',
                    readOnly: 'true'
                },
                'description': {
                    description: 'Description',
                    tooltip: 'Attribute description',
                    type: 'string',
                    formTemplate: "<textarea class='input-xlarge' rows='2' readonly='true'></textarea>"
                },
                'type': {
                    description: 'Type',
                    tooltip: 'Attribute type',
                    type: 'string',
                    readOnly: 'true'
                }
            }
        };

        $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('gridOptions.selectedItems', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                Jmx.log.debug("Selected items: ", newValue);
                $scope.selectedItems = newValue;
            }
        }, true);

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            // clear selection if we clicked the jmx nav bar button
            // otherwise we may show data from Camel/ActiveMQ or other plugins that
            // reuse the JMX plugin for showing tables (#884)
            var currentUrl = $location.url();
            if (currentUrl.endsWith("/jmx/attributes")) {
                Jmx.log.debug("Reset selection in JMX plugin");
                workspace.selection = null;
                $scope.lastKey = null;
            }

            setTimeout(updateTableContents, 50);
        });

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid()) {
                Core.unregister(jolokia, $scope);
                return;
            }
            setTimeout(function () {
                $scope.gridData = [];
                Core.$apply($scope);
                setTimeout(function () {
                    updateTableContents();
                }, 10);
            }, 10);
        });

        $scope.hasWidget = function (row) {
            return true;
        };

        $scope.onCancelAttribute = function () {
            // clear entity
            $scope.entity = {};
        };

        $scope.onUpdateAttribute = function () {
            var value = $scope.entity["attrValueEdit"];
            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, onSuccess(function (response) {
                    notification("success", "Updated attribute " + key);
                }));
            }
        };

        $scope.onViewAttribute = function (row) {
            if (!row.summary) {
                return;
            }

            // create entity and populate it with data from the selected row
            $scope.entity = {};
            $scope.entity["key"] = row.key;
            $scope.entity["description"] = row.attrDesc;
            $scope.entity["type"] = row.type;
            $scope.entity["rw"] = row.rw;
            var type = asJsonSchemaType(row.type, row.key);
            var readOnly = !row.rw;

            // calculate a textare with X number of rows that usually fit the value to display
            var len = row.summary.length;
            var rows = (len / 40) + 1;
            if (rows > 10) {
                // cap at most 10 rows to not make the dialog too large
                rows = 10;
            }

            if (readOnly) {
                // if the value is empty its a &nbsp; as we need this for the table to allow us to click on the empty row
                if (row.summary === '&nbsp;') {
                    $scope.entity["attrValueView"] = '';
                } else {
                    $scope.entity["attrValueView"] = row.summary;
                }

                // clone from the basic schema to the new schema we create on-the-fly
                // this is needed as the dialog have problems if reusing the schema, and changing the schema afterwards
                // so its safer to create a new schema according to our needs
                $scope.attributeSchemaView = {};
                for (var i in attributeSchemaBasic) {
                    $scope.attributeSchemaView[i] = attributeSchemaBasic[i];
                }

                // and add the new attrValue which is dynamic computed
                $scope.attributeSchemaView.properties.attrValueView = {
                    description: 'Value',
                    label: "Value",
                    tooltip: 'Attribute value',
                    type: 'string',
                    formTemplate: "<textarea class='input-xlarge' rows='" + rows + "' readonly='true'></textarea>"
                };

                // just to be safe, then delete not needed part of the schema
                if ($scope.attributeSchemaView) {
                    delete $scope.attributeSchemaView.properties.attrValueEdit;
                }
            } else {
                // if the value is empty its a &nbsp; as we need this for the table to allow us to click on the empty row
                if (row.summary === '&nbsp;') {
                    $scope.entity["attrValueEdit"] = '';
                } else {
                    $scope.entity["attrValueEdit"] = row.summary;
                }

                // clone from the basic schema to the new schema we create on-the-fly
                // this is needed as the dialog have problems if reusing the schema, and changing the schema afterwards
                // so its safer to create a new schema according to our needs
                $scope.attributeSchemaEdit = {};
                for (var i in attributeSchemaBasic) {
                    $scope.attributeSchemaEdit[i] = attributeSchemaBasic[i];
                }

                // and add the new attrValue which is dynamic computed
                $scope.attributeSchemaEdit.properties.attrValueEdit = {
                    description: 'Value',
                    label: "Value",
                    tooltip: 'Attribute value',
                    type: 'string',
                    formTemplate: "<textarea class='input-xlarge' rows='" + rows + "'></textarea>"
                };

                // just to be safe, then delete not needed part of the schema
                if ($scope.attributeSchemaEdit) {
                    delete $scope.attributeSchemaEdit.properties.attrValueView;
                }
            }

            $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("<i class=\"" + widgetType['icon'] + " clickable\" title=\"" + widgetType['title'] + "\" ng-click=\"row.entity.addChartToDashboard('" + widgetType['type'] + "')\"></i>");
            });
            return rc.join() + "&nbsp;";
        };

        $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 (typeof completeFunction === "undefined") { 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, 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") ? "icon-cog" : "icon-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.selection;
            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) {
                    var asQuery = function (node) {
                        var path = escapeMBeanPath(node);
                        var query = {
                            type: "LIST",
                            method: "post",
                            path: path,
                            ignoreErrors: true
                        };
                        return query;
                    };
                    var infoQuery = asQuery(mbean);
                    jolokia.request(infoQuery, onSuccess(function (response) {
                        $scope.attributesInfoCache = response.value;
                        Jmx.log.debug("Updated attributes info cache for mbean " + mbean);
                    }));
                }
            }

            if (mbean) {
                request = { type: 'read', mbean: mbean };
                if (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;
                    });
                    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;
                                //$scope.columnDefs = [];
                            }
                        } else {
                            console.log("Too many type names " + typeNames);
                        }
                    }
                }
            }

            //var callback = onSuccess(render, { error: render });
            var callback = 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);
            }
            if (node) {
                $scope.lastKey = node.key;
            }
            Core.$apply($scope);
        }

        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 (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' : 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);
                            });

                            $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);

                        //console.log("Would have selected " + JSON.stringify($scope.selectedItems));
                        Core.$apply($scope);
                    }
                    // if the last row, then fire an event
                } else {
                    console.log("No mbean name in request " + JSON.stringify(response.request));
                }
            } else {
                $scope.gridOptions.columnDefs = Jmx.propertiesColumnDefs;
                $scope.gridOptions.enableRowClickSelection = false;
                var showAllAttributes = true;
                if (angular.isObject(data)) {
                    var properties = [];
                    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: humanizeValue(key), value: 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);
                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);
                };
            });
        }

        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 = "<table class='table table-striped'>";
                var summary = "";
                var object = value;
                var keys = Object.keys(value).sort();
                angular.forEach(keys, function (key) {
                    var value = object[key];
                    detailHtml += "<tr><td>" + humanizeValue(key) + "</td><td>" + value + "</td></tr>";
                    summary += "" + humanizeValue(key) + ": " + value + "  ";
                });
                detailHtml += "</table>";
                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 = '&nbsp;';
                    data.tooltip = "";
                } else {
                    data.tooltip = text;
                }
                data.summary = "" + text + "";
                data.detailHtml = "<pre>" + text + "</pre>";
                if (angular.isArray(value)) {
                    var html = "<ul>";
                    angular.forEach(value, function (item) {
                        html += "<li>" + item + "</li>";
                    });
                    html += "</ul>";
                    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.AttributesController = AttributesController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function AttributeController($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
                }, onSuccess(render));
            }
        };

        function render(response) {
            if (!Object.equal($scope.data, response.value)) {
                $scope.data = safeNull(response.value);
                Core.$apply($scope);
            }
        }
    }
    Jmx.AttributeController = AttributeController;

    function AttributeChartController($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
                }, onSuccess(render));
            }
        };

        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 = [];

                Object.extended(response.value).keys(function (key, value) {
                    $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);
            }
        }
    }
    Jmx.AttributeChartController = AttributeChartController;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    Jmx.log = Logger.get("JMX");

    var attributesToolBars = {};

    Jmx.lazyLoaders = {};

    function findLazyLoadingFunction(workspace, folder) {
        var factories = workspace.jmxTreeLazyLoadRegistry[folder.domain];
        var lazyFunction = null;
        if (factories && factories.length) {
            angular.forEach(factories, function (customLoader) {
                if (!lazyFunction) {
                    lazyFunction = customLoader(folder);
                }
            });
        }
        return lazyFunction;
    }
    Jmx.findLazyLoadingFunction = findLazyLoadingFunction;

    function registerLazyLoadHandler(domain, lazyLoaderFactory) {
        if (!Jmx.lazyLoaders) {
            Jmx.lazyLoaders = {};
        }
        var array = Jmx.lazyLoaders[domain];
        if (!array) {
            array = [];
            Jmx.lazyLoaders[domain] = array;
        }
        array.push(lazyLoaderFactory);
    }
    Jmx.registerLazyLoadHandler = registerLazyLoadHandler;

    function unregisterLazyLoadHandler(domain, lazyLoaderFactory) {
        if (Jmx.lazyLoaders) {
            var array = Jmx.lazyLoaders[domain];
            if (array) {
                array.remove(lazyLoaderFactory);
            }
        }
    }
    Jmx.unregisterLazyLoadHandler = unregisterLazyLoadHandler;

    /**
    * Registers a toolbar template for the given plugin name, jmxDomain.
    * @method addAttributeToolBar
    * @for Jmx
    * @param {String} pluginName used so that we can later on remove this function when the plugin is removed
    * @param {String} jmxDomain the JMX domain to avoid having to evaluate too many functions on each selection
    * @param {Function} fn the function used to decide which attributes tool bar should be used for the given select
    */
    function addAttributeToolBar(pluginName, jmxDomain, fn) {
        var array = attributesToolBars[jmxDomain];
        if (!array) {
            array = [];
            attributesToolBars[jmxDomain] = array;
        }
        array.push(fn);
    }
    Jmx.addAttributeToolBar = addAttributeToolBar;

    /**
    * Try find a custom toolbar HTML template for the given selection or returns the default value
    * @method getAttributeToolbar
    * @for Jmx
    * @param {Core.NodeSelection} node
    * @param {String} defaultValue
    */
    function getAttributeToolBar(node, defaultValue) {
        if (typeof defaultValue === "undefined") { defaultValue = "app/jmx/html/attributeToolBar.html"; }
        var answer = null;
        var jmxDomain = (node) ? node.domain : null;
        if (jmxDomain) {
            var array = attributesToolBars[jmxDomain];
            if (array) {
                for (var idx in array) {
                    var fn = array[idx];
                    answer = fn(node);
                    if (answer)
                        break;
                }
            }
        }
        return (answer) ? answer : defaultValue;
    }
    Jmx.getAttributeToolBar = getAttributeToolBar;

    function updateTreeSelectionFromURL($location, treeElement, activateIfNoneSelected) {
        if (typeof activateIfNoneSelected === "undefined") { activateIfNoneSelected = false; }
        updateTreeSelectionFromURLAndAutoSelect($location, treeElement, null, activateIfNoneSelected);
    }
    Jmx.updateTreeSelectionFromURL = updateTreeSelectionFromURL;

    function updateTreeSelectionFromURLAndAutoSelect($location, treeElement, autoSelect, activateIfNoneSelected) {
        if (typeof activateIfNoneSelected === "undefined") { activateIfNoneSelected = false; }
        var dtree = treeElement.dynatree("getTree");
        if (dtree) {
            var node = null;
            var key = $location.search()['nid'];
            if (key) {
                try  {
                    node = dtree.activateKey(key);
                } catch (e) {
                    // tree not visible we suspect!
                }
            }
            if (node) {
                node.expand(true);
            } else {
                if (!treeElement.dynatree("getActiveNode")) {
                    // lets expand the first node
                    var root = treeElement.dynatree("getRoot");
                    var children = root ? root.getChildren() : null;
                    if (children && children.length) {
                        var first = children[0];
                        first.expand(true);

                        // invoke any auto select function, and use its result as new first, if any returned
                        if (autoSelect) {
                            var result = autoSelect(first);
                            if (result) {
                                first = result;
                            }
                        }
                        if (activateIfNoneSelected) {
                            first.expand();
                            first.activate();
                        }
                    } else {
                        /*
                        causes NPE :)
                        
                        var first = children[0];
                        first.expand(true);
                        if (activateIfNoneSelected) {
                        first.activate();
                        }
                        */
                    }
                }
            }
        }
    }
    Jmx.updateTreeSelectionFromURLAndAutoSelect = updateTreeSelectionFromURLAndAutoSelect;

    function getUniqueTypeNames(children) {
        var typeNameMap = {};
        angular.forEach(children, function (mbean) {
            var typeName = mbean.typeName;
            if (typeName) {
                typeNameMap[typeName] = mbean;
            }
        });

        // only query if all the typenames are the same
        var typeNames = Object.keys(typeNameMap);
        return typeNames;
    }
    Jmx.getUniqueTypeNames = getUniqueTypeNames;

    function enableTree($scope, $location, workspace, treeElement, children, redraw, onActivateFn) {
        if (typeof redraw === "undefined") { redraw = false; }
        if (typeof onActivateFn === "undefined") { onActivateFn = null; }
        //$scope.workspace = workspace;
        if (treeElement.length) {
            if (!onActivateFn) {
                onActivateFn = function (node) {
                    var data = node.data;

                    //$scope.select(data);
                    workspace.updateSelectionNode(data);
                    Core.$apply($scope);
                };
            }
            workspace.treeElement = treeElement;
            treeElement.dynatree({
                /*
                * The event handler called when a different node in the tree is selected
                */
                onActivate: onActivateFn,
                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, function () {
                            treeNode.setLazyNodeStatus(DTNodeStatus_Ok);
                            var newChildren = folder.children;
                            if (newChildren !== oldChildren) {
                                treeNode.removeChildren();
                                angular.forEach(newChildren, function (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["nid"] = 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: $scope.workspace.tree.children
                children: children
            });

            if (redraw) {
                workspace.redrawTree();
            }
        }
    }
    Jmx.enableTree = enableTree;
})(Jmx || (Jmx = {}));
/**
* @module Jmx
*/
var Jmx;
(function (Jmx) {
    function AreaChartController($scope, $routeParams, jolokia, $templateCache, localStorage, $element) {
        $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");
            }

            $scope.data = {
                _type: "date_histogram",
                entries: $scope.entries
            };

            Core.$apply($scope);
        };

        Core.register(jolokia, $scope, $scope.req, onSuccess($scope.render));
    }
    Jmx.AreaChartController = AreaChartController;
})(Jmx || (Jmx = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function CamelCanvasController($scope, $element, workspace, jolokia, wikiRepository, $templateCache, $interpolate) {
        $scope.addDialog = new UI.Dialog();
        $scope.propertiesDialog = new UI.Dialog();
        $scope.modified = false;
        $scope.camelIgnoreIdForLabel = Camel.ignoreIdForLabel(localStorage);
        $scope.camelMaximumLabelWidth = Camel.maximumLabelWidth(localStorage);
        $scope.camelMaximumTraceOrDebugBodyLength = Camel.maximumTraceOrDebugBodyLength(localStorage);

        $scope.forms = {};

        $scope.nodeTemplate = $interpolate($templateCache.get("nodeTemplate"));

        $scope.$watch("camelContextTree", function () {
            var tree = $scope.camelContextTree;
            $scope.rootFolder = tree;

            // now we've got cid values in the tree and DOM, lets create an index so we can bind the DOM to the tree model
            $scope.folders = Camel.addFoldersToIndex($scope.rootFolder);

            var doc = Core.pathGet(tree, ["xmlDocument"]);
            if (doc) {
                $scope.doc = doc;
                reloadRouteIds();
                onRouteSelectionChanged();
            }
        });

        $scope.addAndCloseDialog = function () {
            var nodeModel = $scope.selectedNodeModel();
            if (nodeModel) {
                addNewNode(nodeModel);
            }
            $scope.addDialog.close();
        };

        $scope.removeNode = function () {
            var folder = getSelectedOrRouteFolder();
            if (folder) {
                var nodeName = Camel.getFolderCamelNodeId(folder);
                folder.detach();
                if ("route" === nodeName) {
                    // lets also clear the selected route node
                    $scope.selectedRouteId = null;
                }
                updateSelection(null);
                treeModified();
            }
        };

        $scope.doLayout = function () {
            $scope.drawnRouteId = null;
            onRouteSelectionChanged();
        };

        function isRouteOrNode() {
            return !$scope.selectedFolder;
        }

        $scope.getDeleteTitle = function () {
            if (isRouteOrNode()) {
                return "Delete this route";
            }
            return "Delete this node";
        };

        $scope.getDeleteTarget = function () {
            if (isRouteOrNode()) {
                return "Route";
            }
            return "Node";
        };

        $scope.isFormDirty = function () {
            Wiki.log.debug("endpointForm: ", $scope.endpointForm);
            if ($scope.endpointForm.$dirty) {
                return true;
            }
            if (!$scope.forms['formEditor']) {
                return false;
            } else {
                return $scope.forms['formEditor']['$dirty'];
            }
        };

        /* TODO
        $scope.resetForms = () => {
        
        }
        */
        /*
        * Converts a path and a set of endpoint parameters into a URI we can then use to store in the XML
        */
        function createEndpointURI(endpointScheme, slashesText, endpointPath, endpointParameters) {
            console.log("scheme " + endpointScheme + " path " + endpointPath + " parameters " + endpointParameters);

            // now lets create the new URI from the path and parameters
            // TODO should we use JMX for this?
            var uri = ((endpointScheme) ? endpointScheme + ":" + slashesText : "") + (endpointPath ? endpointPath : "");
            var paramText = Core.hashToString(endpointParameters);
            if (paramText) {
                uri += "?" + paramText;
            }
            return uri;
        }

        $scope.updateProperties = function () {
            Wiki.log.info("old URI is " + $scope.nodeData.uri);
            var uri = createEndpointURI($scope.endpointScheme, ($scope.endpointPathHasSlashes ? "//" : ""), $scope.endpointPath, $scope.endpointParameters);
            Wiki.log.info("new URI is " + uri);
            if (uri) {
                $scope.nodeData.uri = uri;
            }

            var key = null;
            var selectedFolder = $scope.selectedFolder;
            if (selectedFolder) {
                key = selectedFolder.key;

                // lets delete the current selected node's div so its updated with the new template values
                var elements = $element.find(".canvas").find("[id='" + key + "']").first().remove();
            }

            treeModified();

            if (key) {
                updateSelection(key);
            }

            if ($scope.isFormDirty()) {
                $scope.endpointForm.$setPristine();
                if ($scope.forms['formEditor']) {
                    $scope.forms['formEditor'].$setPristine();
                }
            }

            Core.$apply($scope);
        };

        $scope.save = function () {
            // generate the new XML
            if ($scope.rootFolder) {
                var xmlNode = Camel.generateXmlFromFolder($scope.rootFolder);
                if (xmlNode) {
                    var text = Core.xmlNodeToString(xmlNode);
                    if (text) {
                        var decoded = decodeURIComponent(text);
                        Wiki.log.debug("Saving xml decoded: " + decoded);

                        // lets save the file...
                        var commitMessage = $scope.commitMessage || "Updated page " + $scope.pageId;
                        wikiRepository.putPage($scope.branch, $scope.pageId, decoded, commitMessage, function (status) {
                            Wiki.onComplete(status);
                            notification("success", "Saved " + $scope.pageId);
                            $scope.modified = false;
                            goToView();
                            Core.$apply($scope);
                        });
                    }
                }
            }
        };

        $scope.cancel = function () {
            console.log("cancelling...");
            // TODO show dialog if folks are about to lose changes...
        };

        $scope.$watch("selectedRouteId", onRouteSelectionChanged);

        function goToView() {
            // TODO lets navigate to the view if we have a separate view one day :)
            /*
            if ($scope.breadcrumbs && $scope.breadcrumbs.length > 1) {
            var viewLink = $scope.breadcrumbs[$scope.breadcrumbs.length - 2];
            console.log("goToView has found view " + viewLink);
            var path = Core.trimLeading(viewLink, "#");
            $location.path(path);
            } else {
            console.log("goToView has no breadcrumbs!");
            }
            */
        }

        function addNewNode(nodeModel) {
            var doc = $scope.doc || document;
            var parentFolder = $scope.selectedFolder || $scope.rootFolder;
            var key = nodeModel["_id"];
            if (!key) {
                console.log("WARNING: no id for model " + JSON.stringify(nodeModel));
            } else {
                var treeNode = $scope.selectedFolder;
                if (key === "route") {
                    // lets add to the root of the tree
                    treeNode = $scope.rootFolder;
                } else {
                    if (!treeNode) {
                        // lets select the last route - and create a new route if need be
                        var root = $scope.rootFolder;
                        var children = root.children;
                        if (!children || !children.length) {
                            addNewNode(Camel.getCamelSchema("route"));
                            children = root.children;
                        }
                        if (children && children.length) {
                            treeNode = getRouteFolder($scope.rootFolder, $scope.selectedRouteId) || children[children.length - 1];
                        } else {
                            console.log("Could not add a new route to the empty tree!");
                            return;
                        }
                    }

                    // if the parent folder likes to act as a pipeline, then add
                    // after the parent, rather than as a child
                    var parentTypeName = Camel.getFolderCamelNodeId(treeNode);
                    if (!Camel.acceptOutput(parentTypeName)) {
                        treeNode = treeNode.parent || treeNode;
                    }
                }
                if (treeNode) {
                    var node = doc.createElement(key);
                    parentFolder = treeNode;
                    var addedNode = Camel.addRouteChild(parentFolder, node);

                    // TODO add the schema here for an element??
                    // or default the data or something
                    var nodeData = {};
                    if (key === "endpoint" && $scope.endpointConfig) {
                        var key = $scope.endpointConfig.key;
                        if (key) {
                            nodeData["uri"] = key + ":";
                        }
                    }
                    addedNode["camelNodeData"] = nodeData;
                    addedNode["endpointConfig"] = $scope.endpointConfig;

                    if (key === "route") {
                        // lets generate a new routeId and switch to it
                        var count = $scope.routeIds.length;
                        var nodeId = null;
                        while (true) {
                            nodeId = "route" + (++count);
                            if (!$scope.routeIds.find(nodeId)) {
                                break;
                            }
                        }
                        addedNode["routeXmlNode"].setAttribute("id", nodeId);
                        $scope.selectedRouteId = nodeId;
                    }
                }
            }
            treeModified();
        }

        function treeModified(reposition) {
            if (typeof reposition === "undefined") { reposition = true; }
            // lets recreate the XML model from the update Folder tree
            var newDoc = Camel.generateXmlFromFolder($scope.rootFolder);
            var tree = Camel.loadCamelTree(newDoc, $scope.pageId);
            if (tree) {
                $scope.rootFolder = tree;
                $scope.doc = Core.pathGet(tree, ["xmlDocument"]);
            }
            $scope.modified = true;
            reloadRouteIds();
            $scope.doLayout();
            Core.$apply($scope);
        }

        function reloadRouteIds() {
            $scope.routeIds = [];
            var doc = $($scope.doc);
            $scope.camelSelectionDetails.selectedCamelContextId = doc.find("camelContext").attr("id");
            doc.find("route").each(function (idx, route) {
                var id = route.getAttribute("id");
                if (id) {
                    $scope.routeIds.push(id);
                }
            });
        }

        function onRouteSelectionChanged() {
            if ($scope.doc) {
                if (!$scope.selectedRouteId && $scope.routeIds && $scope.routeIds.length) {
                    $scope.selectedRouteId = $scope.routeIds[0];
                }
                if ($scope.selectedRouteId && $scope.selectedRouteId !== $scope.drawnRouteId) {
                    var nodes = [];
                    var links = [];
                    Camel.loadRouteXmlNodes($scope, $scope.doc, $scope.selectedRouteId, nodes, links, getWidth());
                    updateSelection($scope.selectedRouteId);

                    // now we've got cid values in the tree and DOM, lets create an index so we can bind the DOM to the tree model
                    $scope.folders = Camel.addFoldersToIndex($scope.rootFolder);
                    showGraph(nodes, links);
                    $scope.drawnRouteId = $scope.selectedRouteId;
                }
                $scope.camelSelectionDetails.selectedRouteId = $scope.selectedRouteId;
            }
        }

        function showGraph(nodes, links) {
            layoutGraph(nodes, links);
        }

        function getNodeId(node) {
            if (angular.isNumber(node)) {
                var idx = node;
                node = $scope.nodeStates[idx];
                if (!node) {
                    console.log("Cant find node at " + idx);
                    return "node-" + idx;
                }
            }
            return node.cid || "node-" + node.id;
        }

        function getSelectedOrRouteFolder() {
            return $scope.selectedFolder || getRouteFolder($scope.rootFolder, $scope.selectedRouteId);
        }

        function getContainerElement() {
            var rootElement = $element;
            var containerElement = rootElement.find(".canvas");
            if (!containerElement || !containerElement.length)
                containerElement = rootElement;
            return containerElement;
        }

        // context menu (right click) on any component.
        /* TODO disabling this for now just so I can look at elements easily in the dev tools
        jsPlumb.bind("contextmenu", function (component, originalEvent) {
        alert("context menu on component " + component.id);
        originalEvent.preventDefault();
        return false;
        });
        */
        /*
        function clearCanvasLayout(jsPlumb, containerElement) {
        try {
        containerElement.empty();
        jsPlumb.reset();
        } catch (e) {
        // ignore errors
        }
        return jsPlumb;
        }
        */
        // configure canvas layout and styles
        var endpointStyle = ["Dot", { radius: 4, cssClass: 'camel-canvas-endpoint' }];
        var hoverPaintStyle = { strokeStyle: "red", lineWidth: 3 };

        //var labelStyles: any[] = [ "Label", { label:"FOO", id:"label" }];
        var labelStyles = ["Label"];
        var arrowStyles = [
            "Arrow", {
                location: 1,
                id: "arrow",
                length: 8,
                width: 8,
                foldback: 0.8
            }];
        var connectorStyle = ["StateMachine", { curviness: 10, proximityLimit: 50 }];

        jsPlumb.importDefaults({
            Endpoint: endpointStyle,
            HoverPaintStyle: hoverPaintStyle,
            ConnectionOverlays: [
                arrowStyles,
                labelStyles
            ]
        });

        $scope.$on('$destroy', function () {
            jsPlumb.reset();
        });

        // double click on any connection
        jsPlumb.bind("dblclick", function (connection, originalEvent) {
            if (jsPlumb.isSuspendDrawing()) {
                return;
            }
            alert("double click on connection from " + connection.sourceId + " to " + connection.targetId);
        });

        jsPlumb.bind('connection', function (info, evt) {
            if (jsPlumb.isSuspendDrawing()) {
                return;
            }

            //log.debug("Connection event: ", info);
            Wiki.log.debug("Creating connection from ", info.source.get(0).id, " to ", info.target.get(0).id);
            var link = getLink(info);
            var source = $scope.folders[link.source];
            var target = $scope.folders[link.target];
            source.moveChild(target);
            treeModified();
        });

        jsPlumb.bind('connectionDetached', function (info, evt) {
            if (jsPlumb.isSuspendDrawing()) {
                return;
            }

            //log.debug("Connection detach event: ", info);
            Wiki.log.debug("Detaching connection from ", info.source.get(0).id, " to ", info.target.get(0).id);
            var link = getLink(info);
            var source = $scope.folders[link.source];
            var target = $scope.folders[link.target];
            // TODO orphan target folder without actually deleting it
        });

        // lets delete connections on click
        jsPlumb.bind("click", function (c) {
            if (jsPlumb.isSuspendDrawing()) {
                return;
            }
            jsPlumb.detach(c);
        });

        function layoutGraph(nodes, links) {
            var transitions = [];
            var states = Core.createGraphStates(nodes, links, transitions);

            Wiki.log.debug("links: ", links);
            Wiki.log.debug("transitions: ", transitions);

            $scope.nodeStates = states;
            var containerElement = getContainerElement();

            jsPlumb.doWhileSuspended(function () {
                //set our container to some arbitrary initial size
                containerElement.css({
                    'width': '800px',
                    'height': '800px',
                    'min-height': '800px',
                    'min-width': '800px'
                });
                var containerHeight = 0;
                var containerWidth = 0;

                containerElement.find('div.component').each(function (i, el) {
                    Wiki.log.debug("Checking: ", el, " ", i);
                    if (!states.any(function (node) {
                        return el.id === getNodeId(node);
                    })) {
                        Wiki.log.debug("Removing element: ", el.id);
                        jsPlumb.remove(el);
                    }
                });

                angular.forEach(states, function (node) {
                    Wiki.log.debug("node: ", node);
                    var id = getNodeId(node);
                    var div = containerElement.find('#' + id);

                    if (!div[0]) {
                        div = $($scope.nodeTemplate({
                            id: id,
                            node: node
                        }));
                        div.appendTo(containerElement);
                    }

                    // Make the node a jsplumb source
                    jsPlumb.makeSource(div, {
                        filter: "img.nodeIcon",
                        anchor: "Continuous",
                        connector: connectorStyle,
                        connectorStyle: { strokeStyle: "#666", lineWidth: 3 },
                        maxConnections: -1
                    });

                    // and also a jsplumb target
                    jsPlumb.makeTarget(div, {
                        dropOptions: { hoverClass: "dragHover" },
                        anchor: "Continuous"
                    });

                    jsPlumb.draggable(div, {
                        containment: '.camel-canvas'
                    });

                    // add event handlers to this node
                    div.click(function () {
                        var newFlag = !div.hasClass("selected");
                        containerElement.find('div.component').toggleClass("selected", false);
                        div.toggleClass("selected", newFlag);
                        var id = div.attr("id");
                        updateSelection(newFlag ? id : null);
                        Core.$apply($scope);
                    });

                    div.dblclick(function () {
                        var id = div.attr("id");
                        updateSelection(id);

                        //$scope.propertiesDialog.open();
                        Core.$apply($scope);
                    });

                    var height = div.height();
                    var width = div.width();
                    if (height || width) {
                        node.width = width;
                        node.height = height;
                        div.css({
                            'min-width': width,
                            'min-height': height
                        });
                    }
                });

                var edgeSep = 10;

                // Create the layout and get the buildGraph
                dagre.layout().nodeSep(100).edgeSep(edgeSep).rankSep(75).nodes(states).edges(transitions).debugLevel(1).run();

                angular.forEach(states, function (node) {
                    // position the node in the graph
                    var id = getNodeId(node);
                    var div = $("#" + id);
                    var divHeight = div.height();
                    var divWidth = div.width();
                    var leftOffset = node.dagre.x + divWidth;
                    var bottomOffset = node.dagre.y + divHeight;
                    if (containerHeight < bottomOffset) {
                        containerHeight = bottomOffset + edgeSep * 2;
                    }
                    if (containerWidth < leftOffset) {
                        containerWidth = leftOffset + edgeSep * 2;
                    }
                    div.css({ top: node.dagre.y, left: node.dagre.x });
                });

                // size the container to fit the graph
                containerElement.css({
                    'width': containerWidth,
                    'height': containerHeight,
                    'min-height': containerHeight,
                    'min-width': containerWidth
                });

                containerElement.dblclick(function () {
                    $scope.propertiesDialog.open();
                });

                jsPlumb.setSuspendEvents(true);

                // Detach all the current connections and reconnect everything based on the updated graph
                jsPlumb.detachEveryConnection({ fireEvent: false });

                angular.forEach(links, function (link) {
                    jsPlumb.connect({
                        source: getNodeId(link.source),
                        target: getNodeId(link.target)
                    });
                });
                jsPlumb.setSuspendEvents(false);
            });

            return states;
        }

        function getLink(info) {
            var sourceId = info.source.get(0).id;
            var targetId = info.target.get(0).id;
            return {
                source: sourceId,
                target: targetId
            };
        }

        function getNodeByCID(nodes, cid) {
            return nodes.find(function (node) {
                return node.cid === cid;
            });
        }

        /*
        * Updates the selection with the given folder or ID
        */
        function updateSelection(folderOrId) {
            var folder = null;
            if (angular.isString(folderOrId)) {
                var id = folderOrId;
                folder = (id && $scope.folders) ? $scope.folders[id] : null;
            } else {
                folder = folderOrId;
            }
            $scope.selectedFolder = folder;
            folder = getSelectedOrRouteFolder();
            $scope.nodeXmlNode = null;
            $scope.propertiesTemplate = null;
            if (folder) {
                var nodeName = Camel.getFolderCamelNodeId(folder);
                $scope.nodeData = Camel.getRouteFolderJSON(folder);
                $scope.nodeDataChangedFields = {};
                $scope.nodeModel = Camel.getCamelSchema(nodeName);
                if ($scope.nodeModel) {
                    $scope.propertiesTemplate = "app/wiki/html/camelPropertiesEdit.html";
                }
                $scope.selectedEndpoint = null;
                if ("endpoint" === nodeName) {
                    var uri = $scope.nodeData["uri"];
                    if (uri) {
                        // lets decompose the URI into scheme, path and parameters
                        var idx = uri.indexOf(":");
                        if (idx > 0) {
                            var endpointScheme = uri.substring(0, idx);
                            var endpointPath = uri.substring(idx + 1);

                            // for empty paths lets assume we need // on a URI
                            $scope.endpointPathHasSlashes = endpointPath ? false : true;
                            if (endpointPath.startsWith("//")) {
                                endpointPath = endpointPath.substring(2);
                                $scope.endpointPathHasSlashes = true;
                            }
                            idx = endpointPath.indexOf("?");
                            var endpointParameters = {};
                            if (idx > 0) {
                                var parameters = endpointPath.substring(idx + 1);
                                endpointPath = endpointPath.substring(0, idx);
                                endpointParameters = Core.stringToHash(parameters);
                            }

                            $scope.endpointScheme = endpointScheme;
                            $scope.endpointPath = endpointPath;
                            $scope.endpointParameters = endpointParameters;

                            console.log("endpoint " + endpointScheme + " path " + endpointPath + " and parameters " + JSON.stringify(endpointParameters));
                            $scope.loadEndpointSchema(endpointScheme);
                            $scope.selectedEndpoint = {
                                endpointScheme: endpointScheme,
                                endpointPath: endpointPath,
                                parameters: endpointParameters
                            };
                        }
                    }
                }
            }
        }

        function getWidth() {
            var canvasDiv = $($element);
            return canvasDiv.width();
        }

        function getFolderIdAttribute(route) {
            var id = null;
            if (route) {
                var xmlNode = route["routeXmlNode"];
                if (xmlNode) {
                    id = xmlNode.getAttribute("id");
                }
            }
            return id;
        }

        function getRouteFolder(tree, routeId) {
            var answer = null;
            if (tree) {
                angular.forEach(tree.children, function (route) {
                    if (!answer) {
                        var id = getFolderIdAttribute(route);
                        if (routeId === id) {
                            answer = route;
                        }
                    }
                });
            }
            return answer;
        }
        /*
        if (jsPlumb) {
        jsPlumb.bind("ready", setup);
        }
        
        function setup() {
        $scope.jsPlumbSetup = true;
        }
        */
    }
    Wiki.CamelCanvasController = CamelCanvasController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function GitPreferences($scope, localStorage, userDetails) {
        Core.initPreferenceScope($scope, localStorage, {
            'gitUserName': {
                'value': userDetails.username
            },
            'gitUserEmail': {
                'value': ''
            }
        });
    }
    Wiki.GitPreferences = GitPreferences;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function HistoryController($scope, $location, $routeParams, $templateCache, workspace, marked, fileExtensionTypeRegistry, wikiRepository) {
        Wiki.initScope($scope, $routeParams, $location);
        $scope.selectedItems = [];

        // TODO we could configure this?
        $scope.dateFormat = 'EEE, MMM d, y : hh:mm:ss a';

        $scope.gridOptions = {
            data: 'logs',
            showFilter: false,
            selectedItems: $scope.selectedItems,
            showSelectionCheckbox: true,
            displaySelectionCheckbox: true,
            filterOptions: {
                filterText: ''
            },
            columnDefs: [
                {
                    field: 'commitHashText',
                    displayName: 'Change',
                    cellTemplate: $templateCache.get('changeCellTemplate.html'),
                    cellFilter: "",
                    width: "*"
                },
                {
                    field: 'date',
                    displayName: 'Modified',
                    cellFilter: "date: dateFormat",
                    width: "**"
                },
                {
                    field: 'author',
                    displayName: 'Author',
                    cellFilter: "",
                    width: "**"
                },
                {
                    field: 'shortMessage',
                    displayName: 'Message',
                    cellTemplate: '<div class="ngCellText" title="{{row.entity.shortMessage}}">{{row.entity.trimmedMessage}}</div>',
                    cellFilter: "",
                    width: "****"
                }
            ]
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        $scope.$watch('workspace.tree', function () {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(updateView, 50);
            }
        });
        $scope.canRevert = function () {
            return $scope.selectedItems.length === 1 && $scope.selectedItems[0] !== $scope.logs[0];
        };

        $scope.revert = function () {
            if ($scope.selectedItems.length > 0) {
                var objectId = $scope.selectedItems[0].name;
                if (objectId) {
                    var commitMessage = "Reverting file " + $scope.pageId + " to previous version " + objectId;
                    wikiRepository.revertTo($scope.branch, objectId, $scope.pageId, commitMessage, function (result) {
                        Wiki.onComplete(result);

                        // now lets update the view
                        notification('success', "Successfully reverted " + $scope.pageId);
                        updateView();
                    });
                }
                $scope.selectedItems.splice(0, $scope.selectedItems.length);
            }
        };

        $scope.diff = function () {
            var defaultValue = " ";
            var objectId = defaultValue;
            if ($scope.selectedItems.length > 0) {
                objectId = $scope.selectedItems[0].name || defaultValue;
            }
            var baseObjectId = defaultValue;
            if ($scope.selectedItems.length > 1) {
                baseObjectId = $scope.selectedItems[1].name || defaultValue;

                // make the objectId (the one that will start with b/ path) always newer than baseObjectId
                if ($scope.selectedItems[0].date < $scope.selectedItems[1].date) {
                    var _ = baseObjectId;
                    baseObjectId = objectId;
                    objectId = _;
                }
            }
            var link = Wiki.startLink($scope.branch) + "/diff/" + $scope.pageId + "/" + objectId + "/" + baseObjectId;
            var path = Core.trimLeading(link, "#");
            $location.path(path);
        };

        updateView();

        function updateView() {
            var objectId = "";
            var limit = 0;

            $scope.git = wikiRepository.history($scope.branch, objectId, $scope.pageId, limit, function (logArray) {
                angular.forEach(logArray, function (log) {
                    // lets use the shorter hash for links by default
                    var commitId = log.commitHashText || log.name;
                    log.commitLink = Wiki.startLink($scope.branch) + "/commit/" + $scope.pageId + "/" + commitId;
                });
                $scope.logs = logArray;
                Core.$apply($scope);
            });
            Wiki.loadBranches(wikiRepository, $scope);
        }
    }
    Wiki.HistoryController = HistoryController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function goToLink(link, $timeout, $location) {
        var href = Core.trimLeading(link, "#");
        $timeout(function () {
            console.log("About to navigate to: " + href);
            $location.url(href);
        }, 100);
    }

    function ViewController($scope, $location, $routeParams, $route, $http, $timeout, workspace, marked, fileExtensionTypeRegistry, wikiRepository, $compile, $templateCache, jolokia) {
        var log = Logger.get("Wiki");

        Wiki.initScope($scope, $routeParams, $location);

        $scope.fabricTopLevel = "fabric/profiles/";

        $scope.versionId = $scope.branch;

        $scope.profileId = Fabric.pagePathToProfileId($scope.pageId);
        $scope.showProfileHeader = $scope.profileId && $scope.pageId.endsWith(Fabric.profileSuffix) ? true : false;

        $scope.operationCounter = 1;
        $scope.addDialog = new UI.Dialog();
        $scope.generateDialog = new UI.Dialog();
        $scope.renameDialog = new UI.Dialog();
        $scope.moveDialog = new UI.Dialog();
        $scope.deleteDialog = new UI.Dialog();
        $scope.isFile = false;
        $scope.rename = {
            newFileName: ""
        };
        $scope.move = {
            moveFolder: ""
        };
        $scope.createDocumentTree = Wiki.createWizardTree(workspace, $scope);

        $scope.createDocumentTreeActivations = ["camel-spring.xml", "ReadMe.md"];
        $scope.fileExists = {
            exists: false,
            name: ""
        };

        // bind filter model values to search params...
        Core.bindModelToSearchParam($scope, $location, "searchText", "q", "");

        // only reload the page if certain search parameters change
        Core.reloadWhenParametersChange($route, $scope, $location);

        $scope.gridOptions = {
            data: 'children',
            displayFooter: false,
            selectedItems: [],
            showSelectionCheckbox: true,
            enableSorting: false,
            useExternalSorting: true,
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: $templateCache.get('fileCellTemplate.html'),
                    headerCellTemplate: $templateCache.get('fileColumnTemplate.html')
                }
            ]
        };

        $scope.childActions = [];

        var maybeUpdateView = Core.throttled(updateView, 1000);

        $scope.$on('wikiBranchesUpdated', function () {
            updateView();
        });

        /*
        if (!$scope.nameOnly) {
        $scope.gridOptions.columnDefs.push({
        field: 'lastModified',
        displayName: 'Modified',
        cellFilter: "date:'EEE, MMM d, y : hh:mm:ss a'"
        });
        $scope.gridOptions.columnDefs.push({
        field: 'length',
        displayName: 'Size',
        cellFilter: "number"
        });
        }
        */
        $scope.createDashboardLink = function () {
            var href = '/wiki/branch/:branch/view/*page';
            var page = $routeParams['page'];
            var title = page ? page.split("/").last() : null;
            var size = angular.toJson({
                size_x: 2,
                size_y: 2
            });
            var answer = "#/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&size=" + encodeURIComponent(size) + "&routeParams=" + encodeURIComponent(angular.toJson($routeParams));
            if (title) {
                answer += "&title=" + encodeURIComponent(title);
            }
            return answer;
        };

        $scope.displayClass = function () {
            if (!$scope.children || $scope.children.length === 0) {
                return "";
            }
            return "span9";
        };

        $scope.parentLink = function () {
            var start = Wiki.startLink($scope.branch);
            var prefix = start + "/view";

            //console.log("pageId: ", $scope.pageId)
            var parts = $scope.pageId.split("/");

            //console.log("parts: ", parts);
            var path = "/" + parts.first(parts.length - 1).join("/");

            //console.log("path: ", path);
            return Core.createHref($location, prefix + path, []);
        };

        $scope.childLink = function (child) {
            var start = Wiki.startLink($scope.branch);
            var prefix = start + "/view";
            var postFix = "";
            var path = Wiki.encodePath(child.path);
            if (child.directory) {
                // if we are a folder with the same name as a form file, lets add a form param...
                var formPath = path + ".form";
                var children = $scope.children;
                if (children) {
                    var formFile = children.find(function (child) {
                        return child['path'] === formPath;
                    });
                    if (formFile) {
                        prefix = start + "/formTable";
                        postFix = "?form=" + formPath;
                    }
                }
            } else {
                var xmlNamespaces = child.xmlNamespaces;
                if (xmlNamespaces && xmlNamespaces.length) {
                    if (xmlNamespaces.any(function (ns) {
                        return Wiki.camelNamespaces.any(ns);
                    })) {
                        prefix = start + "/camel/canvas";
                    } else if (xmlNamespaces.any(function (ns) {
                        return Wiki.dozerNamespaces.any(ns);
                    })) {
                        prefix = start + "/dozer/mappings";
                    } else {
                        console.log("child " + path + " has namespaces " + xmlNamespaces);
                    }
                }
                if (child.path.endsWith(".form")) {
                    postFix = "?form=/";
                } else if (Wiki.isIndexPage(child.path)) {
                    // lets default to book view on index pages
                    prefix = start + "/book";
                }
            }
            return Core.createHref($location, prefix + path + postFix, ["form"]);
        };

        $scope.fileName = function (entity) {
            return Wiki.hideFineNameExtensions(entity.name);
        };

        $scope.fileClass = function (entity) {
            if (entity.name.has(".profile")) {
                return "green";
            }
            return "";
        };

        $scope.fileIconHtml = function (entity) {
            return Wiki.fileIconHtml(entity);
        };

        $scope.format = Wiki.fileFormat($scope.pageId, fileExtensionTypeRegistry);
        var options = {
            readOnly: true,
            mode: {
                name: $scope.format
            }
        };
        $scope.codeMirrorOptions = CodeEditor.createEditorSettings(options);

        $scope.editLink = function () {
            var pageName = ($scope.directory) ? $scope.readMePath : $scope.pageId;
            return (pageName) ? Wiki.editLink($scope.branch, pageName, $location) : null;
        };

        $scope.branchLink = function (branch) {
            if (branch) {
                return Wiki.branchLink(branch, $scope.pageId, $location);
            }
            return null;
        };

        $scope.historyLink = "#/wiki" + ($scope.branch ? "/branch/" + $scope.branch : "") + "/history/" + $scope.pageId;

        $scope.$watch('workspace.tree', function () {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //log.info("Reloading view as the tree changed and we have a git mbean now");
                setTimeout(maybeUpdateView, 50);
            }
        });

        /*
        // TODO this doesn't work for some reason!
        $scope.$on('jmxTreeUpdated', function () {
        console.log("view: jmx tree updated!");
        });
        */
        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            //log.info("Reloading view due to $routeChangeSuccess");
            setTimeout(maybeUpdateView, 50);
        });

        $scope.onSubmit = function (json, form) {
            notification("success", "Submitted form :" + form.get(0).name + " data: " + JSON.stringify(json));
        };

        $scope.onCancel = function (form) {
            notification("success", "Clicked cancel!");
        };

        $scope.onCreateDocumentSelect = function (node) {
            $scope.selectedCreateDocumentTemplate = node ? node.entity : null;
            $scope.selectedCreateDocumentTemplateRegex = $scope.selectedCreateDocumentTemplate.regex || /.*/;
            checkFileExists(getNewDocumentPath());
        };

        $scope.$watch("newDocumentName", function () {
            checkFileExists(getNewDocumentPath());
        });

        $scope.openAddDialog = function () {
            $scope.newDocumentName = null;
            $scope.addDialog.open();
        };

        $scope.addAndCloseDialog = function (fileName) {
            $scope.newDocumentName = fileName;
            var template = $scope.selectedCreateDocumentTemplate;
            var path = getNewDocumentPath();
            if (!template || !path) {
                return;
            }
            var name = Wiki.fileName(path);
            var fileName = name;
            var folder = Wiki.fileParent(path);
            var exemplar = template.exemplar;

            var commitMessage = "Created " + template.label;
            var exemplarUri = url("/app/wiki/exemplar/" + exemplar);

            if (template.folder) {
                notification("success", "Creating new folder " + name);

                wikiRepository.createDirectory($scope.branch, path, commitMessage, function (status) {
                    $scope.addDialog.close();
                    Core.$apply($scope);
                    var link = Wiki.viewLink($scope.branch, path, $location);
                    goToLink(link, $timeout, $location);
                });
            } else if (template.profile) {
                function toPath(profileName) {
                    var answer = "fabric/profiles/" + profileName;
                    answer = answer.replace(/-/g, "/");
                    answer = answer + ".profile";
                    return answer;
                }

                function toProfileName(path) {
                    var answer = path.replace(/^fabric\/profiles\//, "");
                    answer = answer.replace(/\//g, "-");
                    answer = answer.replace(/\.profile$/, "");
                    return answer;
                }

                // strip off any profile name in case the user creates a profile while looking at
                // another profile
                folder = folder.replace(/\/=?(\w*)\.profile$/, "");

                var concatenated = folder + "/" + name;

                var profileName = toProfileName(concatenated);
                var targetPath = toPath(profileName);

                $scope.addDialog.close();

                Fabric.createProfile(workspace.jolokia, $scope.branch, profileName, ['default'], function () {
                    notification('success', 'Created profile ' + profileName);
                    Core.$apply($scope);

                    Fabric.newConfigFile(workspace.jolokia, $scope.branch, profileName, 'ReadMe.md', function () {
                        notification('info', 'Created empty Readme.md in profile ' + profileName);
                        Core.$apply($scope);

                        var contents = "Here's an empty ReadMe.md for '" + profileName + "', please update!";

                        Fabric.saveConfigFile(workspace.jolokia, $scope.branch, profileName, 'ReadMe.md', contents.encodeBase64(), function () {
                            notification('info', 'Updated Readme.md in profile ' + profileName);
                            Core.$apply($scope);
                            var link = Wiki.viewLink($scope.branch, targetPath, $location);
                            goToLink(link, $timeout, $location);
                        }, function (response) {
                            notification('error', 'Failed to set ReadMe.md data in profile ' + profileName + ' due to ' + response.error);
                            Core.$apply($scope);
                        });
                    }, function (response) {
                        notification('error', 'Failed to create ReadMe.md in profile ' + profileName + ' due to ' + response.error);
                        Core.$apply($scope);
                    });
                }, function (response) {
                    notification('error', 'Failed to create profile ' + profileName + ' due to ' + response.error);
                    Core.$apply($scope);
                });
            } else if (template.version) {
                if (name === exemplar) {
                    name = '';
                }
                Fabric.doCreateVersion($scope, jolokia, $location, name);
            } else if (template.generated) {
                $scope.addDialog.close();

                var generateDialog = $scope.generateDialog;
                $scope.formSchema = template.generated.schema;
                $scope.formData = template.generated.form(workspace, $scope);
                $scope.generate = function () {
                    template.generated.generate(workspace, $scope.formData, function (contents) {
                        generateDialog.close();
                        wikiRepository.putPageBase64($scope.branch, path, contents, commitMessage, function (status) {
                            console.log("Created file " + name);
                            Wiki.onComplete(status);
                            $scope.generateDialog.close();
                            updateView();
                        });
                    }, function (error) {
                        generateDialog.close();
                        notification('error', error);
                    });
                };
                generateDialog.open();
            } else {
                notification("success", "Creating new document " + name);

                $http.get(exemplarUri).success(function (contents) {
                    // TODO lets check this page does not exist - if it does lets keep adding a new post fix...
                    wikiRepository.putPage($scope.branch, path, contents, commitMessage, function (status) {
                        console.log("Created file " + name);
                        Wiki.onComplete(status);

                        // lets navigate to the edit link
                        // load the directory and find the child item
                        $scope.git = wikiRepository.getPage($scope.branch, folder, $scope.objectId, function (details) {
                            // lets find the child entry so we can calculate its correct edit link
                            var link = null;
                            if (details && details.children) {
                                console.log("scanned the directory " + details.children.length + " children");
                                var child = details.children.find(function (c) {
                                    return c.name === fileName;
                                });
                                if (child) {
                                    link = $scope.childLink(child);
                                } else {
                                    console.log("Could not find name '" + fileName + "' in the list of file names " + JSON.stringify(details.children.map(function (c) {
                                        return c.name;
                                    })));
                                }
                            }
                            if (!link) {
                                console.log("WARNING: could not find the childLink so reverting to the wiki edit page!");
                                link = Wiki.editLink($scope.branch, path, $location);
                            }
                            $scope.addDialog.close();
                            Core.$apply($scope);
                            goToLink(link, $timeout, $location);
                        });
                    });
                });
            }
            $scope.addDialog.close();
        };

        $scope.openDeleteDialog = function () {
            if ($scope.gridOptions.selectedItems.length) {
                $scope.selectedFileHtml = "<ul>" + $scope.gridOptions.selectedItems.map(function (file) {
                    return "<li>" + file.name + "</li>";
                }).sort().join("") + "</ul>";
                $scope.deleteDialog.open();
            } else {
                console.log("No items selected right now! " + $scope.gridOptions.selectedItems);
            }
        };

        $scope.deleteAndCloseDialog = function () {
            var files = $scope.gridOptions.selectedItems;
            var fileCount = files.length;
            console.log("Deleting selection: " + files);
            angular.forEach(files, function (file, idx) {
                var path = $scope.pageId + "/" + file.name;
                console.log("About to delete " + path);
                $scope.git = wikiRepository.removePage($scope.branch, path, null, function (result) {
                    if (idx + 1 === fileCount) {
                        $scope.gridOptions.selectedItems.splice(0, fileCount);
                        var message = Core.maybePlural(fileCount, "document");
                        notification("success", "Deleted " + message);
                        Core.$apply($scope);
                        updateView();
                    }
                });
            });
            $scope.deleteDialog.close();
        };

        $scope.$watch("rename.newFileName", function () {
            // ignore errors if the file is the same as the rename file!
            var path = getRenameFilePath();
            if ($scope.originalRenameFilePath === path) {
                $scope.fileExists = { exsits: false, name: null };
            } else {
                checkFileExists(path);
            }
        });

        $scope.openRenameDialog = function () {
            var name = null;
            if ($scope.gridOptions.selectedItems.length) {
                var selected = $scope.gridOptions.selectedItems[0];
                name = selected.name;
            }
            if (name) {
                $scope.rename.newFileName = name;
                $scope.originalRenameFilePath = getRenameFilePath();
                $scope.renameDialog.open();
                $timeout(function () {
                    $('#renameFileName').focus();
                }, 50);
            } else {
                console.log("No items selected right now! " + $scope.gridOptions.selectedItems);
            }
        };

        $scope.renameAndCloseDialog = function () {
            if ($scope.gridOptions.selectedItems.length) {
                var selected = $scope.gridOptions.selectedItems[0];
                var newPath = getRenameFilePath();
                if (selected && newPath) {
                    var oldName = selected.name;
                    var newName = Wiki.fileName(newPath);
                    var oldPath = $scope.pageId + "/" + oldName;
                    console.log("About to rename file " + oldPath + " to " + newPath);
                    $scope.git = wikiRepository.rename($scope.branch, oldPath, newPath, null, function (result) {
                        notification("success", "Renamed file to  " + newName);
                        $scope.gridOptions.selectedItems.splice(0, 1);
                        $scope.renameDialog.close();
                        Core.$apply($scope);
                        updateView();
                    });
                }
            }
            $scope.renameDialog.close();
        };

        $scope.openMoveDialog = function () {
            if ($scope.gridOptions.selectedItems.length) {
                $scope.move.moveFolder = $scope.pageId;
                $scope.moveDialog.open();
                $timeout(function () {
                    $('#moveFolder').focus();
                }, 50);
            } else {
                console.log("No items selected right now! " + $scope.gridOptions.selectedItems);
            }
        };

        $scope.moveAndCloseDialog = function () {
            var files = $scope.gridOptions.selectedItems;
            var fileCount = files.length;
            var moveFolder = $scope.move.moveFolder;
            var oldFolder = $scope.pageId;
            if (moveFolder && fileCount && moveFolder !== oldFolder) {
                console.log("Moving " + fileCount + " file(s) to " + moveFolder);
                angular.forEach(files, function (file, idx) {
                    var oldPath = oldFolder + "/" + file.name;
                    var newPath = moveFolder + "/" + file.name;
                    console.log("About to move " + oldPath + " to " + newPath);
                    $scope.git = wikiRepository.rename($scope.branch, oldPath, newPath, null, function (result) {
                        if (idx + 1 === fileCount) {
                            $scope.gridOptions.selectedItems.splice(0, fileCount);
                            var message = Core.maybePlural(fileCount, "document");
                            notification("success", "Moved " + message + " to " + newPath);
                            $scope.moveDialog.close();
                            Core.$apply($scope);
                            updateView();
                        }
                    });
                });
            }
            $scope.moveDialog.close();
        };

        $scope.folderNames = function (text) {
            return wikiRepository.completePath($scope.branch, text, true, null);
        };

        setTimeout(maybeUpdateView, 50);

        function isDiffView() {
            var path = $location.path();
            return path && (path.startsWith("/wiki/diff") || path.startsWith("/wiki/branch/" + $scope.branch + "/diff"));
        }

        function updateView() {
            if (isDiffView()) {
                var baseObjectId = $routeParams["baseObjectId"];
                $scope.git = wikiRepository.diff($scope.objectId, baseObjectId, $scope.pageId, onFileDetails);
            } else {
                $scope.git = wikiRepository.getPage($scope.branch, $scope.pageId, $scope.objectId, onFileDetails);
            }
            Wiki.loadBranches(wikiRepository, $scope);
        }

        $scope.updateView = updateView;

        function viewContents(pageName, contents) {
            $scope.sourceView = null;

            var format = null;
            if (isDiffView()) {
                format = "diff";
            } else {
                format = Wiki.fileFormat(pageName, fileExtensionTypeRegistry) || $scope.format;
            }
            if ("markdown" === format) {
                // lets convert it to HTML
                $scope.html = contents ? marked(contents) : "";
                $scope.html = $compile($scope.html)($scope);
            } else if (format && format.startsWith("html")) {
                $scope.html = contents;
                $compile($scope.html)($scope);
            } else {
                var form = null;
                if (format && format === "javascript") {
                    form = $location.search()["form"];
                }
                $scope.source = contents;
                $scope.form = form;
                if (form) {
                    // now lets try load the form JSON so we can then render the form
                    $scope.sourceView = null;
                    if (form === "/") {
                        onFormSchema(_jsonSchema);
                    } else {
                        $scope.git = wikiRepository.getPage($scope.branch, form, $scope.objectId, function (details) {
                            onFormSchema(Wiki.parseJson(details.text));
                        });
                    }
                } else {
                    $scope.sourceView = "app/wiki/html/sourceView.html";
                }
            }
            Core.$apply($scope);
        }

        function onFormSchema(json) {
            $scope.formDefinition = json;
            if ($scope.source) {
                $scope.formEntity = Wiki.parseJson($scope.source);
            }
            $scope.sourceView = "app/wiki/html/formView.html";
            Core.$apply($scope);
        }

        function onFileDetails(details) {
            var contents = details.text;
            $scope.directory = details.directory;

            if (details && details.format) {
                $scope.format = details.format;
            } else {
                $scope.format = Wiki.fileFormat($scope.pageId, fileExtensionTypeRegistry);
            }
            $scope.codeMirrorOptions.mode.name = $scope.format;

            //console.log("format is '" + $scope.format + "'");
            $scope.children = null;

            if (details.directory) {
                var directories = details.children.filter(function (dir) {
                    return dir.directory && !dir.name.has(".profile");
                });
                var profiles = details.children.filter(function (dir) {
                    return dir.directory && dir.name.has(".profile");
                });
                var files = details.children.filter(function (file) {
                    return !file.directory;
                });

                directories = directories.sortBy(function (dir) {
                    return dir.name;
                });
                profiles = profiles.sortBy(function (dir) {
                    return dir.name;
                });

                files = files.sortBy(function (file) {
                    return file.name;
                }).sortBy(function (file) {
                    return file.name.split('.').last();
                });

                $scope.children = Array.create(directories, profiles, files);
            }

            $scope.html = null;
            $scope.source = null;
            $scope.readMePath = null;

            $scope.isFile = false;
            if ($scope.children) {
                // if we have a readme then lets render it...
                var item = $scope.children.find(function (info) {
                    var name = (info.name || "").toLowerCase();
                    var ext = Wiki.fileExtension(name);
                    return name && ext && ((name.startsWith("readme.") || name === "readme") || (name.startsWith("index.") || name === "index"));
                });
                if (item) {
                    var pageName = item.path;
                    $scope.readMePath = pageName;
                    wikiRepository.getPage($scope.branch, pageName, $scope.objectId, function (readmeDetails) {
                        viewContents(pageName, readmeDetails.text);
                    });
                }
            } else {
                var pageName = $scope.pageId;
                viewContents(pageName, contents);
                $scope.isFile = true;
            }
            Core.$apply($scope);
        }

        function checkFileExists(path) {
            $scope.operationCounter += 1;
            var counter = $scope.operationCounter;
            if (path) {
                wikiRepository.exists($scope.branch, path, function (result) {
                    // filter old results
                    if ($scope.operationCounter === counter) {
                        console.log("for path " + path + " got result " + result);
                        $scope.fileExists.exists = result ? true : false;
                        $scope.fileExists.name = result ? result.name : null;
                        Core.$apply($scope);
                    } else {
                        // console.log("Ignoring old results for " + path);
                    }
                });
            }
        }

        // Called by hawtio TOC directive...
        $scope.getContents = function (filename, cb) {
            var pageId = filename;
            if ($scope.directory) {
                pageId = $scope.pageId + '/' + filename;
            } else {
                var pathParts = $scope.pageId.split('/');
                pathParts = pathParts.remove(pathParts.last());
                pathParts.push(filename);
                pageId = pathParts.join('/');
            }
            log.debug("pageId: ", $scope.pageId);
            log.debug("branch: ", $scope.branch);
            log.debug("filename: ", filename);
            log.debug("using pageId: ", pageId);
            wikiRepository.getPage($scope.branch, pageId, undefined, function (data) {
                cb(data.text);
            });
        };

        function getNewDocumentPath() {
            var template = $scope.selectedCreateDocumentTemplate;
            if (!template) {
                console.log("No template selected.");
                return null;
            }
            var exemplar = template.exemplar || "";
            var name = $scope.newDocumentName || exemplar;

            if (name.indexOf('.') < 0) {
                // lets add the file extension from the exemplar
                var idx = exemplar.lastIndexOf(".");
                if (idx > 0) {
                    name += exemplar.substring(idx);
                }
            }

            // lets deal with directories in the name
            var folder = $scope.pageId;
            if ($scope.isFile) {
                // if we are a file lets discard the last part of the path
                var idx = folder.lastIndexOf("/");
                if (idx <= 0) {
                    folder = "";
                } else {
                    folder = folder.substring(0, idx);
                }
            }
            var fileName = name;
            var idx = name.lastIndexOf("/");
            if (idx > 0) {
                folder += "/" + name.substring(0, idx);
                name = name.substring(idx + 1);
            }
            folder = Core.trimLeading(folder, "/");
            return folder + (folder ? "/" : "") + name;
        }

        function getRenameFilePath() {
            var newFileName = $scope.rename.newFileName;
            return ($scope.pageId && newFileName) ? $scope.pageId + "/" + newFileName : null;
        }
    }
    Wiki.ViewController = ViewController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function NavBarController($scope, $location, $routeParams, workspace, wikiRepository) {
        Wiki.initScope($scope, $routeParams, $location);

        $scope.createLink = function () {
            var pageId = Wiki.pageId($routeParams, $location);
            return Wiki.createLink($scope.branch, pageId, $location, $scope);
        };

        $scope.startLink = Wiki.startLink($scope.branch);

        $scope.sourceLink = function () {
            var path = $location.path();
            var answer = null;
            angular.forEach(Wiki.customViewLinks($scope), function (link) {
                if (path.startsWith(link)) {
                    answer = Core.createHref($location, Wiki.startLink($scope.branch) + "/view" + path.substring(link.length));
                }
            });

            // remove the form parameter on view/edit links
            return (!answer && $location.search()["form"]) ? Core.createHref($location, "#" + path, ["form"]) : answer;
        };

        $scope.isActive = function (href) {
            if (!href) {
                return false;
            }
            return href.endsWith($routeParams['page']);
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(loadBreadcrumbs, 50);
        });

        loadBreadcrumbs();

        function switchFromViewToCustomLink(breadcrumb, link) {
            var href = breadcrumb.href;
            if (href) {
                breadcrumb.href = href.replace("wiki/view", link);
            }
        }

        function loadBreadcrumbs() {
            var start = Wiki.startLink($scope.branch);
            var href = start + "/view";
            $scope.breadcrumbs = [
                { href: href, name: "root" }
            ];
            var path = Wiki.pageId($routeParams, $location);
            var array = path ? path.split("/") : [];
            angular.forEach(array, function (name) {
                if (!name.startsWith("/") && !href.endsWith("/")) {
                    href += "/";
                }
                href += Wiki.encodePath(name);
                if (!name.isBlank()) {
                    $scope.breadcrumbs.push({ href: href, name: name });
                }
            });

            // lets swizzle the last one or two to be formTable views if the last or 2nd to last
            var loc = $location.path();
            if ($scope.breadcrumbs.length) {
                var last = $scope.breadcrumbs[$scope.breadcrumbs.length - 1];

                // possibly trim any required file extensions
                last.name = Wiki.hideFineNameExtensions(last.name);

                var swizzled = false;
                angular.forEach(Wiki.customViewLinks($scope), function (link) {
                    if (!swizzled && loc.startsWith(link)) {
                        // lets swizzle the view to the current link
                        switchFromViewToCustomLink($scope.breadcrumbs.last(), Core.trimLeading(link, "/"));
                        swizzled = true;
                    }
                });
                if (!swizzled && $location.search()["form"]) {
                    var lastName = $scope.breadcrumbs.last().name;
                    if (lastName && lastName.endsWith(".json")) {
                        // previous breadcrumb should be a formTable
                        switchFromViewToCustomLink($scope.breadcrumbs[$scope.breadcrumbs.length - 2], "wiki/formTable");
                    }
                }
            }

            /*
            if (loc.startsWith("/wiki/history") || loc.startsWith("/wiki/version")
            || loc.startsWith("/wiki/diff") || loc.startsWith("/wiki/commit")) {
            // lets add a history tab
            $scope.breadcrumbs.push({href: "#/wiki/history/" + path, name: "History"});
            } else if ($scope.branch) {
            var prefix ="/wiki/branch/" + $scope.branch;
            if (loc.startsWith(prefix + "/history") || loc.startsWith(prefix + "/version")
            || loc.startsWith(prefix + "/diff") || loc.startsWith(prefix + "/commit")) {
            // lets add a history tab
            $scope.breadcrumbs.push({href: "#/wiki/branch/" + $scope.branch + "/history/" + path, name: "History"});
            }
            }
            */
            var name = null;
            if (loc.startsWith("/wiki/version")) {
                // lets add a version tab
                name = ($routeParams["objectId"] || "").substring(0, 6) || "Version";
                $scope.breadcrumbs.push({ href: "#" + loc, name: name });
            }
            if (loc.startsWith("/wiki/diff")) {
                // lets add a version tab
                var v1 = ($routeParams["objectId"] || "").substring(0, 6);
                var v2 = ($routeParams["baseObjectId"] || "").substring(0, 6);
                name = "Diff";
                if (v1) {
                    if (v2) {
                        name += " " + v1 + " " + v2;
                    } else {
                        name += " " + v1;
                    }
                }
                $scope.breadcrumbs.push({ href: "#" + loc, name: name });
            }
            Core.$apply($scope);
        }
    }
    Wiki.NavBarController = NavBarController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function EditController($scope, $location, $routeParams, fileExtensionTypeRegistry, wikiRepository) {
        Wiki.initScope($scope, $routeParams, $location);
        $scope.entity = {
            source: null
        };

        var format = Wiki.fileFormat($scope.pageId, fileExtensionTypeRegistry);
        var form = null;
        if ((format && format === "javascript") || isCreate()) {
            form = $location.search()["form"];
        }

        var options = {
            mode: {
                name: format
            }
        };
        $scope.codeMirrorOptions = CodeEditor.createEditorSettings(options);
        $scope.modified = false;

        $scope.isValid = function () {
            return $scope.fileName;
        };

        $scope.canSave = function () {
            return !$scope.modified;
        };

        $scope.$watch('entity.source', function (newValue, oldValue) {
            $scope.modified = newValue && oldValue && newValue !== oldValue;
        }, true);

        Wiki.log.debug("path: ", $scope.path);

        $scope.$watch('modified', function (newValue, oldValue) {
            Wiki.log.debug("modified: ", newValue);
        });

        $scope.viewLink = function () {
            return Wiki.viewLink($scope.branch, $scope.pageId, $location, $scope.fileName);
        };

        $scope.cancel = function () {
            goToView();
        };

        $scope.save = function () {
            if ($scope.modified && $scope.fileName) {
                saveTo($scope["pageId"]);
            }
        };

        $scope.create = function () {
            // lets combine the file name with the current pageId (which is the directory)
            var path = $scope.pageId + "/" + $scope.fileName;
            console.log("creating new file at " + path);
            saveTo(path);
        };

        $scope.onSubmit = function (json, form) {
            if (isCreate()) {
                $scope.create();
            } else {
                $scope.save();
            }
        };

        $scope.onCancel = function (form) {
            setTimeout(function () {
                goToView();
                Core.$apply($scope);
            }, 50);
        };

        updateView();

        function isCreate() {
            return $location.path().startsWith("/wiki/create");
        }

        function updateView() {
            // only load the source if not in create mode
            if (isCreate()) {
                updateSourceView();
            } else {
                Wiki.log.debug("Getting page, branch: ", $scope.branch, " pageId: ", $scope.pageId, " objectId: ", $scope.objectId);
                wikiRepository.getPage($scope.branch, $scope.pageId, $scope.objectId, onFileContents);
            }
        }

        function onFileContents(details) {
            var contents = details.text;
            $scope.entity.source = contents;
            $scope.fileName = $scope.pageId.split('/').last();
            Wiki.log.debug("file name: ", $scope.fileName);
            Wiki.log.debug("file details: ", details);
            updateSourceView();
            Core.$apply($scope);
        }

        function updateSourceView() {
            if (form) {
                if (isCreate()) {
                    // lets default a file name
                    if (!$scope.fileName) {
                        $scope.fileName = "" + Core.getUUID() + ".json";
                    }
                }

                // now lets try load the form defintion JSON so we can then render the form
                $scope.sourceView = null;
                if (form === "/") {
                    onFormSchema(_jsonSchema);
                } else {
                    $scope.git = wikiRepository.getPage($scope.branch, form, $scope.objectId, function (details) {
                        onFormSchema(Wiki.parseJson(details.text));
                    });
                }
            } else {
                $scope.sourceView = "app/wiki/html/sourceEdit.html";
            }
        }

        function onFormSchema(json) {
            $scope.formDefinition = json;
            if ($scope.entity.source) {
                $scope.formEntity = Wiki.parseJson($scope.entity.source);
            }
            $scope.sourceView = "app/wiki/html/formEdit.html";
            Core.$apply($scope);
        }

        function goToView() {
            var path = Core.trimLeading($scope.viewLink(), "#");
            Wiki.log.debug("going to view " + path);
            $location.path(Wiki.decodePath(path));
            Wiki.log.debug("location is now " + $location.path());
        }

        function saveTo(path) {
            var commitMessage = $scope.commitMessage || "Updated page " + $scope.pageId;
            var contents = $scope.entity.source;
            if ($scope.formEntity) {
                contents = JSON.stringify($scope.formEntity, null, "  ");
            }
            Wiki.log.debug("Saving file, branch: ", $scope.branch, " path: ", $scope.path);

            //console.log("About to write contents '" + contents + "'");
            wikiRepository.putPage($scope.branch, path, contents, commitMessage, function (status) {
                Wiki.onComplete(status);
                $scope.modified = false;
                notification("success", "Saved " + path);
                goToView();
                Core.$apply($scope);
            });
        }
    }
    Wiki.EditController = EditController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    

    /**
    * @class GitWikiRepository
    */
    var GitWikiRepository = (function () {
        function GitWikiRepository(factoryMethod) {
            this.factoryMethod = factoryMethod;
            this.directoryPrefix = "";
        }
        GitWikiRepository.prototype.getRepositoryLabel = function (fn, error) {
            this.git().getRepositoryLabel(fn, error);
        };

        GitWikiRepository.prototype.exists = function (branch, path, fn) {
            var fullPath = this.getPath(path);
            this.git().exists(branch, fullPath, fn);
        };

        GitWikiRepository.prototype.completePath = function (branch, completionText, directoriesOnly, fn) {
            return this.git().completePath(branch, completionText, directoriesOnly, fn);
        };

        GitWikiRepository.prototype.getPage = function (branch, path, objectId, fn) {
            var _this = this;
            var git = this.git();
            path = path || "/";
            if (git) {
                if (objectId) {
                    var blobPath = this.getLogPath(path);

                    // TODO deal with versioned directories?
                    git.getContent(objectId, blobPath, function (content) {
                        var details = {
                            text: content,
                            directory: false
                        };
                        fn(details);
                    });
                } else {
                    var fullPath = this.getPath(path);
                    git.read(branch, fullPath, function (details) {
                        // lets fix up any paths to be relative to the wiki
                        var children = details.children;
                        angular.forEach(children, function (child) {
                            var path = child.path;
                            if (path) {
                                var directoryPrefix = "/" + _this.directoryPrefix;
                                if (path.startsWith(directoryPrefix)) {
                                    path = "/" + path.substring(directoryPrefix.length);
                                    child.path = path;
                                }
                            }
                        });
                        fn(details);
                    });
                }
            }
            return git;
        };

        /**
        * Performs a diff on the versions
        * @method diff
        * @for GitWikiRepository
        * @param {String} objectId
        * @param {String} baseObjectId
        * @param {String} path
        * @param {Function} fn
        * @return {any}
        */
        GitWikiRepository.prototype.diff = function (objectId, baseObjectId, path, fn) {
            var fullPath = this.getLogPath(path);
            var git = this.git();
            if (git) {
                git.diff(objectId, baseObjectId, fullPath, function (content) {
                    var details = {
                        text: content,
                        format: "diff",
                        directory: false
                    };
                    fn(details);
                });
            }
            return git;
        };

        GitWikiRepository.prototype.commitInfo = function (commitId, fn) {
            this.git().commitInfo(commitId, fn);
        };

        GitWikiRepository.prototype.commitTree = function (commitId, fn) {
            this.git().commitTree(commitId, fn);
        };

        GitWikiRepository.prototype.putPage = function (branch, path, contents, commitMessage, fn) {
            var fullPath = this.getPath(path);
            this.git().write(branch, fullPath, commitMessage, contents, fn);
        };

        GitWikiRepository.prototype.putPageBase64 = function (branch, path, contents, commitMessage, fn) {
            var fullPath = this.getPath(path);
            this.git().writeBase64(branch, fullPath, commitMessage, contents, fn);
        };

        GitWikiRepository.prototype.createDirectory = function (branch, path, commitMessage, fn) {
            var fullPath = this.getPath(path);
            this.git().createDirectory(branch, fullPath, commitMessage, fn);
        };

        GitWikiRepository.prototype.revertTo = function (branch, objectId, blobPath, commitMessage, fn) {
            var fullPath = this.getLogPath(blobPath);
            this.git().revertTo(branch, objectId, fullPath, commitMessage, fn);
        };

        GitWikiRepository.prototype.rename = function (branch, oldPath, newPath, commitMessage, fn) {
            var fullOldPath = this.getPath(oldPath);
            var fullNewPath = this.getPath(newPath);
            if (!commitMessage) {
                commitMessage = "Renaming page " + oldPath + " to " + newPath;
            }
            this.git().rename(branch, fullOldPath, fullNewPath, commitMessage, fn);
        };

        GitWikiRepository.prototype.removePage = function (branch, path, commitMessage, fn) {
            var fullPath = this.getPath(path);
            if (!commitMessage) {
                commitMessage = "Removing page " + path;
            }
            this.git().remove(branch, fullPath, commitMessage, fn);
        };

        /**
        * Returns the full path to use in the git repo
        * @method getPath
        * @for GitWikiRepository
        * @param {String} path
        * @return {String{
        */
        GitWikiRepository.prototype.getPath = function (path) {
            var directoryPrefix = this.directoryPrefix;
            return (directoryPrefix) ? directoryPrefix + path : path;
        };

        GitWikiRepository.prototype.getLogPath = function (path) {
            return Core.trimLeading(this.getPath(path), "/");
        };

        /**
        * Return the history of the repository or a specific directory or file path
        * @method history
        * @for GitWikiRepository
        * @param {String} branch
        * @param {String} objectId
        * @param {String} path
        * @param {Number} limit
        * @param {Function} fn
        * @return {any}
        */
        GitWikiRepository.prototype.history = function (branch, objectId, path, limit, fn) {
            var fullPath = this.getLogPath(path);
            var git = this.git();
            if (git) {
                git.history(branch, objectId, fullPath, limit, fn);
            }
            return git;
        };

        /**
        * Get the contents of a blobPath for a given commit objectId
        * @method getContent
        * @for GitWikiRepository
        * @param {String} objectId
        * @param {String} blobPath
        * @param {Function} fn
        * @return {any}
        */
        GitWikiRepository.prototype.getContent = function (objectId, blobPath, fn) {
            var fullPath = this.getLogPath(blobPath);
            var git = this.git();
            if (git) {
                git.getContent(objectId, fullPath, fn);
            }
            return git;
        };

        /**
        * Get the list of branches
        * @method branches
        * @for GitWikiRepository
        * @param {Function} fn
        * @return {any}
        */
        GitWikiRepository.prototype.branches = function (fn) {
            var git = this.git();
            if (git) {
                git.branches(fn);
            }
            return git;
        };

        /**
        * Get the JSON contents of the path with optional name wildcard and search
        * @method jsonChildContents
        * @for GitWikiRepository
        * @param {String} path
        * @param {String} nameWildcard
        * @param {String} search
        * @param {Function} fn
        * @return {any}
        */
        GitWikiRepository.prototype.jsonChildContents = function (path, nameWildcard, search, fn) {
            var fullPath = this.getLogPath(path);
            var git = this.git();
            if (git) {
                git.readJsonChildContent(fullPath, nameWildcard, search, fn);
            }
            return git;
        };

        GitWikiRepository.prototype.git = function () {
            var repository = this.factoryMethod();
            if (!repository) {
                console.log("No repository yet! TODO we should use a local impl!");
            }
            return repository;
        };
        return GitWikiRepository;
    })();
    Wiki.GitWikiRepository = GitWikiRepository;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    Wiki.log = Logger.get("Wiki");

    Wiki.camelNamespaces = ["http://camel.apache.org/schema/spring", "http://camel.apache.org/schema/blueprint"];
    Wiki.springNamespaces = ["http://www.springframework.org/schema/beans"];
    Wiki.droolsNamespaces = ["http://drools.org/schema/drools-spring"];
    Wiki.dozerNamespaces = ["http://dozer.sourceforge.net"];
    Wiki.activemqNamespaces = ["http://activemq.apache.org/schema/core"];

    Wiki.excludeAdjustmentPrefixes = ["http://", "https://", "#"];

    /**
    * The custom views within the wiki namespace; either "/wiki/$foo" or "/wiki/branch/$branch/$foo"
    */
    Wiki.customWikiViewPages = ["/formTable", "/camel/diagram", "/camel/canvas", "/camel/properties", "/dozer/mappings"];

    /**
    * Which extensions do we wish to hide in the wiki file listing
    * @property hideExtentions
    * @for Wiki
    * @type Array
    */
    Wiki.hideExtentions = [".profile"];

    /**
    * The wizard tree for creating new content in the wiki
    * @property documentTemplates
    * @for Wiki
    * @type Array
    */
    Wiki.documentTemplates = [
        {
            label: "Folder",
            tooltip: "Create a new folder to contain documents",
            folder: true,
            icon: "/img/icons/wiki/folder.gif",
            exemplar: "New Folder"
        },
        {
            label: "Fabric8 Profile",
            tooltip: "Create a new empty Fabric8 profile.  Using a hyphen ('-') will create a folder heirarchy, for example 'my-awesome-profile' will be available via the path 'my/awesome/profile'.",
            profile: true,
            addClass: "icon-book green",
            exemplar: "user-profile",
            fabricOnly: true
        },
        {
            label: "Fabric8 Version",
            tooltip: "Create a new Fabric8 version based on the latest available version.  Leave the name blank to use the next available version name.  Version names must be in the form of x.y.z, for example 1.2.foo is okay, 1.2-foo is not",
            version: true,
            addClass: "icon-code-fork green",
            exemplar: "1.1.MyVersion",
            fabricOnly: true,
            regex: /[1-9][0-9]*(\\.[0-9]+)*/
        },
        {
            label: "Properties File",
            tooltip: "A properties file typically used to configure Java classes",
            exemplar: "properties-file.properties"
        },
        {
            label: "Key Store File",
            tooltip: "Creates a keystore (database) of cryptographic keys, X.509 certificate chains, and trusted certificates.",
            exemplar: 'keystore.jks',
            generated: {
                mbean: ['hawtio', { type: 'KeystoreService' }],
                init: function (workspace, $scope) {
                    var mbean = 'hawtio:type=KeystoreService';
                    var response = workspace.jolokia.request({ type: "read", mbean: mbean, attribute: "SecurityProviderInfo" }, {
                        success: function (response) {
                            $scope.securityProviderInfo = response.value;
                        },
                        error: function (response) {
                            console.log('Could not find the supported security algorithms: ', response.error);
                        }
                    });
                },
                generate: function (workspace, form, success, error) {
                    var encodedForm = JSON.stringify(form);
                    var mbean = 'hawtio:type=KeystoreService';
                    var response = workspace.jolokia.request({
                        type: 'exec',
                        mbean: mbean,
                        operation: 'createKeyStoreViaJSON(java.lang.String)',
                        arguments: [encodedForm]
                    }, {
                        method: 'POST',
                        success: function (response) {
                            success(response.value);
                        },
                        error: function (response) {
                            error(response.error);
                        }
                    });
                },
                form: function (workspace, $scope) {
                    return {
                        storeType: $scope.securityProviderInfo.supportedKeyStoreTypes[0],
                        createPrivateKey: false,
                        keyLength: 4096,
                        keyAlgorithm: $scope.securityProviderInfo.supportedKeyAlgorithms[0],
                        keyValidity: 365
                    };
                },
                schema: {
                    "description": "Keystore Settings",
                    "type": "java.lang.String",
                    "properties": {
                        "storePassword": {
                            "description": "Keystore password.",
                            "type": "password",
                            'input-attributes': { "required": "", "ng-minlength": 6 }
                        },
                        "storeType": {
                            "description": "The type of store to create",
                            "type": "java.lang.String",
                            'input-element': "select",
                            'input-attributes': { "ng-options": "v for v in securityProviderInfo.supportedKeyStoreTypes" }
                        },
                        "createPrivateKey": {
                            "description": "Should we generate a self-signed private key?",
                            "type": "boolean"
                        },
                        "keyCommonName": {
                            "description": "The common name of the key, typically set to the hostname of the server",
                            "type": "java.lang.String",
                            'control-group-attributes': { 'ng-show': "formData.createPrivateKey" }
                        },
                        "keyLength": {
                            "description": "The length of the cryptographic key",
                            "type": "Long",
                            'control-group-attributes': { 'ng-show': "formData.createPrivateKey" }
                        },
                        "keyAlgorithm": {
                            "description": "The key algorithm",
                            "type": "java.lang.String",
                            'input-element': "select",
                            'input-attributes': { "ng-options": "v for v in securityProviderInfo.supportedKeyAlgorithms" },
                            'control-group-attributes': { 'ng-show': "formData.createPrivateKey" }
                        },
                        "keyValidity": {
                            "description": "The number of days the key will be valid for",
                            "type": "Long",
                            'control-group-attributes': { 'ng-show': "formData.createPrivateKey" }
                        },
                        "keyPassword": {
                            "description": "Password to the private key",
                            "type": "password",
                            'control-group-attributes': { 'ng-show': "formData.createPrivateKey" }
                        }
                    }
                }
            }
        },
        {
            label: "Markdown Document",
            tooltip: "A basic markup document using the Markdown wiki markup, particularly useful for ReadMe files in directories",
            exemplar: "ReadMe.md"
        },
        {
            label: "HTML Document",
            tooltip: "A HTML document you can edit directly using the HTML markup",
            exemplar: "document.html"
        },
        {
            label: "XML Document",
            tooltip: "An empty XML document",
            exemplar: "document.xml"
        },
        {
            label: "Integration Flows",
            tooltip: "Camel routes for defining your integration flows",
            children: [
                {
                    label: "Camel XML document",
                    tooltip: "A vanilla Camel XML document for integration flows",
                    icon: "/img/icons/camel.svg",
                    exemplar: "camel.xml"
                },
                {
                    label: "Camel OSGi Blueprint XML document",
                    tooltip: "A vanilla Camel XML document for integration flows when using OSGi Blueprint",
                    icon: "/img/icons/camel.svg",
                    exemplar: "camel-blueprint.xml"
                },
                {
                    label: "Camel Spring XML document",
                    tooltip: "A vanilla Camel XML document for integration flows when using the Spring framework",
                    icon: "/img/icons/camel.svg",
                    exemplar: "camel-spring.xml"
                }
            ]
        },
        {
            label: "Data Mapping Document",
            tooltip: "Dozer based configuration of mapping documents",
            icon: "/app/dozer/img/dozer.gif",
            exemplar: "dozerMapping.xml"
        }
    ];

    function isWikiEnabled(workspace, jolokia, localStorage) {
        return Git.createGitRepository(workspace, jolokia, localStorage) !== null;
    }
    Wiki.isWikiEnabled = isWikiEnabled;

    /**
    * Returns all the links for the given branch for the custom views, starting with "/"
    * @param $scope
    * @returns {string[]}
    */
    function customViewLinks($scope) {
        var branch = $scope.branch;
        var prefix = Core.trimLeading(Wiki.startLink(branch), "#");
        return Wiki.customWikiViewPages.map(function (path) {
            return prefix + path;
        });
    }
    Wiki.customViewLinks = customViewLinks;

    /**
    * Returns a new create document wizard tree
    * @method createWizardTree
    * @for Wiki
    * @static
    */
    function createWizardTree(workspace, $scope) {
        var root = new Folder("New Documents");
        addCreateWizardFolders(workspace, $scope, root, Wiki.documentTemplates);
        return root;
    }
    Wiki.createWizardTree = createWizardTree;

    function addCreateWizardFolders(workspace, $scope, parent, templates) {
        angular.forEach(templates, function (template) {
            if (template['fabricOnly'] && !Fabric.hasFabric(workspace)) {
                return;
            }

            if (template.generated) {
                if (template.generated.mbean) {
                    var exists = workspace.treeContainsDomainAndProperties.apply(workspace, template.generated.mbean);
                    if (!exists) {
                        return;
                    }
                }
                if (template.generated.init) {
                    template.generated.init(workspace, $scope);
                    template.generated.init = null;
                }
            }

            var title = template.label || key;
            var node = new Folder(title);
            node.parent = parent;
            node.entity = template;

            var addClass = template.addClass;
            if (addClass) {
                node.addClass = addClass;
            }

            var key = template.exemplar;
            var parentKey = parent.key || "";
            node.key = parentKey ? parentKey + "_" + key : key;
            var icon = template.icon;
            if (icon) {
                node.icon = url(icon);
            }

            // compiler was complaining about 'label' had no idea where it's coming from
            // var tooltip = value["tooltip"] || value["description"] || label;
            var tooltip = template["tooltip"] || template["description"] || '';
            node.tooltip = tooltip;
            if (template["folder"]) {
                node.isFolder = function () {
                    return true;
                };
            }
            parent.children.push(node);

            var children = template.children;
            if (children) {
                addCreateWizardFolders(workspace, $scope, node, children);
            }
        });
    }
    Wiki.addCreateWizardFolders = addCreateWizardFolders;

    function startLink(branch) {
        var start = "#/wiki";
        if (branch) {
            start += "/branch/" + branch;
        }
        return start;
    }
    Wiki.startLink = startLink;

    /**
    * Returns true if the given filename/path is an index page (named index.* and is a markdown/html page).
    *
    * @param path
    * @returns {boolean}
    */
    function isIndexPage(path) {
        return path && (path.endsWith("index.md") || path.endsWith("index.html") || path.endsWith("index")) ? true : false;
    }
    Wiki.isIndexPage = isIndexPage;

    function viewLink(branch, pageId, $location, fileName) {
        if (typeof fileName === "undefined") { fileName = null; }
        var link = null;
        var start = startLink(branch);
        if (pageId) {
            // figure out which view to use for this page
            var view = isIndexPage(pageId) ? "/book/" : "/view/";
            link = start + view + encodePath(Core.trimLeading(pageId, "/"));
        } else {
            // lets use the current path
            var path = $location.path();
            link = "#" + path.replace(/(edit|create)/, "view");
        }
        if (fileName && pageId && pageId.endsWith(fileName)) {
            return link;
        }
        if (fileName) {
            if (!link.endsWith("/")) {
                link += "/";
            }
            link += fileName;
        }
        return link;
    }
    Wiki.viewLink = viewLink;

    function branchLink(branch, pageId, $location, fileName) {
        if (typeof fileName === "undefined") { fileName = null; }
        return viewLink(branch, pageId, $location, fileName);
    }
    Wiki.branchLink = branchLink;

    function editLink(branch, pageId, $location) {
        var link = null;
        var start = startLink(branch);
        if (pageId) {
            link = start + "/edit/" + encodePath(pageId);
        } else {
            // lets use the current path
            var path = $location.path();
            link = "#" + path.replace(/(view|create)/, "edit");
        }
        return link;
    }
    Wiki.editLink = editLink;

    function createLink(branch, pageId, $location, $scope) {
        var path = $location.path();
        var start = startLink(branch);
        var link = null;
        if (pageId) {
            link = start + "/create/" + encodePath(pageId);
        } else {
            // lets use the current path
            link = "#" + path.replace(/(view|edit|formTable)/, "create");
        }

        // we have the link so lets now remove the last path
        // or if there is no / in the path then remove the last section
        var idx = link.lastIndexOf("/");
        if (idx > 0 && !$scope.children && !path.startsWith("/wiki/formTable")) {
            link = link.substring(0, idx + 1);
        }
        return link;
    }
    Wiki.createLink = createLink;

    function encodePath(pageId) {
        return pageId.split("/").map(encodeURIComponent).join("/");
    }
    Wiki.encodePath = encodePath;

    function decodePath(pageId) {
        return pageId.split("/").map(decodeURIComponent).join("/");
    }
    Wiki.decodePath = decodePath;

    function fileFormat(name, fileExtensionTypeRegistry) {
        var extension = fileExtension(name);
        var answer = null;
        angular.forEach(fileExtensionTypeRegistry, function (array, key) {
            if (array.indexOf(extension) >= 0) {
                answer = key;
            }
        });
        return answer;
    }
    Wiki.fileFormat = fileFormat;

    /**
    * Returns the file name of the given path; stripping off any directories
    * @method fileName
    * @for Wiki
    * @static
    * @param {String} path
    * @return {String}
    */
    function fileName(path) {
        if (path) {
            var idx = path.lastIndexOf("/");
            if (idx > 0) {
                return path.substring(idx + 1);
            }
        }
        return path;
    }
    Wiki.fileName = fileName;

    /**
    * Returns the folder of the given path (everything but the last path name)
    * @method fileParent
    * @for Wiki
    * @static
    * @param {String} path
    * @return {String}
    */
    function fileParent(path) {
        if (path) {
            var idx = path.lastIndexOf("/");
            if (idx > 0) {
                return path.substring(0, idx);
            }
        }

        // lets return the root directory
        return "";
    }
    Wiki.fileParent = fileParent;

    /**
    * Returns the file name for the given name; we hide some extensions
    * @method hideFineNameExtensions
    * @for Wiki
    * @static
    * @param {String} name
    * @return {String}
    */
    function hideFineNameExtensions(name) {
        if (name) {
            angular.forEach(Wiki.hideExtentions, function (extension) {
                if (name.endsWith(extension)) {
                    name = name.substring(0, name.length - extension.length);
                }
            });
        }
        return name;
    }
    Wiki.hideFineNameExtensions = hideFineNameExtensions;

    /**
    * Takes a row containing the entity object; or can take the entity directly.
    *
    * It then uses the name, directory and xmlNamespaces properties
    *
    * @method fileIconHtml
    * @for Wiki
    * @static
    * @param {any} row
    * @return {String}
    *
    */
    function fileIconHtml(row) {
        var name = row.name;
        var directory = row.directory;
        var xmlNamespaces = row.xmlNamespaces;
        var entity = row.entity;
        if (entity) {
            name = name || entity.name;
            directory = directory || entity.directory;
            xmlNamespaces = xmlNamespaces || entity.xmlNamespaces;
        }
        var css = null;
        var icon = null;
        var extension = fileExtension(name);

        // TODO could we use different icons for markdown v xml v html
        if (xmlNamespaces && xmlNamespaces.length) {
            if (xmlNamespaces.any(function (ns) {
                return Wiki.camelNamespaces.any(ns);
            })) {
                icon = "img/icons/camel.svg";
            } else if (xmlNamespaces.any(function (ns) {
                return Wiki.dozerNamespaces.any(ns);
            })) {
                icon = "img/icons/dozer/dozer.gif";
            } else if (xmlNamespaces.any(function (ns) {
                return Wiki.activemqNamespaces.any(ns);
            })) {
                icon = "img/icons/messagebroker.svg";
            } else {
                console.log("file " + name + " has namespaces " + xmlNamespaces);
            }
        }

        if (!icon) {
            if (directory) {
                if ("profile" === extension) {
                    css = "icon-book";
                } else {
                    css = "icon-folder-close";
                }
            } else if ("xml" === extension) {
                css = "icon-file-text";
            } else {
                css = "icon-file-alt";
            }
        }
        if (icon) {
            return "<img src='" + url(icon) + "'>";
        } else {
            return "<i class='" + css + "'></i>";
        }
    }
    Wiki.fileIconHtml = fileIconHtml;

    function iconClass(row) {
        var name = row.getProperty("name");
        var extension = fileExtension(name);
        var directory = row.getProperty("directory");
        if (directory) {
            return "icon-folder-close";
        }
        if ("xml" === extension) {
            return "icon-cog";
        }

        // TODO could we use different icons for markdown v xml v html
        return "icon-file-alt";
    }
    Wiki.iconClass = iconClass;

    /**
    * Extracts the pageId, branch, objectId from the route parameters
    * @method initScope
    * @for Wiki
    * @static
    * @param {*} $scope
    * @param {any} $routeParams
    * @param {ng.ILocationService} $location
    */
    function initScope($scope, $routeParams, $location) {
        $scope.pageId = Wiki.pageId($routeParams, $location);
        $scope.branch = $routeParams["branch"] || $location.search()["branch"];
        $scope.objectId = $routeParams["objectId"];
        $scope.startLink = Wiki.startLink($scope.branch);
        $scope.historyLink = startLink($scope.branch) + "/history/" + ($scope.pageId || "");
    }
    Wiki.initScope = initScope;

    /**
    * Loads the branches for this wiki repository and stores them in the branches property in
    * the $scope and ensures $scope.branch is set to a valid value
    *
    * @param wikiRepository
    * @param $scope
    */
    function loadBranches(wikiRepository, $scope) {
        wikiRepository.branches(function (response) {
            // lets sort by version number
            $scope.branches = response.sortBy(function (v) {
                return Core.versionToSortableString(v);
            }, true);

            // default the branch name if we have 'master'
            if (!$scope.branch && $scope.branches.find(function (branch) {
                return branch === "master";
            })) {
                $scope.branch = "master";
            }
        });
    }
    Wiki.loadBranches = loadBranches;

    /**
    * Extracts the pageId from the route parameters
    * @method pageId
    * @for Wiki
    * @static
    * @param {any} $routeParams
    * @param @ng.ILocationService @location
    * @return {String}
    */
    function pageId($routeParams, $location) {
        var pageId = $routeParams['page'];
        if (!pageId) {
            for (var i = 0; i < 100; i++) {
                var value = $routeParams['path' + i];
                if (angular.isDefined(value)) {
                    if (!pageId) {
                        pageId = value;
                    } else {
                        pageId += "/" + value;
                    }
                } else
                    break;
            }
            return pageId || "/";
        }

        // if no $routeParams variables lets figure it out from the $location
        if (!pageId) {
            pageId = pageIdFromURI($location.path());
        }
        return pageId;
    }
    Wiki.pageId = pageId;

    function pageIdFromURI(url) {
        var wikiPrefix = "/wiki/";
        if (url && url.startsWith(wikiPrefix)) {
            var idx = url.indexOf("/", wikiPrefix.length + 1);
            if (idx > 0) {
                return url.substring(idx + 1, url.length);
            }
        }
        return null;
    }
    Wiki.pageIdFromURI = pageIdFromURI;

    function fileExtension(name) {
        if (name.indexOf('#') > 0)
            name = name.substring(0, name.indexOf('#'));
        return Core.fileExtension(name, "markdown");
    }
    Wiki.fileExtension = fileExtension;

    function onComplete(status) {
        console.log("Completed operation with status: " + JSON.stringify(status));
    }
    Wiki.onComplete = onComplete;

    /**
    * Parses the given JSON text reporting to the user if there is a parse error
    * @method parseJson
    * @for Wiki
    * @static
    * @param {String} text
    * @return {any}
    */
    function parseJson(text) {
        if (text) {
            try  {
                return JSON.parse(text);
            } catch (e) {
                notification("error", "Failed to parse JSON: " + e);
            }
        }
        return null;
    }
    Wiki.parseJson = parseJson;

    /**
    * Adjusts a relative or absolute link from a wiki or file system to one using the hash bang syntax
    * @method adjustHref
    * @for Wiki
    * @static
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {String} href
    * @param {String} fileExtension
    * @return {string}
    */
    function adjustHref($scope, $location, href, fileExtension) {
        var extension = fileExtension ? "." + fileExtension : "";

        // if the last part of the path has a dot in it lets
        // exclude it as we are relative to a markdown or html file in a folder
        // such as when viewing readme.md or index.md
        var path = $location.path();
        var folderPath = path;
        var idx = path.lastIndexOf("/");
        if (idx > 0) {
            var lastName = path.substring(idx + 1);
            if (lastName.indexOf(".") >= 0) {
                folderPath = path.substring(0, idx);
            }
        }

        // Deal with relative URLs first...
        if (href.startsWith('../')) {
            var parts = href.split('/');
            var pathParts = folderPath.split('/');
            var parents = parts.filter(function (part) {
                return part === "..";
            });
            parts = parts.last(parts.length - parents.length);
            pathParts = pathParts.first(pathParts.length - parents.length);

            return '#' + pathParts.join('/') + '/' + parts.join('/') + extension + $location.hash();
        }

        // Turn an absolute link into a wiki link...
        if (href.startsWith('/')) {
            return Wiki.branchLink($scope.branch, href + extension, $location) + extension;
        }

        if (!Wiki.excludeAdjustmentPrefixes.any(function (exclude) {
            return href.startsWith(exclude);
        })) {
            return '#' + folderPath + "/" + href + extension + $location.hash();
        } else {
            return null;
        }
    }
    Wiki.adjustHref = adjustHref;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
* @main Wiki
*/
var Wiki;
(function (Wiki) {
    var pluginName = 'wiki';

    Wiki.templatePath = 'app/wiki/html/';
    Wiki.tab = null;

    angular.module(pluginName, ['bootstrap', 'ui.bootstrap.dialog', 'ui.bootstrap.tabs', 'ngResource', 'hawtioCore', 'hawtio-ui', 'tree', 'camel']).config(function ($routeProvider) {
        // allow optional branch paths...
        angular.forEach(["", "/branch/:branch"], function (path) {
            $routeProvider.when('/wiki' + path + '/view', { templateUrl: 'app/wiki/html/viewPage.html', reloadOnSearch: false }).when('/wiki' + path + '/view/*page', { templateUrl: 'app/wiki/html/viewPage.html', reloadOnSearch: false }).when('/wiki' + path + '/book/*page', { templateUrl: 'app/wiki/html/viewBook.html', reloadOnSearch: false }).when('/wiki' + path + '/create/*page', { templateUrl: 'app/wiki/html/createPage.html' }).when('/wiki' + path + '/edit/*page', { templateUrl: 'app/wiki/html/editPage.html' }).when('/wiki' + path + '/version/*page/:objectId', { templateUrl: 'app/wiki/html/viewPage.html' }).when('/wiki' + path + '/history/*page', { templateUrl: 'app/wiki/html/history.html' }).when('/wiki' + path + '/commit/*page/:objectId', { templateUrl: 'app/wiki/html/commit.html' }).when('/wiki' + path + '/diff/*page/:objectId/:baseObjectId', { templateUrl: 'app/wiki/html/viewPage.html', reloadOnSearch: false }).when('/wiki' + path + '/formTable/*page', { templateUrl: 'app/wiki/html/formTable.html' }).when('/wiki' + path + '/dozer/mappings/*page', { templateUrl: 'app/wiki/html/dozerMappings.html' }).when('/wiki' + path + '/configurations/*page', { templateUrl: 'app/wiki/html/configurations.html' }).when('/wiki' + path + '/configuration/:pid/*page', { templateUrl: 'app/wiki/html/configuration.html' }).when('/wiki' + path + '/configuration/:pid/:factoryPid/*page', { templateUrl: 'app/wiki/html/configuration.html' }).when('/wiki' + path + '/camel/diagram/*page', { templateUrl: 'app/wiki/html/camelDiagram.html' }).when('/wiki' + path + '/camel/canvas/*page', { templateUrl: 'app/wiki/html/camelCanvas.html' }).when('/wiki' + path + '/camel/properties/*page', { templateUrl: 'app/wiki/html/camelProperties.html' });
        });
    }).factory('wikiRepository', function (workspace, jolokia, localStorage) {
        return new Wiki.GitWikiRepository(function () {
            return Git.createGitRepository(workspace, jolokia, localStorage);
        });
    }).factory('fileExtensionTypeRegistry', function () {
        return {
            "markdown": ["md", "markdown", "mdown", "mkdn", "mkd"],
            "htmlmixed": ["html", "xhtml", "htm"],
            "text/x-java": ["java"],
            "text/x-scala": ["scala"],
            "javascript": ["js", "json", "javascript", "jscript", "ecmascript", "form"],
            "xml": ["xml", "xsd", "wsdl", "atom"],
            "properties": ["properties"]
        };
    }).filter('fileIconClass', function () {
        return Wiki.iconClass;
    }).directive('wikiHrefAdjuster', function ($location) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attr) {
                $element.bind('DOMNodeInserted', function (event) {
                    var ays = $element.find('a');
                    angular.forEach(ays, function (a) {
                        if (a.hasAttribute('no-adjust')) {
                            return;
                        }
                        a = $(a);
                        var href = (a.attr('href') || "").trim();
                        if (href) {
                            var fileExtension = a.attr('file-extension');
                            var newValue = Wiki.adjustHref($scope, $location, href, fileExtension);
                            if (newValue) {
                                a.attr('href', newValue);
                            }
                        }
                    });
                    var imgs = $element.find('img');
                    angular.forEach(imgs, function (a) {
                        if (a.hasAttribute('no-adjust')) {
                            return;
                        }
                        a = $(a);
                        var href = (a.attr('src') || "").trim();
                        if (href) {
                            if (href.startsWith("/")) {
                                href = url(href);
                                a.attr('src', href);

                                // lets avoid this element being reprocessed
                                a.attr('no-adjust', 'true');
                            }
                        }
                    });
                });
            }
        };
    }).directive('wikiTitleLinker', function ($location) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attr) {
                var loaded = false;

                function offsetTop(elements) {
                    if (elements) {
                        var offset = elements.offset();
                        if (offset) {
                            return offset.top;
                        }
                    }
                    return 0;
                }

                function scrollToHash() {
                    var answer = false;
                    var id = $location.search()["hash"];
                    return scrollToId(id);
                }

                function scrollToId(id) {
                    var answer = false;
                    var id = $location.search()["hash"];
                    if (id) {
                        var selector = 'a[name="' + id + '"]';
                        var targetElements = $element.find(selector);
                        if (targetElements && targetElements.length) {
                            var scrollDuration = 1;
                            var delta = offsetTop($($element));
                            var top = offsetTop(targetElements) - delta;
                            if (top < 0) {
                                top = 0;
                            }

                            //log.info("scrolling to hash: " + id + " top: " + top + " delta:" + delta);
                            $('body,html').animate({
                                scrollTop: top
                            }, scrollDuration);
                            answer = true;
                        } else {
                            //log.info("could find element for: " + selector);
                        }
                    }
                    return answer;
                }

                function addLinks(event) {
                    var headings = $element.find('h1,h2,h3,h4,h5,h6,h7');
                    var updated = false;
                    angular.forEach(headings, function (he) {
                        var h1 = $(he);

                        // now lets try find a child header
                        var a = h1.parent("a");
                        if (!a || !a.length) {
                            var text = h1.text();
                            if (text) {
                                var target = text.replace(/ /g, "-");
                                var pathWithHash = "#" + $location.path() + "?hash=" + target;
                                var link = Core.createHref($location, pathWithHash, ['hash']);

                                // lets wrap the heading in a link
                                var newA = $('<a name="' + target + '" href="' + link + '" ng-click="onLinkClick()"></a>');
                                newA.on("click", function () {
                                    setTimeout(function () {
                                        if (scrollToId(target)) {
                                        }
                                    }, 50);
                                });

                                newA.insertBefore(h1);
                                h1.detach();
                                newA.append(h1);
                                updated = true;
                            }
                        }
                    });
                    if (updated && !loaded) {
                        setTimeout(function () {
                            if (scrollToHash()) {
                                loaded = true;
                            }
                        }, 50);
                    }
                }

                function onEventInserted(event) {
                    // avoid any more events while we do our thing
                    $element.unbind('DOMNodeInserted', onEventInserted);
                    addLinks(event);
                    $element.bind('DOMNodeInserted', onEventInserted);
                }

                $element.bind('DOMNodeInserted', onEventInserted);
            }
        };
    }).run(function ($location, workspace, viewRegistry, jolokia, localStorage, layoutFull, helpRegistry, preferencesRegistry, wikiRepository, postLoginTasks, $rootScope) {
        viewRegistry['wiki'] = layoutFull;
        helpRegistry.addUserDoc('wiki', 'app/wiki/doc/help.md', function () {
            return Wiki.isWikiEnabled(workspace, jolokia, localStorage);
        });

        preferencesRegistry.addTab("Git", 'app/wiki/html/gitPreferences.html');

        Wiki.tab = {
            id: "wiki",
            content: "Wiki",
            title: "View and edit wiki pages",
            isValid: function (workspace) {
                return Wiki.isWikiEnabled(workspace, jolokia, localStorage);
            },
            href: function () {
                return "#/wiki/view";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("/wiki") && !workspace.linkContains("fabric", "profiles") && !workspace.linkContains("editFeatures");
            }
        };
        workspace.topLevelTabs.push(Wiki.tab);

        postLoginTasks.addTask('wikiGetRepositoryLabel', function () {
            wikiRepository.getRepositoryLabel(function (label) {
                Wiki.tab.content = label;
                Core.$apply($rootScope);
            }, function (response) {
                // silently ignore
            });
        });

        // add empty regexs to templates that don't define
        // them so ng-pattern doesn't barf
        Wiki.documentTemplates.forEach(function (template) {
            Wiki.log.debug("Checking template: ", template);
            if (!template['regex']) {
                Wiki.log.debug("Setting regex");
                template.regex = /(?:)/;
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function DozerMappingsController($scope, $location, $routeParams, workspace, jolokia, wikiRepository, $templateCache) {
        var log = Logger.get("Dozer");

        Wiki.initScope($scope, $routeParams, $location);
        Dozer.schemaConfigure();

        $scope.profileId = Fabric.pagePathToProfileId($scope.pageId);
        $scope.versionId = $scope.branch || "1.0";

        $scope.schema = {};
        $scope.addDialog = new UI.Dialog();
        $scope.propertiesDialog = new UI.Dialog();
        $scope.deleteDialog = false;
        $scope.unmappedFieldsHasValid = false;
        $scope.modified = false;

        $scope.selectedItems = [];
        $scope.mappings = [];
        $scope.schemas = [];

        $scope.aName = '';
        $scope.bName = '';

        $scope.connectorStyle = ["Bezier"];

        $scope.main = "";
        $scope.tab = "Mappings";

        $scope.gridOptions = {
            selectedItems: $scope.selectedItems,
            data: 'mappings',
            displayFooter: false,
            showFilter: false,
            //sortInfo: { field: 'timestamp', direction: 'DESC'},
            filterOptions: {
                filterText: "searchText"
            },
            columnDefs: [
                {
                    field: 'class_a',
                    displayName: 'From',
                    cellTemplate: '<div class="ngCellText">{{row.entity.class_a.name}}</div>'
                },
                {
                    field: 'class_b',
                    displayName: 'To',
                    cellTemplate: '<div class="ngCellText">{{row.entity.class_b.name}}</div>'
                }
            ]
        };

        if ($scope.profileId) {
            Fabric.profileJolokia(jolokia, $scope.profileId, $scope.versionId, function (containerJolokia) {
                $scope.containerJolokia = containerJolokia;
                $scope.missingContainer = !containerJolokia ? true : false;
            });
        }

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        $scope.triggerRefresh = function (timeout) {
            if (typeof timeout === "undefined") { timeout = 500; }
            $scope.main = "";
            setTimeout(function () {
                $scope.main = $templateCache.get("pageTemplate.html");
                Core.$apply($scope);
            }, timeout);
        };

        $scope.disableReload = function () {
            var aValue = Core.pathGet($scope, ["selectedMapping", "class_a", "value"]);
            var bValue = Core.pathGet($scope, ["selectedMapping", "class_b", "value"]);
            return aValue === $scope.aName && bValue === $scope.bName;
        };

        $scope.doReload = function () {
            $scope.selectedMapping.class_a.value = $scope.aName;
            $scope.selectedMapping.class_b.value = $scope.bName;
            $scope.triggerRefresh();
        };

        $scope.$watch('selectedMapping', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.aName = newValue.class_a.value;
                $scope.bName = newValue.class_b.value;
                $scope.triggerRefresh();
            }
        });

        $scope.$watch('selectedMapping.class_a.value', function (newValue, oldValue) {
            if (newValue !== oldValue && newValue !== '') {
                $scope.fetchProperties(newValue, $scope.selectedMapping.class_a, 'Right');
            }
        });

        $scope.$watch('selectedMapping.class_b.value', function (newValue, oldValue) {
            if (newValue !== oldValue && newValue !== '') {
                $scope.fetchProperties(newValue, $scope.selectedMapping.class_b, 'Left');
            }
        });

        $scope.fetchProperties = function (className, target, anchor) {
            var introspectorMBean = Dozer.getIntrospectorMBean(workspace);
            if (introspectorMBean && !$scope.missingContainer) {
                var aJolokia = $scope.containerJolokia || jolokia;
                aJolokia.request({
                    type: 'exec',
                    mbean: introspectorMBean,
                    operation: 'getProperties(java.lang.String)',
                    arguments: [className]
                }, {
                    success: function (response) {
                        target.error = null;
                        target.properties = response.value;
                        var parentId = '';
                        if (angular.isDefined(target.value)) {
                            parentId = target.value;
                        } else {
                            parentId = target.path;
                        }

                        angular.forEach(target.properties, function (property) {
                            property.id = Core.getUUID();
                            property.path = parentId + '/' + property.displayName;
                            property.anchor = anchor;
                            // TODO - Let's see if we need to do this...
                            /*
                            var lookup = !Dozer.excludedPackages.any((excluded) => { return property.typeName.has(excluded); });
                            if (lookup) {
                            $scope.fetchProperties(property.typeName, property, anchor);
                            }
                            */
                        });
                        Core.$apply($scope);
                    },
                    error: function (response) {
                        target.properties = null;
                        target.error = {
                            'type': response.error_type,
                            'stackTrace': response.error
                        };
                        log.error("got: " + response);
                        Core.$apply($scope);
                    }
                });
            }
        };

        $scope.getSourceAndTarget = function (info) {
            var sourcePath = info.source.attr('field-path');
            var targetPath = info.target.attr('field-path');

            var sourceField = sourcePath.split('/').last();
            var targetField = sourcePath.split('/').last();

            return {
                from: sourceField,
                to: targetField
            };
        };

        function extractProperty(clazz, prop) {
            return (!clazz || !clazz.properties) ? null : clazz.properties.find(function (property) {
                return property.path.endsWith('/' + prop);
            });
        }

        // The jsPlumb directive will call this after it's done it's thing...
        function addConnectionClickHandler(connection, jsplumb) {
            connection.bind('click', function (connection) {
                jsplumb.detach(connection);
            });
        }

        function getPaintStyle() {
            return {
                strokeStyle: UI.colors.sample(),
                lineWidth: 4
            };
        }

        $scope.jsPlumbCallback = function (jsplumb, nodes, nodesById, connections) {
            // Set up any connections loaded from the XML
            // TODO - currently we actually are only looking at the top-level properties
            angular.forEach($scope.selectedMapping.fields, function (field) {
                var a_property = extractProperty($scope.selectedMapping.class_a, field.a.value);
                var b_property = extractProperty($scope.selectedMapping.class_b, field.b.value);

                if (a_property && b_property) {
                    var a_node = nodesById[a_property.id];
                    var b_node = nodesById[b_property.id];

                    var connection = $scope.jsPlumb.connect({
                        source: a_node.el,
                        target: b_node.el
                    }, {
                        connector: $scope.connectorStyle,
                        maxConnections: 1,
                        paintStyle: getPaintStyle()
                    });

                    //Ensure loaded connections can also be removed
                    addConnectionClickHandler(connection, jsplumb);
                    a_node.connections.push(connection);
                    b_node.connections.push(connection);
                }
            });

            // Handle new connection events...
            jsplumb.bind('connection', function (info) {
                // Add a handler so we can click on a connection to make it go away
                addConnectionClickHandler(info.connection, jsplumb);
                info.connection.setPaintStyle(getPaintStyle());

                var newMapping = $scope.getSourceAndTarget(info);

                var field = new Dozer.Field(new Dozer.FieldDefinition(newMapping.from), new Dozer.FieldDefinition(newMapping.to));
                $scope.selectedMapping.fields.push(field);
                $scope.modified = true;
                Core.$apply($scope);
            });

            // Handle connection detach events...
            jsplumb.bind('connectionDetached', function (info) {
                var toDetach = $scope.getSourceAndTarget(info);
                var field = new Dozer.Field(new Dozer.FieldDefinition(toDetach.from), new Dozer.FieldDefinition(toDetach.to));
                $scope.selectedMapping.fields.remove(field);
                $scope.modified = true;
                Core.$apply($scope);
            });
        };

        $scope.formatStackTrace = function (exception) {
            return Log.formatStackTrace(exception);
        };

        $scope.addMapping = function () {
            var treeNode = $scope.rootTreeNode;
            if (treeNode) {
                var parentFolder = treeNode.data;
                var mapping = new Dozer.Mapping();
                var addedNode = Dozer.createMappingFolder(mapping, parentFolder);
                var added = treeNode.addChild(addedNode);
                if (added) {
                    added.expand(true);
                    added.select(true);
                    added.activate(true);
                    onTreeModified();
                }
                $scope.mappings.push(mapping);
                $scope.selectedMapping = mapping;
            }
        };

        $scope.addField = function () {
            if ($scope.selectedMapping) {
                // lets find all the possible unmapped fields we can map from...
                Dozer.findUnmappedFields(workspace, $scope.selectedMapping, function (data) {
                    log.warn("has unmapped data fields: " + data);
                    $scope.unmappedFields = data;
                    $scope.unmappedFieldsHasValid = false;
                    $scope.addDialog.open();
                    Core.$apply($scope);
                });
            }
        };

        $scope.addAndCloseDialog = function () {
            log.info("About to add the unmapped fields " + JSON.stringify($scope.unmappedFields, null, "  "));
            if ($scope.selectedMapping) {
                // TODO whats the folder???
                angular.forEach($scope.unmappedFields, function (unmappedField) {
                    if (unmappedField.valid) {
                        // TODO detect exclude!
                        var field = new Dozer.Field(new Dozer.FieldDefinition(unmappedField.fromField), new Dozer.FieldDefinition(unmappedField.toField));
                        $scope.selectedMapping.fields.push(field);
                        var treeNode = $scope.selectedMappingTreeNode;
                        var mappingFolder = $scope.selectedMappingFolder;
                        if (treeNode && mappingFolder) {
                            var fieldFolder = Dozer.addMappingFieldFolder(field, mappingFolder);
                            var added = treeNode.addChild(fieldFolder);
                            if (added) {
                                added.expand(true);
                                added.select(true);
                                added.activate(true);
                                onTreeModified();
                            }
                        } else {
                            log.warn("No treenode and folder for mapping node! treeNode " + treeNode + " mappingFolder " + mappingFolder);
                        }
                    }
                });
            }
            $scope.addDialog.close();
        };

        $scope.canDelete = function () {
            return $scope.selectedFolder ? true : false;
        };

        $scope.removeNode = function () {
            if ($scope.selectedFolder && $scope.treeNode) {
                // TODO deal with deleting fields
                var folder = $scope.selectedFolder;
                var entity = folder.entity;
                if (entity instanceof Dozer.Field) {
                    // lets remove this from the parent mapping
                    var mapping = Core.pathGet(folder, ["parent", "entity"]);
                    if (mapping) {
                        mapping.fields.remove(entity);
                    }
                }
                $scope.selectedFolder.detach();
                $scope.treeNode.remove();
                $scope.selectedFolder = null;
                $scope.treeNode = null;
                onTreeModified();
            }
        };

        $scope.saveMappings = function () {
            $scope.model.mappings = $scope.mappings;
            var text = Dozer.saveToXmlText($scope.model);
            if (text) {
                var commitMessage = $scope.commitMessage || "Updated page " + $scope.pageId;
                wikiRepository.putPage($scope.branch, $scope.pageId, text, commitMessage, function (status) {
                    Wiki.onComplete(status);
                    $scope.modified = false;
                    notification("success", "Saved " + $scope.pageId);
                    goToView();
                    Core.$apply($scope);
                });
            }
        };

        $scope.save = function () {
            if ($scope.tab === "Mappings") {
                $scope.saveMappings();
                return;
            }
            if ($scope.model) {
                // lets copy the mappings from the tree
                var model = Dozer.loadModelFromTree($scope.rootTreeNode, $scope.model);
                var text = Dozer.saveToXmlText(model);
                if (text) {
                    var commitMessage = $scope.commitMessage || "Updated page " + $scope.pageId;
                    wikiRepository.putPage($scope.branch, $scope.pageId, text, commitMessage, function (status) {
                        Wiki.onComplete(status);
                        $scope.modified = false;
                        notification("success", "Saved " + $scope.pageId);
                        goToView();
                        Core.$apply($scope);
                    });
                }
            }
        };

        $scope.cancel = function () {
            log.info("cancelling...");
            // TODO show dialog if folks are about to lose changes...
        };

        $scope.onRootTreeNode = function (rootTreeNode) {
            $scope.rootTreeNode = rootTreeNode;
        };

        $scope.onNodeSelect = function (folder, treeNode) {
            $scope.selectedFolder = folder;
            $scope.treeNode = treeNode;
            $scope.propertiesTemplate = null;
            $scope.dozerEntity = null;
            $scope.selectedDescription = "";
            $scope.selectedMapping = null;
            $scope.selectedMappingTreeNode = null;
            $scope.selectedMappingFolder = null;

            // now the model is bound, lets add a listener
            if ($scope.removeModelChangeListener) {
                $scope.removeModelChangeListener();
                $scope.removeModelChangeListener = null;
            }

            if (folder) {
                var entity = folder.entity;
                $scope.dozerEntity = entity;
                var propertiesTemplate = "app/wiki/html/dozerPropertiesEdit.html";
                if (entity instanceof Dozer.Field) {
                    //var field: Dozer.Field = entity;
                    $scope.propertiesTemplate = propertiesTemplate;
                    $scope.nodeModel = io_hawt_dozer_schema_Field;
                    $scope.selectedDescription = "Field Mapping";
                    $scope.selectedMapping = Core.pathGet(folder, ["parent", "entity"]);
                    $scope.selectedMappingFolder = folder.parent;
                    $scope.selectedMappingTreeNode = treeNode.parent;
                } else if (entity instanceof Dozer.Mapping) {
                    //var mapping: Dozer.Mapping = entity;
                    $scope.propertiesTemplate = propertiesTemplate;
                    $scope.nodeModel = io_hawt_dozer_schema_Mapping;
                    $scope.selectedDescription = "Class Mapping";
                    $scope.selectedMapping = entity;
                    $scope.selectedMappingFolder = folder;
                    $scope.selectedMappingTreeNode = treeNode;
                }
                if ($scope.selectedMapping && !$scope.removeModelChangeListener) {
                    /*
                    TODO problem is we have many forms here so we end up creating lots of change events when really we don't change things!: )
                    // maybe watch the entity instead?
                    
                    console.log("Adding onTreeModified form listener");
                    $scope.removeModelChangeListener = $scope.$on("hawtio.form.modelChange", () => {
                    console.log("form modified!");
                    onTreeModified();
                    });
                    */
                }
            }

            Core.$apply($scope);
        };

        $scope.onUnmappedFieldChange = function (unmappedField) {
            unmappedField.valid = unmappedField.toField ? true : false;
            $scope.unmappedFieldsHasValid = $scope.unmappedFields.find(function (f) {
                return f.valid;
            });
        };

        function findFieldNames(className, text) {
            //console.log("Finding the to field names for expression '" + text + "'  on class " + className);
            var properties = Dozer.findProperties(workspace, className, text, null);
            return properties.map(function (p) {
                return p.name;
            });
        }

        $scope.fromFieldNames = function (text) {
            var className = Core.pathGet($scope.selectedMapping, ["class_a", "value"]);
            return findFieldNames(className, text);
        };

        $scope.toFieldNames = function (text) {
            var className = Core.pathGet($scope.selectedMapping, ["class_b", "value"]);
            return findFieldNames(className, text);
        };

        $scope.classNames = function (text) {
            // lets only query if the size is reasonable
            if (!text || text.length < 2)
                return [];
            return Core.time("Time the query of classes", function () {
                log.info("searching for class names with filter '" + text + "'");
                var answer = Dozer.findClassNames(workspace, text);
                log.info("Found results: " + answer.length);
                return answer;
            });
        };

        updateView();

        function updateView() {
            $scope.pageId = Wiki.pageId($routeParams, $location);
            if (Git.getGitMBean(workspace)) {
                $scope.git = wikiRepository.getPage($scope.branch, $scope.pageId, $scope.objectId, onResults);
            }
        }

        function onResults(response) {
            var text = response.text;
            if (text) {
                if ($scope.responseText !== text) {
                    $scope.responseText = text;

                    // lets remove any dodgy characters so we can use it as a DOM id
                    $scope.model = Dozer.loadDozerModel(text, $scope.pageId);

                    $scope.mappings = Core.pathGet($scope.model, ["mappings"]);

                    $scope.mappingTree = Dozer.createDozerTree($scope.model);
                    if (!angular.isDefined($scope.selectedMapping)) {
                        $scope.selectedMapping = $scope.mappings.first();
                    }

                    $scope.main = $templateCache.get("pageTemplate.html");
                }
            } else {
                log.warn("No XML found for page " + $scope.pageId);
            }
            Core.$apply($scope);
        }

        function onTreeModified() {
            $scope.modified = true;
        }

        function goToView() {
            // TODO lets navigate to the view if we have a separate view one day :)
            /*
            if ($scope.breadcrumbs && $scope.breadcrumbs.length > 1) {
            var viewLink = $scope.breadcrumbs[$scope.breadcrumbs.length - 2];
            console.log("goToView has found view " + viewLink);
            var path = Core.trimLeading(viewLink, "#");
            $location.path(path);
            } else {
            console.log("goToView has no breadcrumbs!");
            }
            */
        }
    }
    Wiki.DozerMappingsController = DozerMappingsController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function CommitController($scope, $location, $routeParams, $templateCache, workspace, marked, fileExtensionTypeRegistry, wikiRepository) {
        Wiki.initScope($scope, $routeParams, $location);
        $scope.commitId = $scope.objectId;
        $scope.selectedItems = [];

        // TODO we could configure this?
        $scope.dateFormat = 'EEE, MMM d, y : hh:mm:ss a';

        $scope.gridOptions = {
            data: 'commits',
            showFilter: false,
            multiSelect: false,
            selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            displaySelectionCheckbox: true,
            selectedItems: $scope.selectedItems,
            filterOptions: {
                filterText: ''
            },
            columnDefs: [
                {
                    field: 'path',
                    displayName: 'File Name',
                    cellTemplate: $templateCache.get('fileCellTemplate.html'),
                    width: "***",
                    cellFilter: ""
                }
            ]
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        $scope.$watch('workspace.tree', function () {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(updateView, 50);
            }
        });

        $scope.canRevert = function () {
            return $scope.selectedItems.length === 1;
        };

        $scope.revert = function () {
            if ($scope.selectedItems.length > 0) {
                var path = commitPath($scope.selectedItems[0]);
                var objectId = $scope.commitId;
                if (path && objectId) {
                    var commitMessage = "Reverting file " + $scope.pageId + " to previous version " + objectId;
                    wikiRepository.revertTo($scope.branch, objectId, $scope.pageId, commitMessage, function (result) {
                        Wiki.onComplete(result);

                        // now lets update the view
                        updateView();
                    });
                }
            }
        };

        function commitPath(commit) {
            return commit.path || commit.name;
        }

        $scope.diff = function () {
            if ($scope.selectedItems.length > 0) {
                var commit = $scope.selectedItems[0];

                /*
                var commit = row;
                var entity = row.entity;
                if (entity) {
                commit = entity;
                }
                */
                var link = Wiki.startLink($scope.branch) + "/diff/" + commitPath(commit) + "/" + $scope.commitId + "/";
                var path = Core.trimLeading(link, "#");
                $location.path(path);
            }
        };

        updateView();

        function updateView() {
            var commitId = $scope.commitId;

            Wiki.loadBranches(wikiRepository, $scope);

            wikiRepository.commitInfo(commitId, function (commitInfo) {
                $scope.commitInfo = commitInfo;
                Core.$apply($scope);
            });

            wikiRepository.commitTree(commitId, function (commits) {
                $scope.commits = commits;
                angular.forEach(commits, function (commit) {
                    commit.fileIconHtml = Wiki.fileIconHtml(commit);
                    commit.fileClass = commit.name.endsWith(".profile") ? "green" : "";
                    var changeType = commit.changeType;
                    var path = commitPath(commit);
                    if (path) {
                        commit.fileLink = Wiki.startLink($scope.branch) + '/version/' + path + '/' + commitId;
                    }
                    if (changeType) {
                        changeType = changeType.toLowerCase();
                        if (changeType.startsWith("a")) {
                            commit.changeClass = "change-add";
                            commit.change = "add";
                            commit.title = "added";
                        } else if (changeType.startsWith("d")) {
                            commit.changeClass = "change-delete";
                            commit.change = "delete";
                            commit.title = "deleted";
                            commit.fileLink = null;
                        } else {
                            commit.changeClass = "change-modify";
                            commit.change = "modify";
                            commit.title = "modified";
                        }
                        commit.changeTypeHtml = '<span class="' + commit.changeClass + '">' + commit.title + '</span>';
                    }
                });
                Core.$apply($scope);
            });
        }
    }
    Wiki.CommitController = CommitController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function FormTableController($scope, $location, $routeParams, workspace, wikiRepository) {
        Wiki.initScope($scope, $routeParams, $location);
        $scope.columnDefs = [];

        $scope.gridOptions = {
            data: 'list',
            displayFooter: false,
            showFilter: false,
            filterOptions: {
                filterText: ''
            },
            columnDefs: $scope.columnDefs
        };

        $scope.viewLink = function (row) {
            return childLink(row, "/view");
        };
        $scope.editLink = function (row) {
            return childLink(row, "/edit");
        };

        function childLink(child, prefix) {
            var start = Wiki.startLink($scope.branch);
            var childId = (child) ? child["_id"] || "" : "";
            return Core.createHref($location, start + prefix + "/" + $scope.pageId + "/" + childId);
        }

        var linksColumn = {
            field: '_id',
            displayName: 'Actions',
            cellTemplate: '<div class="ngCellText""><a ng-href="{{viewLink(row.entity)}}" class="btn">View</a> <a ng-href="{{editLink(row.entity)}}" class="btn">Edit</a></div>'
        };

        $scope.$watch('workspace.tree', function () {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(updateView, 50);
            }
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        var form = $location.search()["form"];
        if (form) {
            wikiRepository.getPage($scope.branch, form, $scope.objectId, onFormData);
        }

        updateView();

        function onResults(response) {
            var list = [];
            var map = Wiki.parseJson(response);
            angular.forEach(map, function (value, key) {
                value["_id"] = key;
                list.push(value);
            });
            $scope.list = list;
            Core.$apply($scope);
        }

        function updateView() {
            var filter = Core.pathGet($scope, ["gridOptions", "filterOptions", "filterText"]) || "";
            $scope.git = wikiRepository.jsonChildContents($scope.pageId, "*.json", filter, onResults);
        }

        function onFormData(details) {
            var text = details.text;
            if (text) {
                $scope.formDefinition = Wiki.parseJson(text);

                var columnDefs = [];
                var schema = $scope.formDefinition;
                angular.forEach(schema.properties, function (property, name) {
                    if (name) {
                        if (!Forms.isArrayOrNestedObject(property, schema)) {
                            var colDef = {
                                field: name,
                                displayName: property.description || name,
                                visible: true
                            };
                            columnDefs.push(colDef);
                        }
                    }
                });
                columnDefs.push(linksColumn);

                $scope.columnDefs = columnDefs;
                $scope.gridOptions.columnDefs = columnDefs;

                // now we have the grid column stuff loaded, lets load the datatable
                $scope.tableView = "app/wiki/html/formTableDatatable.html";
            }
        }
        Core.$apply($scope);
    }
    Wiki.FormTableController = FormTableController;
})(Wiki || (Wiki = {}));
/**
* @module Wiki
*/
var Wiki;
(function (Wiki) {
    function CamelController($scope, $location, $routeParams, localStorage, workspace, wikiRepository, jolokia) {
        Wiki.initScope($scope, $routeParams, $location);
        Camel.initEndpointChooserScope($scope, $location, localStorage, workspace, jolokia);
        $scope.schema = Camel.getConfiguredCamelModel();
        $scope.modified = false;

        $scope.findProfileCamelContext = true;
        $scope.camelSelectionDetails = {
            selectedCamelContextId: null,
            selectedRouteId: null
        };

        $scope.isValid = function (nav) {
            return nav && nav.isValid(workspace);
        };

        $scope.camelSubLevelTabs = [
            {
                content: '<i class="icon-picture"></i> Canvas',
                title: "Edit the diagram in a draggy droppy way",
                isValid: function (workspace) {
                    return true;
                },
                href: function () {
                    return Wiki.startLink($scope.branch) + "/camel/canvas/" + $scope.pageId;
                }
            },
            {
                content: '<i class=" icon-sitemap"></i> Tree',
                title: "View the routes as a tree",
                isValid: function (workspace) {
                    return true;
                },
                href: function () {
                    return Wiki.startLink($scope.branch) + "/camel/properties/" + $scope.pageId;
                }
            }
        ];

        var routeModel = _apacheCamelModel.definitions.route;
        routeModel["_id"] = "route";

        $scope.addDialog = new UI.Dialog();

        // TODO doesn't seem that angular-ui uses these?
        $scope.addDialog.options["dialogClass"] = "modal-large";
        $scope.addDialog.options["cssClass"] = "modal-large";

        $scope.paletteItemSearch = "";
        $scope.paletteTree = new Folder("Palette");
        $scope.paletteActivations = ["Routing_aggregate"];

        // load $scope.paletteTree
        angular.forEach(_apacheCamelModel.definitions, function (value, key) {
            if (value.group) {
                var group = (key === "route") ? $scope.paletteTree : $scope.paletteTree.getOrElse(value.group);
                if (!group.key) {
                    group.key = value.group;
                }
                value["_id"] = key;
                var title = value["title"] || key;
                var node = new Folder(title);
                node.key = group.key + "_" + key;
                node["nodeModel"] = value;
                var imageUrl = Camel.getRouteNodeIcon(value);
                node.icon = imageUrl;

                // compiler was complaining about 'label' had no idea where it's coming from
                // var tooltip = value["tooltip"] || value["description"] || label;
                var tooltip = value["tooltip"] || value["description"] || '';
                node.tooltip = tooltip;

                group.children.push(node);
            }
        });

        // load $scope.componentTree
        $scope.componentTree = new Folder("Endpoints");

        $scope.$watch("componentNames", function () {
            var componentNames = $scope.componentNames;
            if (componentNames && componentNames.length) {
                $scope.componentTree = new Folder("Endpoints");
                angular.forEach($scope.componentNames, function (endpointName) {
                    var category = Camel.getEndpointCategory(endpointName);
                    var groupName = category.label || "Core";
                    var groupKey = category.id || groupName;
                    var group = $scope.componentTree.getOrElse(groupName);

                    var value = Camel.getEndpointConfig(endpointName, category);
                    var key = endpointName;
                    var label = value["label"] || endpointName;
                    var node = new Folder(label);
                    node.key = groupKey + "_" + key;
                    node.key = key;
                    node["nodeModel"] = value;
                    var tooltip = value["tooltip"] || value["description"] || label;
                    var imageUrl = url(value["icon"] || Camel.endpointIcon);
                    node.icon = imageUrl;
                    node.tooltip = tooltip;

                    group.children.push(node);
                });
            }
        });
        $scope.componentActivations = ["bean"];

        $scope.$watch('addDialog.show', function () {
            if ($scope.addDialog.show) {
                setTimeout(function () {
                    $('#submit').focus();
                }, 50);
            }
        });

        $scope.$on("hawtio.form.modelChange", onModelChangeEvent);

        $scope.onRootTreeNode = function (rootTreeNode) {
            $scope.rootTreeNode = rootTreeNode;

            // restore the real data at the root for saving the doc etc
            rootTreeNode.data = $scope.camelContextTree;
        };

        $scope.addNode = function () {
            if ($scope.nodeXmlNode) {
                $scope.addDialog.open();
            } else {
                addNewNode(routeModel);
            }
        };

        $scope.onPaletteSelect = function (node) {
            $scope.selectedPaletteNode = (node && node["nodeModel"]) ? node : null;
            if ($scope.selectedPaletteNode) {
                $scope.selectedComponentNode = null;
            }
            console.log("Selected " + $scope.selectedPaletteNode + " : " + $scope.selectedComponentNode);
        };

        $scope.onComponentSelect = function (node) {
            $scope.selectedComponentNode = (node && node["nodeModel"]) ? node : null;
            if ($scope.selectedComponentNode) {
                $scope.selectedPaletteNode = null;
                var nodeName = node.key;
                console.log("loading endpoint schema for node " + nodeName);
                $scope.loadEndpointSchema(nodeName);
                $scope.selectedComponentName = nodeName;
            }
            console.log("Selected " + $scope.selectedPaletteNode + " : " + $scope.selectedComponentNode);
        };

        $scope.selectedNodeModel = function () {
            var nodeModel = null;
            if ($scope.selectedPaletteNode) {
                nodeModel = $scope.selectedPaletteNode["nodeModel"];
                $scope.endpointConfig = null;
            } else if ($scope.selectedComponentNode) {
                // TODO lest create an endpoint nodeModel and associate
                // the dummy URL and properties etc...
                var endpointConfig = $scope.selectedComponentNode["nodeModel"];
                var endpointSchema = $scope.endpointSchema;
                nodeModel = $scope.schema.definitions.endpoint;
                $scope.endpointConfig = {
                    key: $scope.selectedComponentNode.key,
                    schema: endpointSchema,
                    details: endpointConfig
                };
            }
            return nodeModel;
        };

        $scope.addAndCloseDialog = function () {
            var nodeModel = $scope.selectedNodeModel();
            if (nodeModel) {
                addNewNode(nodeModel);
            } else {
                console.log("WARNING: no nodeModel!");
            }
            $scope.addDialog.close();
        };

        $scope.removeNode = function () {
            if ($scope.selectedFolder && $scope.treeNode) {
                $scope.selectedFolder.detach();
                $scope.treeNode.remove();
                $scope.selectedFolder = null;
                $scope.treeNode = null;
            }
        };

        $scope.canDelete = function () {
            return $scope.selectedFolder ? true : false;
        };

        $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.save = function () {
            // generate the new XML
            if ($scope.rootTreeNode) {
                var xmlNode = Camel.generateXmlFromFolder($scope.rootTreeNode);
                if (xmlNode) {
                    var text = Core.xmlNodeToString(xmlNode);
                    if (text) {
                        // lets save the file...
                        var commitMessage = $scope.commitMessage || "Updated page " + $scope.pageId;
                        wikiRepository.putPage($scope.branch, $scope.pageId, text, commitMessage, function (status) {
                            Wiki.onComplete(status);
                            notification("success", "Saved " + $scope.pageId);
                            $scope.modified = false;
                            goToView();
                            Core.$apply($scope);
                        });
                    }
                }
            }
        };

        $scope.cancel = function () {
            console.log("cancelling...");
            // TODO show dialog if folks are about to lose changes...
        };

        $scope.$watch('workspace.tree', function () {
            if (!$scope.git) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(updateView, 50);
            }
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        function getFolderXmlNode(treeNode) {
            var routeXmlNode = Camel.createFolderXmlTree(treeNode, null);
            if (routeXmlNode) {
                $scope.nodeXmlNode = routeXmlNode;
            }
            return routeXmlNode;
        }

        $scope.onNodeSelect = function (folder, treeNode) {
            $scope.selectedFolder = folder;
            $scope.treeNode = treeNode;
            $scope.propertiesTemplate = null;
            $scope.diagramTemplate = null;
            $scope.nodeXmlNode = null;
            if (folder) {
                $scope.nodeData = Camel.getRouteFolderJSON(folder);
                $scope.nodeDataChangedFields = {};
            }
            var nodeName = Camel.getFolderCamelNodeId(folder);

            // lets lazily create the XML tree so it can be used by the diagram
            var routeXmlNode = getFolderXmlNode(treeNode);
            if (nodeName) {
                //var nodeName = routeXmlNode.localName;
                $scope.nodeModel = Camel.getCamelSchema(nodeName);
                if ($scope.nodeModel) {
                    $scope.propertiesTemplate = "app/wiki/html/camelPropertiesEdit.html";
                }
                $scope.diagramTemplate = "app/camel/html/routes.html";
                Core.$apply($scope);
            }
        };

        $scope.onNodeDragEnter = function (node, sourceNode) {
            var nodeFolder = node.data;
            var sourceFolder = sourceNode.data;
            if (nodeFolder && sourceFolder) {
                var nodeId = Camel.getFolderCamelNodeId(nodeFolder);
                var sourceId = Camel.getFolderCamelNodeId(sourceFolder);
                if (nodeId && sourceId) {
                    // we can only drag routes onto other routes (before / after / over)
                    if (sourceId === "route") {
                        return nodeId === "route";
                    }
                    return true;
                }
            }
            return false;
        };

        $scope.onNodeDrop = function (node, sourceNode, hitMode, ui, draggable) {
            var nodeFolder = node.data;
            var sourceFolder = sourceNode.data;
            if (nodeFolder && sourceFolder) {
                // we cannot drop a route into a route or a non-route to a top level!
                var nodeId = Camel.getFolderCamelNodeId(nodeFolder);
                var sourceId = Camel.getFolderCamelNodeId(sourceFolder);

                if (nodeId === "route") {
                    // hitMode must be "over" if we are not another route
                    if (sourceId === "route") {
                        if (hitMode === "over") {
                            hitMode = "after";
                        }
                    } else {
                        // disable before / after
                        hitMode = "over";
                    }
                } else {
                    if (Camel.acceptOutput(nodeId)) {
                        hitMode = "over";
                    } else {
                        if (hitMode !== "before") {
                            hitMode = "after";
                        }
                    }
                }
                console.log("nodeDrop nodeId: " + nodeId + " sourceId: " + sourceId + " hitMode: " + hitMode);

                sourceNode.move(node, hitMode);
            }
        };

        updateView();

        function addNewNode(nodeModel) {
            var doc = $scope.doc || document;
            var parentFolder = $scope.selectedFolder || $scope.camelContextTree;
            var key = nodeModel["_id"];
            var beforeNode = null;
            if (!key) {
                console.log("WARNING: no id for model " + JSON.stringify(nodeModel));
            } else {
                var treeNode = $scope.treeNode;
                if (key === "route") {
                    // lets add to the root of the tree
                    treeNode = $scope.rootTreeNode;
                } else {
                    if (!treeNode) {
                        // lets select the last route - and create a new route if need be
                        var root = $scope.rootTreeNode;
                        var children = root.getChildren();
                        if (!children || !children.length) {
                            addNewNode(Camel.getCamelSchema("route"));
                            children = root.getChildren();
                        }
                        if (children && children.length) {
                            treeNode = children[children.length - 1];
                        } else {
                            console.log("Could not add a new route to the empty tree!");
                            return;
                        }
                    }

                    // if the parent folder likes to act as a pipeline, then add
                    // after the parent, rather than as a child
                    var parentId = Camel.getFolderCamelNodeId(treeNode.data);
                    if (!Camel.acceptOutput(parentId)) {
                        // lets add the new node to the end of the parent
                        beforeNode = treeNode.getNextSibling();
                        treeNode = treeNode.getParent() || treeNode;
                    }
                }
                if (treeNode) {
                    var node = doc.createElement(key);
                    parentFolder = treeNode.data;
                    var addedNode = Camel.addRouteChild(parentFolder, node);
                    if (addedNode) {
                        var added = treeNode.addChild(addedNode, beforeNode);
                        if (added) {
                            getFolderXmlNode(added);
                            added.expand(true);
                            added.select(true);
                            added.activate(true);
                        }
                    }
                }
            }
        }

        function onModelChangeEvent(event, name) {
            // lets filter out events due to the node changing causing the
            // forms to be recreated
            if ($scope.nodeData) {
                var fieldMap = $scope.nodeDataChangedFields;
                if (fieldMap) {
                    if (fieldMap[name]) {
                        onNodeDataChanged();
                    } else {
                        // the selection has just changed so we get the initial event
                        // we can ignore this :)
                        fieldMap[name] = true;
                    }
                }
            }
        }

        function onNodeDataChanged() {
            $scope.modified = true;
            var selectedFolder = $scope.selectedFolder;
            if ($scope.treeNode && selectedFolder) {
                var routeXmlNode = getFolderXmlNode($scope.treeNode);
                if (routeXmlNode) {
                    var nodeName = routeXmlNode.localName;
                    var nodeSettings = Camel.getCamelSchema(nodeName);
                    if (nodeSettings) {
                        // update the title and tooltip etc
                        Camel.updateRouteNodeLabelAndTooltip(selectedFolder, routeXmlNode, nodeSettings);
                        $scope.treeNode.render(false, false);
                    }
                }

                // TODO not sure we need this to be honest
                selectedFolder["camelNodeData"] = $scope.nodeData;
            }
        }

        function onResults(response) {
            var text = response.text;
            if (text) {
                // lets remove any dodgy characters so we can use it as a DOM id
                var tree = Camel.loadCamelTree(text, $scope.pageId);
                if (tree) {
                    $scope.camelContextTree = tree;
                }
            } else {
                console.log("No XML found for page " + $scope.pageId);
            }
            Core.$applyLater($scope);
        }

        function updateView() {
            $scope.loadEndpointNames();
            $scope.pageId = Wiki.pageId($routeParams, $location);
            console.log("Has page id: " + $scope.pageId + " with $routeParams " + JSON.stringify($routeParams));

            if (Git.getGitMBean(workspace)) {
                $scope.git = wikiRepository.getPage($scope.branch, $scope.pageId, $scope.objectId, onResults);
            }
        }

        function goToView() {
            // TODO lets navigate to the view if we have a separate view one day :)
            /*
            if ($scope.breadcrumbs && $scope.breadcrumbs.length > 1) {
            var viewLink = $scope.breadcrumbs[$scope.breadcrumbs.length - 2];
            console.log("goToView has found view " + viewLink);
            var path = Core.trimLeading(viewLink, "#");
            $location.path(path);
            } else {
            console.log("goToView has no breadcrumbs!");
            }
            */
        }
    }
    Wiki.CamelController = CamelController;
})(Wiki || (Wiki = {}));
var JUnit;
(function (JUnit) {
    function TreeController($scope, $location, workspace, jolokia, inProgressStatus) {
        var log = Logger.get("JUnit");

        $scope.inProgressData = null;
        $scope.alertClass = "success";

        $scope.testClasses = [];
        $scope.testClassMap = {};

        $scope.gridOptions = {
            selectedItems: [],
            data: 'selectedTests',
            displayFooter: false,
            showFilter: false,
            filterOptions: {
                filterText: ''
            },
            //selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Test Class'
                }
            ]
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        $scope.$on('jmxTreeUpdated', function () {
            reloadTree();
        });

        $scope.runAllTests = function () {
            runTests($scope.testClasses);
        };

        $scope.runTests = function () {
            var tests = ($scope.gridOptions.selectedItems || []).map(function (o) {
                return o.id;
            });
            runTests(tests);
        };

        $scope.runTest = function (className) {
            if (className) {
                runTests([className]);
            }
        };

        $scope.clearResults = function () {
            $scope.testResults = null;
            $scope.alertClass = "success";
            inProgressStatus.data = null;
            inProgressStatus.result = null;
            inProgressStatus.alertClass = "success";
        };

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURL($location, $("#junittree"), true);
        }

        reloadTree();

        function selectionChanged(data) {
            var selectionKey = data ? data.key : null;
            log.debug("Selection is now: " + selectionKey);
            var selectedTests = $scope.testClasses;
            $scope.selectionKey = selectionKey;
            if (selectionKey) {
                selectedTests = $scope.testClassMap[selectionKey] || [selectionKey];
            }
            $scope.selectedTests = selectedTests.map(function (t) {
                return { id: t };
            });
            Core.$apply($scope);
        }

        function reloadTree() {
            var mbean = JUnit.getIntrospectorMBean(workspace);
            var domain = "org.unit";
            var rootFolder = new Folder("Test Cases");
            rootFolder.addClass = "testCases";
            rootFolder.typeName = "testCases";
            rootFolder.domain = domain;
            rootFolder.key = "";
            var children = [rootFolder];

            if (mbean) {
                function render(results) {
                    $scope.testClasses = results;
                    $scope.testClassMap = {};

                    angular.forEach(results, function (className) {
                        var paths = className.split(".");
                        var last = paths.length - 1;
                        var folder = rootFolder;
                        var prefix = "";
                        for (var i = 0; i < last; i++) {
                            var path = paths[i];
                            if (prefix) {
                                prefix += ".";
                            }
                            prefix += path;
                            var list = $scope.testClassMap[prefix];
                            if (!list) {
                                list = [];
                                $scope.testClassMap[prefix] = list;
                            }
                            list.push(className);
                            folder = workspace.folderGetOrElse(folder, path);
                            folder.key = prefix;
                        }
                        var lastPath = paths[last];

                        // lets add the test case...
                        var testClass = new Folder(lastPath);
                        testClass.addClass = "testClass";
                        testClass.domain = domain;
                        testClass.key = className;
                        folder.children.push(testClass);
                    });

                    Core.$apply($scope);

                    var treeElement = $("#junittree");
                    Jmx.enableTree($scope, $location, workspace, treeElement, children, true, function (selectedNode) {
                        var data = selectedNode.data;

                        //$scope.select(data);
                        //workspace.updateSelectionNode(data);
                        selectionChanged(data);
                        Core.$apply($scope);
                    });

                    // lets do this asynchronously to avoid Error: $digest already in progress
                    setTimeout(updateSelectionFromURL, 50);
                }

                jolokia.execute(mbean, "findJUnitTestClassNames", onSuccess(render));
            }
        }

        $scope.runningTests = function () {
            if (inProgressStatus.data !== null && inProgressStatus.data.running) {
                // in case we navigate back, then make sure the scope has the last up to date result to use
                $scope.inProgressData = inProgressStatus.data;
                $scope.alertClass = inProgressStatus.alertClass;
                return true;
            } else {
                return false;
            }
        };

        $scope.hasTestResults = function () {
            if (inProgressStatus.result !== null) {
                // in case we navigate back, then make sure the scope has the last up to date result to use
                $scope.testResults = inProgressStatus.result;
                $scope.alertClass = inProgressStatus.alertClass;
                return true;
            } else {
                return false;
            }
        };

        var renderInProgress = function (response) {
            var result = response.value;
            if (result) {
                log.info("Render inProgress: " + result);

                inProgressStatus.data = result;
                $scope.inProgressData = inProgressStatus.data;

                var alertClass = "success";
                if (result.failureCount > 0) {
                    alertClass = "error";
                }
                inProgressStatus.alertClass = alertClass;
                $scope.alertClass = inProgressStatus.alertClass;

                // if we no longer are running then clear handle
                if (!result.running && inProgressStatus.jhandle !== null) {
                    log.info("Unit test done, unreigster jolokia handle");
                    jolokia.unregister(inProgressStatus.jhandle);
                    inProgressStatus.jhandle = null;
                }

                Core.$apply($scope);
            }
        };

        var renderResults = function (result) {
            if (result) {
                log.info("Render results: " + result);

                inProgressStatus.data = null;
                inProgressStatus.alertClass = null;
                inProgressStatus.result = result;

                var alertClass = "success";
                var notificationClass = "success";
                var message = "JUnit testing succeded with " + result.runCount + " runs.";
                if (result.failureCount > 0) {
                    alertClass = "error";
                    notificationClass = "warning";
                    message = "JUnit testing failed with " + result.failureCount + " failures.";
                }
                $scope.alertClass = alertClass;
                $scope.testResults = inProgressStatus.result;

                // publish notification
                notification(notificationClass, message);

                Core.$apply($scope);
            }
        };

        function runTests(listOfClassNames) {
            // reset before running new set of unit tests
            $scope.clearResults();

            var mbean = JUnit.getJUnitMBean(workspace);
            if (mbean && listOfClassNames && listOfClassNames.length) {
                // register callback for doing live update of testing progress
                if (inProgressStatus.jhandle === null) {
                    log.info("Registering jolokia handle");
                    inProgressStatus.jhandle = jolokia.register(renderInProgress, {
                        type: 'exec', mbean: mbean,
                        operation: 'inProgress()',
                        ignoreErrors: true,
                        arguments: []
                    });

                    // execute the unit tests
                    jolokia.execute(mbean, "runTestClasses", listOfClassNames, onSuccess(renderResults));
                }
            }
        }
    }
    JUnit.TreeController = TreeController;
})(JUnit || (JUnit = {}));
/**
* @module JUnit
*/
var JUnit;
(function (JUnit) {
    JUnit.log = Logger.get("JUnit");

    /**
    * Returns true if the JUnit plugin is enabled (both the hawtio insight and JUnit mbeans are available
    */
    function isJUnitPluginEnabled(workspace) {
        return getIntrospectorMBean(workspace) && getJUnitMBean(workspace);
    }
    JUnit.isJUnitPluginEnabled = isJUnitPluginEnabled;

    function getJUnitMBean(workspace) {
        return Core.getMBeanTypeObjectName(workspace, "hawtio", "JUnitFacade");
    }
    JUnit.getJUnitMBean = getJUnitMBean;

    function getIntrospectorMBean(workspace) {
        return Core.getMBeanTypeObjectName(workspace, "hawtio", "Introspector");
    }
    JUnit.getIntrospectorMBean = getIntrospectorMBean;
})(JUnit || (JUnit = {}));
/**
* @module JUnit
* @main JUnit
*/
var JUnit;
(function (JUnit) {
    var pluginName = 'junit';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'datatable', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/junit/tests', { templateUrl: 'app/junit/html/tests.html', reloadOnSearch: false });
    }).factory('inProgressStatus', function () {
        return {
            jhandle: null,
            data: null,
            result: null,
            alertClass: "success"
        };
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry['junit'] = 'app/junit/html/layoutJUnitTree.html';

        helpRegistry.addUserDoc('junit', 'app/junit/doc/help.md', function () {
            return JUnit.isJUnitPluginEnabled(workspace);
        });

        workspace.topLevelTabs.push({
            id: "junit",
            content: "JUnit",
            title: "View and run test cases in this process",
            isValid: function (workspace) {
                return JUnit.isJUnitPluginEnabled(workspace);
            },
            href: function () {
                return "#/junit/tests";
            }
        });
        /*
        workspace.subLevelTabs.push({
        content: '<i class="icon-list-alt"></i> JUnit',
        title: "View the logs in this process",
        isValid: (workspace:Workspace) => workspace.hasDomainAndProperties('org.fusesource.insight', {type: 'JUnitQuery'}),
        href: () => "#/logs"
        });
        */
    });

    hawtioPluginLoader.addModule(pluginName);
})(JUnit || (JUnit = {}));
/**
* @module Perspective
*/
var Perspective;
(function (Perspective) {
    Perspective.log = Logger.get("Perspective");

    /**
    * The location search parameter for specifying the perspective to view
    * @property perspectiveSearchId
    * @for Perspective
    * @type String
    */
    Perspective.perspectiveSearchId = "p";

    /**
    * Lets you specify which perspective to default to if there's not a single active one
    * @property defaultPerspective
    * @for Perspective
    * @type String
    */
    Perspective.defaultPerspective = null;

    /**
    * A hook so folks can specify the default start page explicitly if the first valid page in the
    * perspective is not the intended start page
    * @property defaultPageLocation
    * @for Perspective
    * @type String
    */
    Perspective.defaultPageLocation = null;

    /**
    * Returns the current perspective ID based on the query parameter or the current
    * discovered perspective
    * @method currentPerspectiveId
    * @for Perspective
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @paran {*} jolokia
    * @param {any} localStorage
    * @return {String}
    */
    function currentPerspectiveId($location, workspace, jolokia, localStorage) {
        var perspective = $location.search()[Perspective.perspectiveSearchId];
        if (!perspective) {
            perspective = Perspective.choosePerspective($location, workspace, jolokia, localStorage);
        }
        return perspective;
    }
    Perspective.currentPerspectiveId = currentPerspectiveId;

    /**
    * Returns an array of all the active perspectives
    * @method getPerspectives
    * @for Perspective
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @paran {*} jolokia
    * @param {any} localStorage
    */
    function getPerspectives($location, workspace, jolokia, localStorage) {
        var perspectives = [];
        angular.forEach(Perspective.metadata, function (perspective, key) {
            if (isValidFunction(workspace, perspective.isValid)) {
                if (!perspective.label) {
                    perspective.label = key;
                }
                if (!perspective.title) {
                    perspective.title = perspective.label;
                }
                perspective.id = key;
                perspectives.push(perspective);
            }
        });
        return perspectives;
    }
    Perspective.getPerspectives = getPerspectives;

    function getPerspectiveById(id) {
        var answer;
        angular.forEach(Perspective.metadata, function (perspective, key) {
            if (key === id) {
                answer = perspective;
            }
        });
        return answer;
    }
    Perspective.getPerspectiveById = getPerspectiveById;

    /**
    * Returns the top level tabs for the given perspectiveId
    * @method topLevelTabsForPerspectiveId
    * @for Perspective
    * @param {Core.Workspace} workspace
    * @param {String} perspective
    * @return {Array}
    */
    function topLevelTabsForPerspectiveId(workspace, perspective) {
        // lets sort using content which is the title in the navbar button, which is what the end user sees
        // if we sort on id, then they may be re-ordered, such as karaf.terminal
        var sortedTopLevelTabs = workspace.topLevelTabs.sortBy(function (f) {
            return f.content;
        });
        var data = perspective ? Perspective.metadata[perspective] : null;
        var metaData = data;
        var answer = [];

        if (!data) {
            answer = sortedTopLevelTabs;
        } else {
            // lets iterate through the available tabs in the perspective
            var topLevelTabs = data.topLevelTabs;

            var includes = filterTabs(topLevelTabs.includes, workspace);
            var excludes = filterTabs(topLevelTabs.excludes, workspace);

            // now do extra filtering of excludes, if they have some conditions in the meta data
            if (metaData) {
                excludes = excludes.filter(function (t) {
                    var metaTab = metaData.topLevelTabs.excludes.find(function (et) {
                        var etid = et.id;
                        return etid && etid === t.id;
                    });
                    if (metaTab != null && angular.isFunction(metaTab.onCondition)) {
                        // not all tabs has on condition function, so use try .. catch
                        var answer = metaTab.onCondition(workspace);
                        if (answer) {
                            Perspective.log.debug("Plugin " + t.id + " excluded in perspective " + perspective);
                            return true;
                        } else {
                            // the condition was false, so it does not apply
                            return false;
                        }
                    }
                    return true;
                });
            }

            // if the meta-data only had excludes, then it means all the top level tabs, excluding these
            if (!topLevelTabs.includes) {
                answer = sortedTopLevelTabs;
            } else {
                // if the meta-data had includes, then its only these
                answer = includes;
            }

            // and remove any excludes
            answer = answer.subtract(excludes);
        }
        return answer;
    }
    Perspective.topLevelTabsForPerspectiveId = topLevelTabsForPerspectiveId;

    function filterTabs(tabs, workspace) {
        var matched = [];
        angular.forEach(tabs, function (tabSpec) {
            var href = tabSpec.href;
            var id = tabSpec.id;
            var rhref = tabSpec.rhref;
            if (href) {
                var hrefValue = href;
                if (angular.isFunction(href)) {
                    hrefValue = href();
                }
                var tab = workspace.topLevelTabs.find(function (t) {
                    var thref = t.href();
                    return thref && thref.startsWith(hrefValue);
                });
                if (!tab && !id && tabSpec.content) {
                    // lets assume the tab is the tabSpec
                    tab = tabSpec;
                }
                if (tab) {
                    matched.push(tab);
                }
            } else if (id) {
                var tab = workspace.topLevelTabs.find(function (t) {
                    var tid = t.id;
                    return tid && tid === id;
                });
                if (tab) {
                    matched.push(tab);
                }
            } else if (rhref) {
                var tab = workspace.topLevelTabs.find(function (t) {
                    var thref = t.href();
                    return thref && thref.match(rhref);
                });
                if (tab) {
                    matched.push(tab);
                }
            }
        });
        return matched;
    }

    /**
    * Filter the top level tabs to only include currently valid tabs.
    */
    function filterOnlyValidTopLevelTabs(workspace, topLevelTabs) {
        var answer = topLevelTabs.filter(function (tab) {
            var href = tab.href();
            return href && isValidFunction(workspace, tab.isValid);
        });
        return answer;
    }
    Perspective.filterOnlyValidTopLevelTabs = filterOnlyValidTopLevelTabs;

    /**
    * Filter the top level tabs to only include currently active tabs.
    */
    function filterOnlyActiveTopLevelTabs(workspace, topLevelTabs) {
        var answer = topLevelTabs.filter(function (tab) {
            var href = tab.href();
            return href && isValidFunction(workspace, tab.isActive);
        });
        return answer;
    }
    Perspective.filterOnlyActiveTopLevelTabs = filterOnlyActiveTopLevelTabs;

    /**
    * Returns the top level tabs for the given perspective (which are valid)
    * @method topLevelTabs
    * @for Perspective
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @paran {*} jolokia
    * @param {any} localStorage
    * @return {Array}
    */
    function getTopLevelTabsForPerspective($location, workspace, jolokia, localStorage) {
        var perspective = currentPerspectiveId($location, workspace, jolokia, localStorage);

        var plugins = Core.configuredPluginsForPerspectiveId(perspective, workspace, jolokia, localStorage);
        var tabs = Core.filterTopLevelTabs(perspective, workspace, plugins);
        tabs = Perspective.filterOnlyValidTopLevelTabs(workspace, tabs);

        return tabs;
    }
    Perspective.getTopLevelTabsForPerspective = getTopLevelTabsForPerspective;

    /**
    * Returns the perspective we should be using right now since none is specified
    * @method choosePerspective
    * @for Perspective
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @paran {*} jolokia
    * @param {any} localStorage
    * @return {String}
    */
    function choosePerspective($location, workspace, jolokia, localStorage) {
        var answer;

        var inFMC = Fabric.isFMCContainer(workspace);
        if (inFMC) {
            var url = $location.url();

            // noisy!
            //log.debug("Checking url: ", url);
            // we want first time users on welcome/index/default page to be in the fabric perspective
            if (url.startsWith("/perspective/defaultPage") || url.startsWith("/login") || url.startsWith("/welcome") || url.startsWith("/index") || url.startsWith("/fabric") || url.startsWith("/dashboard") || url.startsWith("/health") || (url.startsWith("/wiki") && url.has("/fabric/profiles")) || (url.startsWith("/wiki") && url.has("/editFeatures"))) {
                answer = "fabric";
            }
        }
        answer = answer || Perspective.defaultPerspective || "container";

        // noisy!
        // log.debug("Choose perspective url: " + $location.url() + ", in fabric: " + inFMC + " -> " + answer);
        return answer;
    }
    Perspective.choosePerspective = choosePerspective;

    /**
    * Returns the default page after figuring out what the current perspective is
    * @method defaultPage
    * @for Perspective
    * @param {ng.ILocationService} $location
    * @param {Core.Workspace} workspace
    * @paran {*} jolokia
    * @param {any} localStorage
    * @return {String}
    */
    function defaultPage($location, workspace, jolokia, localStorage) {
        if (shouldShowWelcomePage(localStorage) && !Core.isChromeApp()) {
            return "/welcome/";
        }

        // now find the configured default plugin, and then find the top level tab that matches the default plugin
        var answer = Perspective.defaultPageLocation;
        if (!answer && $location && workspace) {
            var perspectiveId = currentPerspectiveId($location, workspace, jolokia, localStorage);
            var defaultPlugin = Core.getDefaultPlugin(perspectiveId, workspace, jolokia, localStorage);
            var tabs = Perspective.topLevelTabsForPerspectiveId(workspace, perspectiveId);
            tabs = Perspective.filterOnlyValidTopLevelTabs(workspace, tabs);

            var defaultTab;
            if (defaultPlugin) {
                tabs.forEach(function (tab) {
                    if (tab.id === defaultPlugin.id) {
                        defaultTab = tab;
                    }
                });
            } else {
                // if no default plugin configured, then select the 1st tab as default
                defaultTab = tabs[0];
            }

            if (defaultTab) {
                // clip the href to get the path to the plugin
                answer = Core.trimLeading(defaultTab.href(), "#");
            }
        }

        return answer || '/help/index';
    }
    Perspective.defaultPage = defaultPage;

    /**
    * Whether to show the welcome page
    */
    function shouldShowWelcomePage(localStorage) {
        var value = localStorage["showWelcomePage"];
        if (angular.isString(value)) {
            return "true" === value;
        }
        return true;
    }
    Perspective.shouldShowWelcomePage = shouldShowWelcomePage;

    /**
    * Returns true if there is no validFn defined or if its defined
    * then the function returns true.
    *
    * @method isValidFunction
    * @for Perspective
    * @param {Core.Workspace} workspace
    * @param {Function} validFn
    * @return {Boolean}
    */
    function isValidFunction(workspace, validFn) {
        return !validFn || validFn(workspace);
    }
})(Perspective || (Perspective = {}));
/**
* @module Perspective
* @main Perspective
*/
var Perspective;
(function (Perspective) {
    var pluginName = 'perspective';
    angular.module(pluginName, ['hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/perspective/defaultPage', {
            templateUrl: 'app/perspective/html/defaultPage.html',
            controller: Perspective.DefaultPageController });
    }).run(function ($location, workspace, viewRegistry, layoutFull) {
        viewRegistry['perspective'] = layoutFull;
    });

    hawtioPluginLoader.addModule(pluginName);
})(Perspective || (Perspective = {}));
/**
* @module Perspective
*/
var Perspective;
(function (Perspective) {
    /**
    * redirects the browser to the default page based on the detected profiles
    * @method DefaultPageController
    * @for Perspective
    * @param {*} $scope
    * @param {ng.ILocationService} $location
    * @param {any} localStorage
    * @param {Core.Workspace} workspace
    */
    function DefaultPageController($scope, $location, localStorage, workspace, jolokia) {
        var url = Perspective.defaultPage($location, workspace, jolokia, localStorage);
        var path = Core.trimLeading(url, "#");
        if (path) {
            console.log("redirecting to default page: " + path);
            $location.url(path);
        } else {
            console.log("No default page could be chosen!");
        }
    }
    Perspective.DefaultPageController = DefaultPageController;
})(Perspective || (Perspective = {}));
/**
* @module Perspective
*/
var Perspective;
(function (Perspective) {
    Perspective.containerPerspectiveEnabled = true;

    /**
    * Configuration for the perspective plugin that defines what tabs are in which perspectives
    * @property metadata
    * @for Perspective
    * @type {any}
    */
    Perspective.metadata = {
        fabric: {
            icon: {
                title: "Fabric8",
                type: "img",
                src: "img/icons/fabric8_icon.svg"
            },
            label: "Fabric",
            isValid: function (workspace) {
                return Fabric.isFMCContainer(workspace);
            },
            lastPage: "#/fabric/containers",
            topLevelTabs: {
                includes: [
                    {
                        href: "#/fabric"
                    },
                    {
                        href: "#/docker"
                    },
                    {
                        href: "#/wiki/branch/"
                    },
                    {
                        href: "#/wiki/profile"
                    },
                    {
                        href: "#/dashboard"
                    },
                    {
                        href: "#/health"
                    },
                    {
                        id: "fabric.insight"
                    }
                ]
            }
        },
        insight: {
            icon: {
                title: "Fabric8 Insight",
                type: "icon",
                src: "icon-eye-open"
            },
            label: "Insight",
            isValid: function (workspace) {
                return Insight.hasInsight(workspace);
            },
            topLevelTabs: {
                includes: [
                    {
                        href: "#/kibanalogs"
                    },
                    {
                        href: "#/insight"
                    },
                    {
                        href: "#/kibanacamel"
                    },
                    {
                        href: "#/camin"
                    },
                    {
                        href: "#/eshead"
                    }
                ]
            }
        },
        container: {
            icon: {
                title: "Java",
                type: "img",
                src: "img/icons/java.svg"
            },
            label: "Container",
            lastPage: "#/logs",
            isValid: function (workspace) {
                return workspace && workspace.tree && workspace.tree.children && workspace.tree.children.length;
            },
            topLevelTabs: {
                excludes: [
                    {
                        href: "#/fabric"
                    },
                    {
                        href: "#/insight"
                    },
                    {
                        href: "#/camin"
                    },
                    {
                        href: "#/kibanalogs"
                    },
                    {
                        href: "#/kibanacamel"
                    },
                    {
                        href: "#/eshead"
                    },
                    {
                        id: "dashboard",
                        // we only want to exclude dashboard if we are running in fabric (as they are in another perspective)
                        // (must use "id" attribute for the plugin, an not href, when using onCondition)
                        onCondition: function (workspace) {
                            return Fabric.isFMCContainer(workspace);
                        }
                    },
                    {
                        id: "health",
                        // we only want to exclude health if we are running in fabric (as they are in another perspective)
                        // (must use "id" attribute for the plugin, an not href, when using onCondition)
                        onCondition: function (workspace) {
                            return Fabric.isFMCContainer(workspace);
                        }
                    },
                    {
                        id: "wiki",
                        // we only want to exclude wiki if we are running in fabric (as they are in another perspective)
                        // (must use "id" attribute for the plugin, an not href, when using onCondition)
                        onCondition: function (workspace) {
                            return Fabric.isFMCContainer(workspace);
                        }
                    }
                ]
            }
        },
        limited: {
            label: "Limited",
            lastPage: "#/logs",
            isValid: function (workspace) {
                return false;
            },
            topLevelTabs: {
                includes: [
                    {
                        href: "#/jmx"
                    },
                    {
                        href: "#/camel"
                    },
                    {
                        href: "#/activemq"
                    },
                    {
                        href: "#/jetty"
                    },
                    {
                        href: "#/logs"
                    }
                ]
            }
        },
        website: {
            label: "WebSite",
            isValid: function (workspace) {
                return Site.sitePluginEnabled && Site.isSiteNavBarValid();
            },
            lastPage: "#/site/doc/index.md",
            topLevelTabs: {
                includes: [
                    {
                        content: "Get Started",
                        title: "How to get started using hawtio",
                        href: function () {
                            return "#/site/doc/GetStarted.md";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    },
                    {
                        content: "FAQ",
                        title: "Frequently Asked Questions",
                        href: function () {
                            return "#/site/FAQ.md";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    },
                    {
                        content: "User Guide",
                        title: "All the docs on using hawtio",
                        href: function () {
                            return "#/site/book/doc/index.md";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    },
                    {
                        content: "Community",
                        title: "Come on in and join our community!",
                        href: function () {
                            return "#/site/doc/Community.html";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    },
                    {
                        content: "Developers",
                        title: "Resources for developers if you want to hack on hawtio or provide your own plugins",
                        href: function () {
                            return "#/site/doc/developers/index.md";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    },
                    {
                        content: "github",
                        title: "Hawtio's source code and issue tracker",
                        href: function () {
                            return "https://github.com/hawtio/hawtio";
                        },
                        isValid: function () {
                            return Site.isSiteNavBarValid();
                        }
                    }
                ]
            }
        }
    };
})(Perspective || (Perspective = {}));
/**
* @module Insight
*/
var Insight;
(function (Insight) {
    function JVMsController($scope, jolokia, localStorage) {
        $scope.time_options = ['1m', '5m', '15m', '1h', '6h', '12h'];
        $scope.timespan = '1m';
        $scope.containers = [];
        $scope.profiles = [Insight.allContainers];
        $scope.versions = [];
        $scope.profile = Insight.allContainers;

        $scope.metrics = [];
        $scope.updateRate = parseInt(localStorage['updateRate']);
        $scope.data = [];

        var chartsMeta = [
            { name: "threads", type: "sta-jvm", field: "threads.count" },
            { name: "mem", type: "sta-jvm", field: "mem.heap_used" }];
        var mainDiv = "#charts";

        $scope.set_timespan = function (t) {
            $scope.timespan = t;
            rebuildCharts();
        };

        $scope.profile_changed = function () {
            rebuildCharts();
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Insight.managerMBean,
            operation: 'containers()',
            arguments: []
        }, onSuccess(onContainers));

        function onContainers(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.containers = [];
                $scope.profiles = [Insight.allContainers];
                $scope.versions = [];
                $scope.result.forEach(function (container) {
                    $scope.profiles = $scope.profiles.union(container.profileIds.map(function (id) {
                        return { id: id };
                    }));
                    $scope.versions = $scope.versions.union([container.versionId]);
                    $scope.containers.push({
                        name: container.id,
                        alive: container.alive,
                        version: container.versionId,
                        profileIds: container.profileIds
                    });
                });
                Core.$apply($scope);
                rebuildCharts();
            }
        }

        function rebuildCharts() {
            var chartsDef = [];
            chartsMeta.forEach(function (meta) {
                $scope.containers.forEach(function (container) {
                    if ($scope.profile === Insight.allContainers || $.inArray($scope.profile.id, container.profileIds) >= 0) {
                        chartsDef.push({
                            name: meta.name + " [" + container.name + "]",
                            type: meta.type,
                            field: meta.field,
                            query: "host: \"" + container.name + "\""
                        });
                    }
                });
            });
            Insight.createCharts($scope, chartsDef, mainDiv, jolokia);
        }
    }
    Insight.JVMsController = JVMsController;
})(Insight || (Insight = {}));
/**
* @module Insight
*/
var Insight;
(function (Insight) {
    Insight.managerMBean = "io.fabric8:type=Fabric";

    Insight.allContainers = { id: '-- all --' };

    function hasInsight(workspace) {
        return workspace.treeContainsDomainAndProperties('org.elasticsearch', { service: 'restjmx' });
    }
    Insight.hasInsight = hasInsight;

    function getInsightMetricsCollectorMBean(workspace) {
        var node = workspace.findMBeanWithProperties('io.fabric8.insight', { type: 'MetricsCollector' });
        if (!node) {
            node = workspace.findMBeanWithProperties('org.fusesource.insight', { type: 'MetricsCollector' });
        }
        return node ? node.objectName : null;
    }
    Insight.getInsightMetricsCollectorMBean = getInsightMetricsCollectorMBean;

    function createCharts($scope, chartsDef, element, jolokia) {
        var chartsDiv = $(element);
        var width = chartsDiv.width() - 80;

        var context = cubism.context().serverDelay(interval_to_seconds('1m') * 1000).clientDelay($scope.updateRate).step(interval_to_seconds($scope.timespan) * 1000).size(width);

        var d3Selection = d3.select(chartsDiv[0]);
        d3Selection.html("");
        d3Selection.selectAll(".axis").data(["top", "bottom"]).enter().append("div").attr("class", function (d) {
            return d + " axis";
        }).each(function (d) {
            d3.select(this).call(context.axis().ticks(12).orient(d));
        });

        d3Selection.append("div").attr("class", "rule").call(context.rule());

        context.on("focus", function (i) {
            d3Selection.selectAll(".value").style("right", i === null ? null : context.size() - i + "px");
        });

        chartsDef.forEach(function (chartDef) {
            d3Selection.call(function (div) {
                div.append("div").data([chart(context, chartDef, jolokia)]).attr("class", "horizon").call(context.horizon());
            });
        });
    }
    Insight.createCharts = createCharts;

    function chart(context, chartDef, jolokia) {
        return context.metric(function (start, stop, step, callback) {
            var values = [], value = 0, start = +start, stop = +stop;
            var range = {
                range: {
                    timestamp: {
                        from: new Date(start).toISOString(),
                        to: new Date(stop).toISOString()
                    }
                }
            };
            var filter;
            if (chartDef.query) {
                filter = {
                    fquery: {
                        query: {
                            filtered: {
                                query: {
                                    query_string: {
                                        query: chartDef.query
                                    }
                                },
                                filter: range
                            }
                        }
                    }
                };
            } else {
                filter = range;
            }
            var request = {
                size: 0,
                facets: {
                    histo: {
                        date_histogram: {
                            value_field: chartDef.field,
                            key_field: "timestamp",
                            interval: step + "ms"
                        },
                        facet_filter: filter
                    }
                }
            };
            var jreq = {
                type: 'exec',
                mbean: 'org.elasticsearch:service=restjmx',
                operation: 'exec',
                arguments: ['POST', '/_all/' + chartDef.type + '/_search', JSON.stringify(request)] };
            jolokia.request(jreq, { success: function (response) {
                    var map = {};
                    var data = jQuery.parseJSON(response.value)["facets"]["histo"]["entries"];
                    data.forEach(function (entry) {
                        map[entry.time] = entry.max;
                    });
                    var delta = 0;
                    if (chartDef.meta !== undefined) {
                        if (chartDef.meta['type'] === 'trends-up' || chartDef.meta['type'] === 'peak') {
                            delta = +1;
                        } else if (chartDef.meta['type'] === 'trends-down') {
                            delta = -1;
                        }
                    }
                    while (start < stop) {
                        var v = 0;
                        if (delta !== 0) {
                            if (map[start - step] !== undefined) {
                                var d = (map[start] - map[start - step]) * delta;
                                v = d > 0 ? d : 0;
                            }
                        } else {
                            if (map[start] !== undefined) {
                                v = map[start];
                            }
                        }
                        values.push(v);
                        start += step;
                    }
                    callback(null, values);
                } });
        }, chartDef.name);
    }

    function interval_to_seconds(string) {
        var matches = string.match(/(\d+)([Mwdhms])/);
        switch (matches[2]) {
            case 'M':
                return matches[1] * 2592000;
                ;
            case 'w':
                return matches[1] * 604800;
                ;
            case 'd':
                return matches[1] * 86400;
                ;
            case 'h':
                return matches[1] * 3600;
                ;
            case 'm':
                return matches[1] * 60;
                ;
            case 's':
                return matches[1];
        }
    }

    function time_ago(string) {
        return new Date(new Date().getTime() - (interval_to_seconds(string) * 1000));
    }
})(Insight || (Insight = {}));
/**
* @module Insight
*/
var Insight;
(function (Insight) {
    function AllController($scope, jolokia, localStorage, workspace) {
        $scope.result = null;
        $scope.containers = [];
        $scope.profiles = [Insight.allContainers];
        $scope.versions = [];
        $scope.profile = Insight.allContainers;

        $scope.time_options = ['1m', '5m', '15m', '1h', '6h', '12h'];
        $scope.timespan = '1m';
        $scope.updateRate = parseInt(localStorage['updateRate']);

        $scope.chartsMeta = [];

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Insight.managerMBean,
            operation: 'containers()',
            arguments: []
        }, onSuccess(onContainers));

        function onContainers(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.containers = [];
                $scope.profiles = [Insight.allContainers];
                $scope.versions = [];
                $scope.result.forEach(function (container) {
                    $scope.profiles = $scope.profiles.union(container.profileIds.map(function (id) {
                        return { id: id };
                    }));
                    $scope.versions = $scope.versions.union([container.versionId]);
                    $scope.containers.push({
                        name: container.id,
                        alive: container.alive,
                        version: container.versionId,
                        profileIds: container.profileIds
                    });
                });
                Core.$apply($scope);
            }
        }

        var mbean = Insight.getInsightMetricsCollectorMBean(workspace);
        $scope.metrics = {};
        if (mbean) {
            $scope.metrics = jQuery.parseJSON(jolokia.getAttribute(mbean, "Metrics"));
        }

        var jreq = {
            type: 'exec',
            mbean: 'org.elasticsearch:service=restjmx',
            operation: 'exec',
            arguments: ['GET', '/_all/_mapping', ''] };

        jolokia.request(jreq, { success: function (response) {
                var data = jQuery.parseJSON(response.value);
                var roots = {};
                var children = [];
                for (var index in data) {
                    if (index.startsWith("insight-")) {
                        for (var mapping in data[index]) {
                            if (mapping.startsWith("sta-")) {
                                var name = mapping.substring(4);
                                if (!roots[name]) {
                                    roots[name] = true;
                                    children.push({
                                        title: name,
                                        expand: true,
                                        children: getChildren(data[index][mapping], name, "", data[index][mapping]["properties"]["host"] !== undefined)
                                    });
                                }
                            }
                        }
                    }
                }
                $("#insighttree").dynatree({
                    checkbox: true,
                    selectMode: 2,
                    onSelect: onSelect,
                    onClick: onClick,
                    onKeydown: onKeydown,
                    children: children
                });
            } });

        $scope.set_timespan = function (t) {
            $scope.timespan = t;
            rebuildCharts();
        };

        $scope.profile_changed = function () {
            rebuildCharts();
        };

        function onSelect(flag, node) {
            var selNodes = node.tree.getSelectedNodes();
            $scope.chartsMeta = selNodes.map(function (node) {
                var data = node.data;
                return { name: data["field"], field: data["field"], type: data["type"], host: data["hasHost"] };
            });
            rebuildCharts();
        }

        function onClick(node, event) {
            // We should not toggle, if target was "checkbox", because this
            // would result in double-toggle (i.e. no toggle)
            if (node.getEventTargetType(event) === "title") {
                node.toggleSelect();
            }
            return true;
        }

        function onKeydown(node, event) {
            if (event.which === 32) {
                node.toggleSelect();
                return false;
            }
        }

        function rebuildCharts() {
            var chartsDef = [];
            $scope.chartsMeta.forEach(function (meta) {
                var metadata = $scope.metrics[meta.type] !== undefined ? $scope.metrics[meta.type][meta.field] : undefined;
                if (meta.host) {
                    $scope.containers.forEach(function (container) {
                        if ($scope.profile === Insight.allContainers || $.inArray($scope.profile.id, container.profileIds) >= 0) {
                            chartsDef.push({
                                name: (metadata !== undefined ? metadata['description'] : meta.name) + " [" + container.name + "]",
                                type: "sta-" + meta.type,
                                field: meta.field,
                                query: "host: \"" + container.name + "\"",
                                meta: metadata
                            });
                        }
                    });
                } else {
                    chartsDef.push({
                        name: metadata !== undefined ? metadata['description'] : meta.name,
                        type: "sta-" + meta.type,
                        field: meta.field,
                        meta: metadata
                    });
                }
            });
            Insight.createCharts($scope, chartsDef, "#charts", jolokia);
        }
    }
    Insight.AllController = AllController;

    function getChildren(node, type, field, hasHost) {
        var children = [];
        for (var p in node["properties"]) {
            var obj = node["properties"][p];
            if (obj["type"] === 'long' || obj["type"] === 'double') {
                children.push({ title: p, field: field + p, type: type, hasHost: hasHost });
            } else if (obj["properties"]) {
                children.push({ title: p, isFolder: true, children: getChildren(obj, type, field + p + ".", hasHost) });
            }
        }
        return children;
    }
})(Insight || (Insight = {}));
/**
* @module Insight
*/
var Insight;
(function (Insight) {
    function ElasticSearchController($scope, jolokia, localStorage) {
        $scope.time_options = ['1m', '5m', '15m', '1h', '6h', '12h'];
        $scope.timespan = '1m';

        $scope.metrics = [];
        $scope.updateRate = parseInt(localStorage['updateRate']);
        $scope.data = [];

        var chartsDef = [
            { name: "active primary shards", type: "sta-elasticsearch", field: "active_primary_shards" },
            { name: "active shards", type: "sta-elasticsearch", field: "active_shards" },
            { name: "relocating shards", type: "sta-elasticsearch", field: "relocating_shards" },
            { name: "initializing shards", type: "sta-elasticsearch", field: "initializing_shards" },
            { name: "unassigned shards", type: "sta-elasticsearch", field: "unassigned_shards" }];
        var mainDiv = "#charts";

        $scope.set_timespan = function (t) {
            $scope.timespan = t;
            rebuildCharts();
        };

        rebuildCharts();

        function rebuildCharts() {
            Insight.createCharts($scope, chartsDef, mainDiv, jolokia);
        }
    }
    Insight.ElasticSearchController = ElasticSearchController;
})(Insight || (Insight = {}));
/**
* @module Insight
* @main Insight
*/
var Insight;
(function (Insight) {
    // create our angular module and tell angular what route(s) it will handle
    var insightPlugin = angular.module('insight', ['hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/insight/all', { templateUrl: 'app/insight/html/all.html' }).when('/insight/jvms', { templateUrl: 'app/insight/html/jvms.html' }).when('/insight/elasticsearch', { templateUrl: 'app/insight/html/elasticsearch.html' });
    });

    insightPlugin.run(function (workspace, viewRegistry, helpRegistry) {
        viewRegistry["insight"] = "app/insight/html/layoutInsight.html";
        helpRegistry.addUserDoc('insight', 'app/insight/doc/help.md', function () {
            return Fabric.hasFabric(workspace) && workspace.treeContainsDomainAndProperties("org.elasticsearch", { service: "restjmx" });
        });

        // instead lets add the Metrics link on the Fabric sub nav bar
        // Set up top-level link to our plugin
        workspace.topLevelTabs.push({
            id: "insight",
            content: "Metrics",
            title: "View Insight metrics",
            href: function () {
                return "#/insight/all";
            },
            isValid: function (workspace) {
                return Fabric.hasFabric(workspace) && workspace.treeContainsDomainAndProperties("org.elasticsearch", { service: "restjmx" });
            }
        });
    });

    // tell the hawtio plugin loader about our plugin so it can be
    // bootstrapped with the rest of angular
    hawtioPluginLoader.addModule('insight');
})(Insight || (Insight = {}));
/**
* @module IDE
*/
var IDE;
(function (IDE) {
    /**
    * Returns the mbean name of the IDE Facade mbean if its available
    */
    function getIdeMBean(workspace) {
        return Core.getMBeanTypeObjectName(workspace, "hawtio", "IdeFacade");
    }
    IDE.getIdeMBean = getIdeMBean;

    /**
    * Returns true if open in IDEA is enabled
    */
    function isOpenInIdeaSupported(workspace, localStorage) {
        var value = localStorage["openInIDEA"];
        return value !== "false";
    }
    IDE.isOpenInIdeaSupported = isOpenInIdeaSupported;

    /**
    * Returns true if open in TextMate is enabled
    */
    function isOpenInTextMateSupported(workspace, localStorage) {
        var value = localStorage["openInTextMate"];
        return value !== "false";
    }
    IDE.isOpenInTextMateSupported = isOpenInTextMateSupported;

    /**
    * Attempts to find the absolute file name for the given file and class name
    */
    function findClassAbsoluteFileName(mbean, jolokia, localStorage, fileName, className, onResult) {
        var sourceRoots = [];

        // TODO load from localStorage
        var answer = null;
        if (mbean) {
            answer = jolokia.execute(mbean, "findClassAbsoluteFileName", fileName, className, sourceRoots, onSuccess(onResult));
        } else {
            onResult(answer);
        }
        return answer;
    }
    IDE.findClassAbsoluteFileName = findClassAbsoluteFileName;

    function asNumber(value, defaultValue) {
        if (typeof defaultValue === "undefined") { defaultValue = 0; }
        if (angular.isNumber(value)) {
            return value;
        } else if (angular.isString(value)) {
            return parseInt(value);
        } else {
            return defaultValue;
        }
    }

    function max(v1, v2) {
        return (v1 >= v2) ? v1 : v2;
    }

    /**
    * Opens the file in IDEA
    */
    function ideaOpenAndNavigate(mbean, jolokia, absoluteFileName, line, column, fn) {
        if (typeof fn === "undefined") { fn = null; }
        var answer = null;
        if (mbean) {
            line = max(asNumber(line) - 1, 0);
            column = max(asNumber(column) - 1, 0);
            answer = jolokia.execute(mbean, "ideaOpenAndNavigate", absoluteFileName, line, column, onSuccess(fn));
        }
        return answer;
    }
    IDE.ideaOpenAndNavigate = ideaOpenAndNavigate;
})(IDE || (IDE = {}));
/**
* IDE integration
*
* @module IDE
*/
var IDE;
(function (IDE) {
    var pluginName = 'ide';

    angular.module(pluginName, ['bootstrap', 'hawtioCore']).directive('hawtioOpenIde', function (localStorage, workspace, jolokia) {
        return new IDE.OpenInIdeDirective(localStorage, workspace, jolokia);
    }).run(function (helpRegistry) {
        helpRegistry.addDevDoc('IDE', 'app/ide/doc/developer.md');
    });

    hawtioPluginLoader.addModule(pluginName);
})(IDE || (IDE = {}));
var IDE;
(function (IDE) {
    var log = Logger.get("IDE");

    var OpenInIdeDirective = (function () {
        function OpenInIdeDirective(localStorage, workspace, jolokia) {
            var _this = this;
            this.localStorage = localStorage;
            this.workspace = workspace;
            this.jolokia = jolokia;
            this.restrict = 'E';
            this.replace = true;
            this.transclude = false;
            this.scope = {
                fileName: '@',
                className: '@',
                line: '@',
                column: '@'
            };
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        OpenInIdeDirective.prototype.doLink = function ($scope, $element, $attrs) {
            var workspace = this.workspace;
            var jolokia = this.jolokia;

            var mbean = IDE.getIdeMBean(workspace);
            var fileName = $scope.fileName;
            if (mbean && fileName) {
                var className = $scope.className;
                var line = $scope.line;
                var col = $scope.col;
                if (!angular.isDefined(line) || line === null)
                    line = 0;
                if (!angular.isDefined(col) || col === null)
                    col = 0;

                if (IDE.isOpenInIdeaSupported(workspace, localStorage)) {
                    var ideaButton = $('<button class="btn btn-mini"><img src="app/ide/img/intellijidea.png" width="16" height="16"></button>');

                    function onResult(absoluteName) {
                        if (!absoluteName) {
                            log.info("Could not find file in source code: " + fileName + " class: " + className);
                            ideaButton.attr("title", "Could not find source file: " + fileName);
                        } else {
                            ideaButton.attr("title", "Opening in IDEA: " + absoluteName);
                            IDE.ideaOpenAndNavigate(mbean, jolokia, absoluteName, line, col);
                        }
                    }

                    ideaButton.on("click", function () {
                        log.info("Finding local file name: " + fileName + " className: " + className);
                        IDE.findClassAbsoluteFileName(mbean, jolokia, localStorage, fileName, className, onResult);
                    });
                    $element.append(ideaButton);
                }
            }
        };
        return OpenInIdeDirective;
    })();
    IDE.OpenInIdeDirective = OpenInIdeDirective;
})(IDE || (IDE = {}));
/**
* @module API
*/
var API;
(function (API) {
    API.log = Logger.get("API");

    API.wadlNamespace = "http://schemas.xmlsoap.org/wsdl/";

    /**
    * Loads the XML for the given url if its defined or ignore if not valid
    * @method loadXml
    * @for API
    * @static
    * @param {String} url
    * @param {Function} onXml
    *
    */
    function loadXml(url, onXml) {
        if (url) {
            API.log.info("Loading XML: " + url);

            var ajaxParams = {
                type: "GET",
                url: url,
                beforeSend: function (xhr) {
                    xhr.setRequestHeader('Authorization', null);
                },
                dataType: "xml",
                contextType: "text/xml",
                success: onXml,
                error: function (jqXHR, textStatus, errorThrow) {
                    API.log.error("Failed to query XML for: " + url + " status:" + textStatus + " error: " + errorThrow);
                }
            };
            $.ajax(ajaxParams);
        }
    }
    API.loadXml = loadXml;

    var wadlXmlToJavaConfig = {};

    function parseJson(json) {
        var answer = null;
        try  {
            //console.log("got JSON: " + responseJson);
            answer = JSON.parse(json);
        } catch (e) {
            API.log.info("Failed to parse JSON " + e);
            API.log.info("JSON: " + json);
        }
        return answer;
    }
    API.parseJson = parseJson;

    function initScope($scope, $location, jolokia) {
        var search = $location.search();
        $scope.container = search["container"];
        $scope.objectName = search["objectName"];

        $scope.showHide = function (resource) {
            if (resource) {
                resource.hide = resource.hide ? false : true;
            }
        };

        $scope.showOperations = function (resource) {
            showHideOperations(resource, false);
        };

        $scope.expandOperations = function (resource) {
            showHideOperations(resource, true);
        };

        function showHideOperations(resource, flag) {
            if (resource) {
                resource.hide = false;
                angular.forEach(resource.resource, function (childResource) {
                    showHideOperations(childResource, flag);
                });
                angular.forEach(resource.method || resource.operations, function (method) {
                    method.expanded = flag;
                });
            }
        }

        $scope.autoFormat = function (codeMirror) {
            if (!codeMirror) {
                // lets try find the codeMirror in a child scope as a hack :)
                codeMirror = findChildScopeValue($scope, "codeMirror");
            }
            if (codeMirror) {
                setTimeout(function () {
                    CodeEditor.autoFormatEditor(codeMirror);
                }, 50);
            }
        };

        /**
        * Note using this method is usually a dirty hack ;)
        */
        function findChildScopeValue(scope, name) {
            var answer = scope[name];
            var childScope = scope.$$childHead;
            while (childScope && !answer) {
                answer = findChildScopeValue(childScope, name);
                childScope = childScope.$$nextSibling;
            }
            return answer;
        }

        if ($scope.container && $scope.objectName) {
            Fabric.containerJolokia(jolokia, $scope.container, function (remoteJolokia) {
                $scope.remoteJolokia = remoteJolokia;
                if (remoteJolokia) {
                    API.loadJsonSchema(remoteJolokia, $scope.objectName, function (jsonSchema) {
                        //log.info("Got JSON Schema: " + JSON.stringify(jsonSchema, null, "  "));
                        $scope.jsonSchema = jsonSchema;
                        Core.$apply($scope);
                    });
                } else {
                    API.log.info("No Remote Jolokia!");
                }
            });
        } else {
            API.log.info("No container or objectName");
        }
        API.log.info("container: " + $scope.container + " objectName: " + $scope.objectName + " url: " + $scope.url);
    }
    API.initScope = initScope;

    /**
    * Loads the JSON schema from a given CXF endpoint mbean
    * @method loadJsonSchema
    * @for API
    * @static
    * @paran {*} jolokia
    * @param {String} mbean
    * @param {Function} onJsonSchemaFn
    */
    function loadJsonSchema(jolokia, mbean, onJsonSchemaFn) {
        function onResults(response) {
            var schema = {};
            if (response) {
                var json = response;
                if (json) {
                    schema = parseJson(json);
                }
            }
            onJsonSchemaFn(schema);
        }
        if (mbean) {
            return jolokia.execute(mbean, "getJSONSchema", onSuccess(onResults));
        } else {
            var schema = {};
            onJsonSchemaFn(schema);
            return schema;
        }
    }
    API.loadJsonSchema = loadJsonSchema;

    /**
    * When a WADL XML document is loaded, lets convert it to JSON and return it
    * @method onWadlXmlLoaded
    * @for API
    * @static
    * @param {any} response
    * @return {any}
    */
    function onWadlXmlLoaded(response) {
        var root = response.documentElement;
        var output = {};
        return API.convertWadlToJson(root, output);
    }
    API.onWadlXmlLoaded = onWadlXmlLoaded;

    /**
    * Converts the given XML element from WADL to JSON
    * @method convertWadlToJson
    * @for API
    * @static
    * @param {any} element
    * @param {any} obj
    * @return {any}
    */
    function convertWadlToJson(element, obj) {
        if (typeof obj === "undefined") { obj = {}; }
        return API.convertXmlToJson(element, obj, wadlXmlToJavaConfig);
    }
    API.convertWadlToJson = convertWadlToJson;

    function convertWadlJsonToSwagger(object) {
        // now lets convert to swagger style json
        var apis = [];
        var basePath = null;
        var resourcePath = null;

        var resources = Core.pathGet(object, ["resources", 0]);
        if (resources) {
            basePath = resources.base;
            angular.forEach(resources.resource, function (resource) {
                var path = resource.path;
                var operations = [];
                angular.forEach(resource.method, function (method) {
                    var name = method.name;
                    var responseMessages = [];

                    /*
                    {
                    "code": 404,
                    "message": "There are no businesses"
                    }
                    */
                    var parameters = [];

                    /*
                    {
                    "name": "query",
                    "description": "a text query to search across facilities",
                    "required": false,
                    "allowMultiple": false,
                    "dataType": "string",
                    "paramType": "query"
                    }
                    
                    */
                    operations.push({
                        "method": method.name,
                        "summary": method.summary,
                        "notes": method.notes,
                        "nickname": method.nickname,
                        "type": method.type,
                        "parameters": parameters,
                        "produces": [
                            "application/json"
                        ],
                        "responseMessages": responseMessages
                    });
                });

                apis.push({
                    path: path,
                    operations: operations
                });
            });
        }
        return {
            "apiVersion": "1.0",
            "swaggerVersion": "1.2",
            "basePath": basePath,
            "resourcePath": resourcePath,
            "produces": [
                "application/json"
            ],
            apis: apis
        };
    }
    API.convertWadlJsonToSwagger = convertWadlJsonToSwagger;

    function nodeName(owner, node) {
        return node ? node.localName : null;
    }

    /**
    * Converts the given child elements or attributes into properties on the object
    * to convert the XML into JSON using the given config to customise which properties should
    * be considered singular
    * @method convertXmlToJson
    * @for API
    * @static
    * @param {any} element
    * @param {any} obj
    * @param {any} config
    * @return {any}
    */
    function convertXmlToJson(element, obj, config) {
        var elementProperyFn = config.elementToPropertyName || nodeName;
        var attributeProperyFn = config.attributeToPropertyName || nodeName;

        angular.forEach(element.childNodes, function (child) {
            if (child.nodeType === 1) {
                var propertyName = elementProperyFn(element, child);
                if (propertyName) {
                    // TODO should we assume everything is a list and then flatten later?
                    var array = obj[propertyName] || [];
                    if (!angular.isArray(array)) {
                        array = [array];
                    }
                    var value = {};
                    convertXmlToJson(child, value, config);
                    array.push(value);
                    obj[propertyName] = array;
                }
            }
        });
        angular.forEach(element.attributes, function (attr) {
            var propertyName = attributeProperyFn(element, attr);
            if (propertyName) {
                var value = attr.nodeValue;
                obj[propertyName] = value;
            }
        });
        return obj;
    }
    API.convertXmlToJson = convertXmlToJson;

    /**
    * Concatenate all the non-null arrays into a single array
    * @param arrays an array of arrays
    * @return the single flatten arrays with any null/undefined values ignored
    */
    function concatArrays(arrays) {
        var answer = [];
        angular.forEach(arrays, function (array) {
            if (array) {
                if (angular.isArray(array)) {
                    answer = answer.concat(array);
                } else {
                    answer.push(array);
                }
            }
        });
        return answer;
    }
    API.concatArrays = concatArrays;
})(API || (API = {}));
/**
* @module API
*/
var API;
(function (API) {
    function WadlViewController($scope, $location, $http, jolokia) {
        API.initScope($scope, $location, jolokia);

        $scope.url = $location.search()["wadl"];
        API.loadXml($scope.url, onWsdl);

        $scope.$watch("apidocs", enrichApiDocsWithSchema);
        $scope.$watch("jsonSchema", enrichApiDocsWithSchema);

        $scope.tryInvoke = function (resource, method) {
            if (resource) {
                var path = resource.fullPath || resource.path;
                if (path) {
                    // lets substitue the parameters
                    angular.forEach(resource.param, function (param) {
                        var name = param.name;
                        if (name) {
                            var value = param.value;
                            if (angular.isUndefined(value) || value === null) {
                                value = "";
                            }
                            value = value.toString();
                            API.log.debug("replacing " + name + " with '" + value + "'");
                            path = path.replace(new RegExp("{" + name + "}", "g"), value);
                        }
                    });
                    API.log.info("Lets invoke resource: " + path);
                    var url = Core.useProxyIfExternal(path);
                    var methodName = method.name || "GET";
                    method.invoke = {
                        url: url,
                        running: true
                    };
                    var requestData = {
                        method: methodName,
                        url: url,
                        headers: {}
                    };
                    if (methodName === "POST" || methodName === "PUT") {
                        // lets see if we can find a payload
                        angular.forEach(method.request, function (request) {
                            if (!requestData["data"]) {
                                requestData["data"] = request.value;
                            }
                            if (!requestData.headers["Content-Type"]) {
                                requestData.headers["Content-Type"] = request.contentType;
                            }
                        });
                    }
                    API.log.info("About to make request: " + angular.toJson(requestData));
                    $http(requestData).success(function (data, status, headers, config) {
                        API.log.info("Worked!" + data);
                        method.invoke = {
                            url: url,
                            realUrl: path,
                            success: true,
                            data: data,
                            dataMode: textFormat(headers),
                            status: status,
                            headers: headers(),
                            config: config
                        };
                        Core.$apply($scope);
                    }).error(function (data, status, headers, config) {
                        // called asynchronously if an error occurs
                        // or server returns response with an error status.
                        API.log.info("Failed: " + status);
                        method.invoke = {
                            url: url,
                            realUrl: path,
                            data: data,
                            dataMode: textFormat(headers),
                            status: status,
                            headers: headers(),
                            config: config
                        };
                        Core.$apply($scope);
                    });
                }
            }
        };

        function textFormat(headers) {
            return contentTypeTextFormat(headers("content-type"));
        }

        function contentTypeTextFormat(contentType) {
            if (contentType) {
                if (contentType.endsWith("xml")) {
                    return "xml";
                }
                if (contentType.endsWith("html")) {
                    return "html";
                }
                if (contentType.endsWith("json")) {
                    return "json";
                }
            }
            return null;
        }

        function enrichApiDocsWithSchema() {
            var apidocs = $scope.apidocs;
            var jsonSchema = $scope.jsonSchema;
            if (apidocs && jsonSchema) {
                enrichResources(jsonSchema, apidocs.resources);
            }
        }

        function enrichResources(jsonSchema, resources, parentUri) {
            if (typeof parentUri === "undefined") { parentUri = null; }
            angular.forEach(resources, function (resource) {
                var base = resource.base;
                if (base) {
                    if (parentUri) {
                        base = parentUri + base;
                    }
                } else {
                    base = parentUri;
                }
                var path = resource.path;
                if (base && path) {
                    if (!base.endsWith("/") && !path.startsWith("/")) {
                        base += "/";
                    }
                    base += path;
                    resource["fullPath"] = base;
                }
                var childResources = resource.resource;
                if (childResources) {
                    enrichResources(jsonSchema, childResources, base);
                }
                angular.forEach(API.concatArrays([resource.method, resource.operation]), function (method) {
                    // lets remove any empty requests
                    var request = method.request;
                    if (request) {
                        var count = request.count(function (n) {
                            return n["representation"];
                        });
                        if (!count) {
                            delete method.request;
                        }
                    }
                    angular.forEach(API.concatArrays([method.request, method.response]), function (object) {
                        var element = object["element"];
                        var representations = object["representation"];
                        if (representations) {
                            var mediaTypes = representations.map(function (r) {
                                return r["mediaType"];
                            });
                            object["mediaTypes"] = mediaTypes;
                            if (mediaTypes && mediaTypes.length) {
                                object["contentType"] = mediaTypes[0];
                            }
                        }
                        angular.forEach(representations, function (representation) {
                            if (!element) {
                                element = representation["element"];
                            }
                            enrichRepresentation(jsonSchema, representation);
                        });
                        if (element) {
                            object["element"] = element;
                        }
                    });
                });
            });
        }

        function enrichRepresentation(jsonSchema, representation) {
            var defs = jsonSchema ? jsonSchema["definitions"] : null;
            if (defs && representation) {
                var contentType = representation["mediaType"];
                if (contentType) {
                    representation["dataMode"] = contentTypeTextFormat(contentType);
                }

                // TODO find a class name in the representation?
                var element = representation["element"];
                if (element) {
                    var idx = element.indexOf(':');
                    if (idx >= 0) {
                        element = element.substring(idx + 1);
                    }

                    // lets see if we can find a definition which ends with this element
                    var elementPostfix = "." + element;
                    var foundDef = null;
                    angular.forEach(defs, function (value, key) {
                        if (!foundDef && (key === element || key.endsWith(elementPostfix))) {
                            foundDef = value;
                            representation["schema"] = foundDef;
                            representation["typeName"] = element;
                            representation["javaClass"] = key;
                        }
                    });
                }
            }
        }

        function onWsdl(response) {
            $scope.apidocs = API.onWadlXmlLoaded(response);

            //log.info("API docs: " + JSON.stringify($scope.apidocs, null, "  "));
            Core.$apply($scope);
        }
    }
    API.WadlViewController = WadlViewController;
})(API || (API = {}));
/**
* @module API
*/
var API;
(function (API) {
    function WsdlViewController($scope, $location, jolokia) {
        var log = Logger.get("API");

        API.initScope($scope, $location, jolokia);
        var wsdlNamespace = "http://schemas.xmlsoap.org/wsdl/";

        $scope.url = $location.search()["wsdl"];
        API.loadXml($scope.url, onWsdl);

        $scope.$watch("services", enrichApiDocsWithSchema);
        $scope.$watch("jsonSchema", enrichApiDocsWithSchema);

        function enrichApiDocsWithSchema() {
            var services = $scope.services;
            var jsonSchema = $scope.jsonSchema;
            if (services && jsonSchema) {
                log.info("We have services and jsonSchema!");
                enrichServices(jsonSchema, services);
            }
        }

        function enrichServices(jsonSchema, services) {
            angular.forEach(services, function (service) {
                angular.forEach(service.operations, function (method) {
                    angular.forEach(API.concatArrays([method.inputs, method.outputs]), function (object) {
                        enrichRepresentation(jsonSchema, object);
                    });
                });
            });
        }

        function enrichRepresentation(jsonSchema, representation) {
            var defs = jsonSchema ? jsonSchema["definitions"] : null;
            if (defs && representation) {
                var name = representation["name"];
                if (name) {
                    var foundDef = defs[name];
                    if (foundDef) {
                        // unwrap arrays
                        if (angular.isArray(foundDef) && foundDef.length > 0) {
                            foundDef = foundDef[0];
                        }
                        log.info("Found def " + angular.toJson(foundDef) + " for name " + name);
                        representation["schema"] = foundDef;
                    }
                }
            }
        }

        function onWsdl(response) {
            $scope.services = [];
            var root = response.documentElement;
            var targetNamespace = root ? root.getAttribute("targetNamespace") : null;
            var name = root ? root.getAttribute("name") : null;
            var portTypes = response.getElementsByTagNameNS(wsdlNamespace, "portType");
            var services = response.getElementsByTagNameNS(wsdlNamespace, "service");
            var bindings = response.getElementsByTagNameNS(wsdlNamespace, "binding");

            angular.forEach(portTypes, function (portType) {
                var service = {
                    name: name,
                    targetNamespace: targetNamespace,
                    portName: portType.getAttribute("name") || "Unknown",
                    operations: []
                };
                $scope.services.push(service);
                var operations = portType.getElementsByTagNameNS(wsdlNamespace, "operation");
                angular.forEach(operations, function (operation) {
                    var input = operation.getElementsByTagNameNS(wsdlNamespace, "input");
                    var output = operation.getElementsByTagNameNS(wsdlNamespace, "output");

                    function createMessageData(data) {
                        var answer = [];
                        angular.forEach(data, function (item) {
                            var name = item.getAttribute("name");
                            if (name) {
                                answer.push({
                                    name: name
                                });
                            }
                        });
                        return answer;
                    }

                    var opData = {
                        name: operation.getAttribute("name") || "Unknown",
                        inputs: createMessageData(input),
                        outputs: createMessageData(output)
                    };
                    service.operations.push(opData);
                });
            });
            Core.$apply($scope);
        }
    }
    API.WsdlViewController = WsdlViewController;
})(API || (API = {}));
/**
* API plugin for browsing WSDL and WADL
*
* @module API
* @main API
*/
var API;
(function (API) {
    var pluginName = 'api';
    angular.module(pluginName, ['bootstrap', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/api/wsdl', { templateUrl: 'app/api/html/wsdl.html' }).when('/api/wadl', { templateUrl: 'app/api/html/wadl.html' });
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry['api'] = layoutFull;
        /*
        helpRegistry.addUserDoc('log', 'app/wsdl/doc/help.md', () => {
        return workspace.treeContainsDomainAndProperties('org.fusesource.insight', {type: 'LogQuery'});
        });
        */
    });

    hawtioPluginLoader.addModule(pluginName);
})(API || (API = {}));
/**
* @module RBAC
* @main RBAC
*/
var RBAC;
(function (RBAC) {
    RBAC.pluginName = "hawtioRbac";
    RBAC.log = Logger.get("RBAC");
    RBAC._module = angular.module(RBAC.pluginName, ["hawtioCore"]);

    RBAC._module.factory('rbacTasks', function (postLoginTasks, jolokia) {
        postLoginTasks.addTask("FetchJMXSecurityMBeans", function () {
            jolokia.request({
                type: 'search',
                mbean: '*:type=security,area=jmx,*'
            }, onSuccess(function (response) {
                var mbeans = response.value;
                var chosen = "";
                if (mbeans.length === 0) {
                    RBAC.log.info("Didn't discover any JMXSecurity mbeans, client-side role based access control is disabled");
                    return;
                } else if (mbeans.length === 1) {
                    chosen = mbeans.first();
                } else if (mbeans.length > 1) {
                    var picked = false;
                    mbeans.forEach(function (mbean) {
                        if (picked) {
                            return;
                        }
                        if (mbean.has("HawtioDummy")) {
                            return;
                        }
                        if (!mbean.has("rank=")) {
                            chosen = mbean;
                            picked = true;
                        }
                    });
                }
                RBAC.log.info("Using mbean ", chosen, " for client-side role based access control");
                RBAC.rbacTasks.initialize(chosen);
            }));
        });

        return RBAC.rbacTasks;
    });

    RBAC._module.factory('rbacACLMBean', function (rbacTasks) {
        return rbacTasks.getACLMBean();
    });

    RBAC._module.run(function (jolokia, rbacTasks, preLogoutTasks, workspace, $rootScope) {
        preLogoutTasks.addTask("resetRBAC", function () {
            RBAC.log.debug("Resetting RBAC tasks");
            rbacTasks.reset();
        });

        rbacTasks.addTask("init", function () {
            RBAC.log.info("Initializing role based access support using mbean: ", rbacTasks.getACLMBean());
        });

        // add info to the JMX tree if we have access to invoke on mbeans
        // or not
        rbacTasks.addTask("JMXTreePostProcess", function () {
            workspace.addTreePostProcessor(function (tree) {
                var mbeans = {};
                RBAC.flattenMBeanTree(mbeans, tree);
                var requests = [];
                angular.forEach(mbeans, function (value, key) {
                    if (!('canInvoke' in value)) {
                        requests.push({
                            type: 'exec',
                            mbean: rbacTasks.getACLMBean(),
                            operation: 'canInvoke(java.lang.String)',
                            arguments: [key]
                        });
                    }
                });
                var numResponses = 0;
                var maybeRedraw = function () {
                    numResponses = numResponses + 1;
                    if (numResponses >= requests.length) {
                        workspace.redrawTree();
                        Core.$apply($rootScope);
                    }
                };
                jolokia.request(requests, onSuccess(function (response) {
                    var mbean = response.request.arguments[0];
                    if (mbean) {
                        mbeans[mbean]['canInvoke'] = response.value;
                        var toAdd = "cant-invoke";
                        if (response.value) {
                            toAdd = "can-invoke";
                        }
                        mbeans[mbean]['addClass'] = RBAC.stripClasses(mbeans[mbean]['addClass']);
                        mbeans[mbean]['addClass'] = RBAC.addClass(mbeans[mbean]['addClass'], toAdd);
                        maybeRedraw();
                    }
                }, {
                    error: function (response) {
                        // silently ignore, but still track if we need to redraw
                        maybeRedraw();
                    }
                }));
            });
        });
    });
})(RBAC || (RBAC = {}));

hawtioPluginLoader.addModule(RBAC.pluginName);
/**
* @module RBAC
*/
var RBAC;
(function (RBAC) {
    function flattenMBeanTree(mbeans, tree) {
        if (!Core.isBlank(tree.objectName)) {
            mbeans[tree.objectName] = tree;
        }
        if (tree.children && tree.children.length > 0) {
            tree.children.forEach(function (child) {
                flattenMBeanTree(mbeans, child);
            });
        }
    }
    RBAC.flattenMBeanTree = flattenMBeanTree;

    function stripClasses(css) {
        if (Core.isBlank(css)) {
            return css;
        }
        var parts = css.split(" ");
        var answer = [];
        parts.forEach(function (part) {
            if (part !== "can-invoke" || part !== "cant-invoke") {
                answer.push(part);
            }
        });
        return answer.join(" ").trim();
    }
    RBAC.stripClasses = stripClasses;

    function addClass(css, _class) {
        if (Core.isBlank(css)) {
            return _class;
        }
        var parts = css.split(" ");
        parts.push(_class);
        return parts.unique().join(" ").trim();
    }
    RBAC.addClass = addClass;
})(RBAC || (RBAC = {}));
/**
* @module RBAC
*/
/// <reference path="../../core/js/tasks.ts"/>
var RBAC;
(function (RBAC) {
    var RBACTasksImpl = (function (_super) {
        __extends(RBACTasksImpl, _super);
        function RBACTasksImpl() {
            _super.apply(this, arguments);
            this.ACLMBean = null;
        }
        RBACTasksImpl.prototype.initialize = function (mbean) {
            this.ACLMBean = mbean;
            _super.prototype.execute.call(this);
        };

        RBACTasksImpl.prototype.getACLMBean = function () {
            return this.ACLMBean;
        };
        return RBACTasksImpl;
    })(Core.TasksImpl);
    RBAC.RBACTasksImpl = RBACTasksImpl;

    RBAC.rbacTasks = new RBAC.RBACTasksImpl();
})(RBAC || (RBAC = {}));
/**
* @module Source
*/
var Source;
(function (Source) {
    function IndexController($scope, $location, $routeParams, workspace, jolokia) {
        $scope.pageId = Wiki.pageId($routeParams, $location);
        $scope.mavenCoords = $routeParams["mavenCoords"];
        var fileName = $scope.pageId;
        if (fileName === '/') {
            fileName = undefined;
        }

        $scope.loadingMessage = "Loading source code from artifacts <b>" + $scope.mavenCoords + "</b>";

        createBreadcrumbs();

        $scope.setFileName = function (breadcrumb) {
            fileName = Core.trimLeading(breadcrumb.fileName, "/");
            fileName = Core.trimLeading(fileName, "/");
            console.log("selected fileName '" + fileName + "'");
            createBreadcrumbs();
            filterFileNames();
        };

        $scope.$watch('workspace.tree', function (newValue, oldValue) {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(maybeUpdateView, 50);
            }
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(maybeUpdateView, 50);
        });

        function filterFileNames() {
            if (fileName) {
                $scope.sourceFiles = $scope.allSourceFiles.filter(function (n) {
                    return n && n.startsWith(fileName);
                }).map(function (n) {
                    return n.substring(fileName.length + 1);
                }).filter(function (n) {
                    return n;
                });
            } else {
                $scope.sourceFiles = $scope.allSourceFiles;
            }
        }

        $scope.sourceLinks = function (aFile) {
            var name = aFile;
            var paths = null;
            var idx = aFile.lastIndexOf('/');
            if (idx > 0) {
                name = aFile.substring(idx + 1);
                paths = aFile.substring(0, idx);
            }
            var answer = "";
            var fullName = fileName || "";
            if (paths) {
                angular.forEach(paths.split("/"), function (path) {
                    if (fullName) {
                        fullName += "/";
                    }
                    fullName += path;
                    answer += "<a href='#/source/index/" + $scope.mavenCoords + "/" + fullName + "'>" + path + "</a>/";
                });
            }
            answer += "<a href='#/source/view/" + $scope.mavenCoords + "/" + fullName + "/" + name + "'>" + name + "</a>";
            return answer;
        };

        function createBreadcrumbs() {
            $scope.breadcrumbs = Source.createBreadcrumbLinks($scope.mavenCoords, fileName);
            angular.forEach($scope.breadcrumbs, function (breadcrumb) {
                breadcrumb.active = false;
            });
            $scope.breadcrumbs.last().active = true;
        }

        function viewContents(response) {
            if (response) {
                $scope.allSourceFiles = response.split("\n").map(function (n) {
                    return n.trim();
                }).filter(function (n) {
                    return n;
                });
            } else {
                $scope.allSourceFiles = [];
            }
            filterFileNames();
            $scope.loadingMessage = null;
            Core.$apply($scope);
        }

        function updateView() {
            if (!$scope.mavenCoords) {
                return;
            }
            var mbean = Source.getInsightMBean(workspace);
            Source.log.debug("In update view, mbean: ", mbean);
            if (mbean) {
                jolokia.execute(mbean, "getSource", $scope.mavenCoords, null, "/", {
                    success: viewContents,
                    error: function (response) {
                        Source.log.error("Failed to download the source code for the maven artifact: ", $scope.mavenCoords);
                        Source.log.info("Stack trace: ", response.stacktrace);
                        $scope.loadingMessage = "Could not download index, please see console for details";
                        Core.$apply($scope);
                    }
                });
            }
        }

        var maybeUpdateView = Core.throttled(updateView, 1000);
        setTimeout(maybeUpdateView, 50);
    }
    Source.IndexController = IndexController;
})(Source || (Source = {}));
/**
* @module Source
* @main Source
*/
var Source;
(function (Source) {
    var pluginName = 'source';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'wiki']).config(function ($routeProvider) {
        $routeProvider.when('/source/index/:mavenCoords', { templateUrl: 'app/source/html/index.html' }).when('/source/index/:mavenCoords/*page', { templateUrl: 'app/source/html/index.html' }).when('/source/view/:mavenCoords/class/:className/*page', { templateUrl: 'app/source/html/source.html' }).when('/source/view/:mavenCoords/*page', { templateUrl: 'app/source/html/source.html' }).when('/source/javadoc/:mavenCoords/*page', { templateUrl: 'app/source/html/javadoc.html' });
    }).run(function ($location, workspace, viewRegistry, jolokia, localStorage, layoutFull, helpRegistry) {
        viewRegistry['source'] = layoutFull;
        helpRegistry.addUserDoc('source', 'app/source/doc/help.md');
    });

    hawtioPluginLoader.addModule(pluginName);
})(Source || (Source = {}));
/**
* @module Source
*/
var Source;
(function (Source) {
    function JavaDocController($scope, $location, $routeParams, workspace, fileExtensionTypeRegistry, jolokia) {
        $scope.pageId = Wiki.pageId($routeParams, $location);
        var mavenCoords = $routeParams["mavenCoords"];
        var fileName = $scope.pageId;

        $scope.loadingMessage = "Loading javadoc code for file <b>" + fileName + "</b> from artifacts <b>" + mavenCoords + "</b>";

        $scope.breadcrumbs = [];

        // TODO load breadcrumbs
        // $scope.breadcrumbs.push({href: "#" + loc, name: name});
        $scope.$watch('workspace.tree', function () {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(updateView, 50);
            }
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateView, 50);
        });

        function viewContents(response) {
            $scope.source = response;
            $scope.loadingMessage = null;
            if (!response) {
                var time = new Date().getTime();
                if (!$scope.lastErrorTime || time - $scope.lastErrorTime > 3000) {
                    $scope.lastErrorTime = time;
                    notification("error", "Could not download the source code for the maven artifacts: " + mavenCoords);
                }
            }
            Core.$apply($scope);
        }

        function updateView() {
            var mbean = Source.getInsightMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "getJavaDoc", mavenCoords, fileName, onSuccess(viewContents));
            }
        }
    }
    Source.JavaDocController = JavaDocController;
})(Source || (Source = {}));
/**
* @module Source
*/
var Source;
(function (Source) {
    function SourceController($scope, $location, $routeParams, workspace, fileExtensionTypeRegistry, jolokia) {
        $scope.pageId = Wiki.pageId($routeParams, $location);
        $scope.format = Wiki.fileFormat($scope.pageId, fileExtensionTypeRegistry);
        var lineNumber = $location.search()["line"] || 1;
        var mavenCoords = $routeParams["mavenCoords"];
        var className = $routeParams["className"] || "";
        var fileName = $scope.pageId || "/";
        var classNamePath = className.replace(/\./g, '/');

        $scope.loadingMessage = "Loading source code for class <b>" + className + "</b> from artifacts <b>" + mavenCoords + "</b>";

        //console.log("Source format is " + $scope.format + " line " + lineNumber + " className " + className + " file " + fileName);
        $scope.breadcrumbs = [];

        var idx = fileName.lastIndexOf('/');
        var path = "/";
        var name = fileName;
        if (idx > 0) {
            path = fileName.substring(0, idx);
            name = fileName.substring(idx + 1);
        } else if (className && className.indexOf('.') > 0) {
            path = classNamePath;
            idx = path.lastIndexOf('/');
            if (idx > 0) {
                name = path.substring(idx + 1);
                path = path.substring(0, idx);
            }
        }
        $scope.breadcrumbs = Source.createBreadcrumbLinks(mavenCoords, path);
        $scope.breadcrumbs.push({ href: $location.url(), name: name, active: true });

        $scope.javaDocLink = function () {
            var path = classNamePath;
            if (!path && fileName && fileName.endsWith(".java")) {
                path = fileName.substring(0, fileName.length - 5);
            }
            if (path) {
                return "javadoc/" + mavenCoords + "/" + path + ".html";
            }
            return null;
        };

        function updateLineSelection() {
            var codeMirror = $scope.codeMirror;
            if (codeMirror && lineNumber) {
                var line = lineNumber - 1;
                var lineText = codeMirror.getLine(line);
                var endChar = (lineText) ? lineText.length : 1000;
                var start = { line: line, ch: 0 };
                var end = { line: line, ch: endChar };
                codeMirror.scrollIntoView(start);
                codeMirror.setCursor(start);
                codeMirror.setSelection(start, end);
                codeMirror.refresh();
                codeMirror.focus();
            }
        }

        $scope.$watch('workspace.tree', function (oldValue, newValue) {
            if (!$scope.git && Git.getGitMBean(workspace)) {
                // lets do this asynchronously to avoid Error: $digest already in progress
                //console.log("Reloading the view as we now seem to have a git mbean!");
                setTimeout(maybeUpdateView, 50);
            }
        });

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(maybeUpdateView, 50);
        });

        function viewContents(response) {
            if (response) {
                Source.log.debug("Downloaded file for the maven artifact: " + mavenCoords);
                $scope.source = response;
                $scope.loadingMessage = null;
            } else {
                // we could not download the source code
                $scope.source = null;
                $scope.loadingMessage = "Cannot download file, please see logging console for details.";
                Source.log.error("Failed to download the source code for the Maven artifact: ", mavenCoords);
            }
            Core.$apply($scope);

            // lets update the line selection asynchronously to check we've properly loaded by now
            setTimeout(updateLineSelection, 100);
        }

        function updateView() {
            var mbean = Source.getInsightMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "getSource", mavenCoords, className, fileName, {
                    success: viewContents,
                    error: function (response) {
                        Source.log.error("Failed to download the source code for the Maven artifact: ", mavenCoords);
                        Source.log.info("Stack trace: ", response.stacktrace);
                        $scope.loadingMessage = "Cannot not download file, please see logging console for details.";
                        Core.$apply($scope);
                    }
                });
            }
        }

        var maybeUpdateView = Core.throttled(updateView, 1000);
        setTimeout(maybeUpdateView, 50);
    }
    Source.SourceController = SourceController;
})(Source || (Source = {}));
/**
* @module Source
*/
var Source;
(function (Source) {
    Source.log = Logger.get("Source");

    /**
    * @method getInsightMBean
    * @for Source
    * @param {Core.Workspace} workspace
    * @returns {*}
    */
    function getInsightMBean(workspace) {
        var mavenStuff = workspace.mbeanTypesToDomain["LogQuery"] || {};
        var insight = mavenStuff["org.fusesource.insight"] || mavenStuff["io.fabric8.insight"] || {};
        var mbean = insight.objectName;
        return mbean;
    }
    Source.getInsightMBean = getInsightMBean;

    /**
    * @method createBreadcrumbLinks
    * @for Source
    * @param {String} mavenCoords
    * @param {pathName} pathName
    * @returns {Array}
    */
    function createBreadcrumbLinks(mavenCoords, pathName) {
        var linkPrefix = "#/source/index/" + mavenCoords;
        var answer = [{ href: linkPrefix, name: "root" }];
        if (pathName) {
            var pathNames = pathName.split("/");
            var fullPath = "";
            angular.forEach(pathNames, function (path) {
                fullPath += "/" + path;
                var href = linkPrefix + fullPath;
                if (!path.isBlank()) {
                    answer.push({ href: href, name: path || "/", fileName: "/" + fullPath });
                }
            });
        }
        return answer;
    }
    Source.createBreadcrumbLinks = createBreadcrumbLinks;
})(Source || (Source = {}));
var Camel;
(function (Camel) {
    function PropertiesController($scope, workspace) {
        $scope.viewTemplate = null;
        $scope.schema = _apacheCamelModel;

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateData, 50);
        });

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;
            updateData();
        });

        function updateData() {
            var routeXmlNode = Camel.getSelectedRouteNode(workspace);
            $scope.nodeData = Camel.getRouteNodeJSON(routeXmlNode);

            if (routeXmlNode) {
                var nodeName = routeXmlNode.nodeName;
                $scope.model = Camel.getCamelSchema(nodeName);

                if ($scope.model) {
                    console.log("data is: " + JSON.stringify($scope.nodeData, null, "  "));
                    console.log("model schema is: " + JSON.stringify($scope.model, null, "  "));

                    $scope.viewTemplate = "app/camel/html/nodePropertiesView.html";
                }
            }
        }
    }
    Camel.PropertiesController = PropertiesController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function AttributesToolBarController($scope, workspace, jolokia) {
        $scope.deleteDialog = false;

        $scope.start = function () {
            $scope.invokeSelectedMBeans(function (item) {
                return Camel.isState(item, "suspend") ? "resume()" : "start()";
            });
        };

        $scope.pause = function () {
            $scope.invokeSelectedMBeans("suspend()");
        };

        $scope.stop = function () {
            $scope.invokeSelectedMBeans("stop()", function () {
                // lets navigate to the parent folder!
                // as this will be going way
                workspace.removeAndSelectParentNode();
            });
        };

        /*
        * Only for routes!
        */
        $scope.delete = function () {
            $scope.invokeSelectedMBeans("remove()", function () {
                // force a reload of the tree
                $scope.workspace.operationCounter += 1;
                Core.$apply($scope);
            });
        };

        $scope.anySelectionHasState = function (state) {
            var selected = $scope.selectedItems || [];
            return selected.length && selected.any(function (s) {
                return Camel.isState(s, state);
            });
        };

        $scope.everySelectionHasState = function (state) {
            var selected = $scope.selectedItems || [];
            return selected.length && selected.every(function (s) {
                return Camel.isState(s, state);
            });
        };
    }
    Camel.AttributesToolBarController = AttributesToolBarController;
})(Camel || (Camel = {}));
/**
* @module Camel
*/
var Camel;
(function (Camel) {
    /**
    * Define the default categories for endpoints and map them to endpoint names
    * @property
    * @for Camel
    * @type {ObjecT}
    */
    Camel.endpointCategories = {
        bigdata: {
            label: "Big Data",
            endpoints: ["hdfs", "hbase", "lucene", "solr"],
            endpointIcon: "/img/icons/camel/endpointRepository24.png"
        },
        database: {
            label: "Database",
            endpoints: ["couchdb", "elasticsearch", "hbase", "jdbc", "jpa", "hibernate", "mongodb", "mybatis", "sql"],
            endpointIcon: "/img/icons/camel/endpointRepository24.png"
        },
        cloud: {
            label: "Cloud",
            endpoints: [
                "aws-cw", "aws-ddb", "aws-sdb", "aws-ses", "aws-sns", "aws-sqs", "aws-s3",
                "gauth", "ghhtp", "glogin", "gtask",
                "jclouds"]
        },
        core: {
            label: "Core",
            endpoints: ["bean", "direct", "seda"]
        },
        messaging: {
            label: "Messaging",
            endpoints: ["jms", "activemq", "amqp", "cometd", "cometds", "mqtt", "netty", "vertx", "websocket"],
            endpointIcon: "/img/icons/camel/endpointQueue24.png"
        },
        mobile: {
            label: "Mobile",
            endpoints: ["apns"]
        },
        sass: {
            label: "SaaS",
            endpoints: ["salesforce", "sap-netweaver"]
        },
        social: {
            label: "Social",
            endpoints: ["atom", "facebook", "irc", "ircs", "rss", "smpp", "twitter", "weather"]
        },
        storage: {
            label: "Storage",
            endpointIcon: "/img/icons/camel/endpointFolder24.png",
            endpoints: ["file", "ftp", "sftp", "scp", "jsch"]
        },
        template: {
            label: "Templating",
            endpoints: ["freemarker", "velocity", "xquery", "xslt", "scalate", "string-template"]
        }
    };

    /**
    * Maps endpoint names to a category object
    * @property
    * @for Camel
    * @type {ObjecT}
    */
    Camel.endpointToCategory = {};

    Camel.endpointIcon = "/img/icons/camel/endpoint24.png";

    /**
    *  specify custom label & icon properties for endpoint names
    * @property
    * @for Camel
    * @type {ObjecT}
    */
    Camel.endpointConfigurations = {
        drools: {
            icon: "/img/icons/camel/endpointQueue24.png"
        },
        quartz: {
            icon: "/img/icons/camel/endpointTimer24.png"
        },
        facebook: {
            icon: "/img/icons/camel/endpoints/facebook24.jpg"
        },
        salesforce: {
            icon: "/img/icons/camel/endpoints/salesForce24.png"
        },
        sap: {
            icon: "/img/icons/camel/endpoints/SAPe24.png"
        },
        "sap-netweaver": {
            icon: "/img/icons/camel/endpoints/SAPNetweaver24.jpg"
        },
        timer: {
            icon: "/img/icons/camel/endpointTimer24.png"
        },
        twitter: {
            icon: "/img/icons/camel/endpoints/twitter24.png"
        },
        weather: {
            icon: "/img/icons/camel/endpoints/weather24.jpg"
        }
    };

    /**
    * Define the default form configurations
    * @property
    * @for Camel
    * @type {ObjecT}
    */
    Camel.endpointForms = {
        file: {
            tabs: {
                //'Core': ['key', 'value'],
                'Options': ['*']
            }
        },
        activemq: {
            tabs: {
                'Connection': ['clientId', 'transacted', 'transactedInOut', 'transactionName', 'transactionTimeout'],
                'Producer': ['timeToLive', 'priority', 'allowNullBody', 'pubSubNoLocal', 'preserveMessageQos'],
                'Consumer': ['concurrentConsumers', 'acknowledgementModeName', 'selector', 'receiveTimeout'],
                'Reply': ['replyToDestination', 'replyToDeliveryPersistent', 'replyToCacheLevelName', 'replyToDestinationSelectorName'],
                'Options': ['*']
            }
        }
    };

    Camel.endpointForms["jms"] = Camel.endpointForms.activemq;

    angular.forEach(Camel.endpointCategories, function (category, catKey) {
        category.id = catKey;
        angular.forEach(category.endpoints, function (endpoint) {
            Camel.endpointToCategory[endpoint] = category;
        });
    });

    /**
    * Override the EIP pattern tabs...
    * @property
    * @for Camel
    * @type {ObjecT}
    */
    var camelModelTabExtensions = {
        route: {
            'Overview': ['id', 'description'],
            'Advanced': ['*']
        }
    };

    function getEndpointIcon(endpointName) {
        var value = Camel.getEndpointConfig(endpointName, null);
        var answer = Core.pathGet(value, ["icon"]);
        if (!answer) {
            var category = getEndpointCategory(endpointName);
            answer = Core.pathGet(category, ["endpointIcon"]);
        }
        return answer || Camel.endpointIcon;
    }
    Camel.getEndpointIcon = getEndpointIcon;

    function getEndpointConfig(endpointName, category) {
        var answer = Camel.endpointConfigurations[endpointName];
        if (!answer) {
            answer = {};
            Camel.endpointConfigurations[endpointName] = answer;
        }
        if (!answer.label) {
            answer.label = endpointName;
        }
        if (!answer.icon) {
            answer.icon = Core.pathGet(category, ["endpointIcon"]) || Camel.endpointIcon;
        }
        if (!answer.category) {
            answer.category = category;
        }
        return answer;
    }
    Camel.getEndpointConfig = getEndpointConfig;

    function getEndpointCategory(endpointName) {
        return Camel.endpointToCategory[endpointName] || Camel.endpointCategories.core;
    }
    Camel.getEndpointCategory = getEndpointCategory;

    function getConfiguredCamelModel() {
        var schema = _apacheCamelModel;
        var definitions = schema["definitions"];
        if (definitions) {
            angular.forEach(camelModelTabExtensions, function (tabs, name) {
                var model = definitions[name];
                if (model) {
                    if (!model["tabs"]) {
                        model["tabs"] = tabs;
                    }
                }
            });
        }
        return schema;
    }
    Camel.getConfiguredCamelModel = getConfiguredCamelModel;

    function initEndpointChooserScope($scope, $location, localStorage, workspace, jolokia) {
        $scope.selectedComponentName = null;
        $scope.endpointParameters = {};
        $scope.endpointPath = "";

        $scope.schema = {
            definitions: {}
        };

        $scope.jolokia = jolokia;

        // lets see if we need to use a remote jolokia container
        var versionId = $scope.branch;
        var profileId = Fabric.pagePathToProfileId($scope.pageId);
        if (profileId && versionId) {
            Fabric.profileJolokia(jolokia, profileId, versionId, function (profileJolokia) {
                if (!profileJolokia) {
                    // TODO we should expose this to the user somewhere nicely!
                    Camel.log.info("No container is running for profile " + profileId + " and version " + versionId + " so using current container for endpoint completion");
                    profileJolokia = jolokia;
                }
                $scope.jolokia = profileJolokia;

                // force a reload
                $scope.profileWorkspace = null;
                $scope.loadEndpointNames();
            });
        }

        var silentOptions = { silent: true };

        $scope.$watch('workspace.selection', function () {
            $scope.loadEndpointNames();
        });

        $scope.$watch('selectedComponentName', function () {
            if ($scope.selectedComponentName !== $scope.loadedComponentName) {
                $scope.endpointParameters = {};
                $scope.loadEndpointSchema($scope.selectedComponentName);
                $scope.loadedComponentName = $scope.selectedComponentName;
            }
        });

        $scope.endpointCompletions = function (completionText) {
            var answer = null;
            var mbean = findCamelContextMBean();
            var componentName = $scope.selectedComponentName;
            var endpointParameters = {};
            if (mbean && componentName && completionText) {
                answer = $scope.jolokia.execute(mbean, 'completeEndpointPath', componentName, endpointParameters, completionText, onSuccess(null, silentOptions));
            }
            return answer || [];
        };

        $scope.loadEndpointNames = function () {
            $scope.componentNames = null;
            var mbean = findCamelContextMBean();
            if (mbean) {
                //$scope.jolokia.execute(mbean, 'findComponentNames', onSuccess(onComponents, silentOptions));
                $scope.jolokia.execute(mbean, 'findComponentNames', onSuccess(onComponents, { silent: true }));
                /*
                $scope.jolokia.execute(mbean, 'findComponentNames', onSuccess(onComponents, {error: function (response) {
                console.log("FAILED: " + response);
                }}));
                */
            } else {
                console.log("WARNING: No camel context mbean so cannot load component names");
            }
        };

        $scope.loadEndpointSchema = function (componentName) {
            var mbean = findCamelContextMBean();
            if (mbean && componentName && componentName !== $scope.loadedEndpointSchema) {
                $scope.selectedComponentName = componentName;
                $scope.jolokia.execute(mbean, 'componentParameterJsonSchema', componentName, onSuccess(onEndpointSchema, silentOptions));
            }
        };

        function onComponents(response) {
            $scope.componentNames = response;
            Camel.log.info("onComponents: " + response);
            $scope.hasComponentNames = $scope.componentNames ? true : false;
            Core.$apply($scope);
        }

        function onEndpointSchema(response) {
            if (response) {
                try  {
                    //console.log("got JSON: " + response);
                    var json = JSON.parse(response);
                    var endpointName = $scope.selectedComponentName;
                    configureEndpointSchema(endpointName, json);
                    $scope.endpointSchema = json;
                    $scope.schema.definitions[endpointName] = json;
                    $scope.loadedEndpointSchema = endpointName;
                    Core.$apply($scope);
                } catch (e) {
                    console.log("Failed to parse JSON " + e);
                    console.log("JSON: " + response);
                }
            }
        }

        function configureEndpointSchema(endpointName, json) {
            console.log("======== configuring schema for " + endpointName);
            var config = Camel.endpointForms[endpointName];
            if (config && json) {
                if (config.tabs) {
                    json.tabs = config.tabs;
                }
            }
        }

        function findCamelContextMBean() {
            var profileWorkspace = $scope.profileWorkspace;
            if (!profileWorkspace) {
                var removeJolokia = $scope.jolokia;
                if (removeJolokia) {
                    profileWorkspace = Core.createRemoteWorkspace(removeJolokia, $location, localStorage);
                    $scope.profileWorkspace = profileWorkspace;
                }
            }
            if (!profileWorkspace) {
                Camel.log.info("No profileWorkspace found so defaulting it to workspace for now");
                profileWorkspace = workspace;
            }

            // TODO we need to find the MBean for the CamelContext / Route we are editing!
            var componentName = $scope.selectedComponentName;
            var selectedCamelContextId;
            var selectedRouteId;
            if (angular.isDefined($scope.camelSelectionDetails)) {
                selectedCamelContextId = $scope.camelSelectionDetails.selectedCamelContextId;
                selectedRouteId = $scope.camelSelectionDetails.selectedRouteId;
            }

            console.log("==== componentName " + componentName + " selectedCamelContextId: " + selectedCamelContextId + " selectedRouteId: " + selectedRouteId);

            var contextsById = Camel.camelContextMBeansById(profileWorkspace);
            if (selectedCamelContextId) {
                var mbean = Core.pathGet(contextsById, [selectedCamelContextId, "mbean"]);
                if (mbean) {
                    return mbean;
                }
            }
            if (selectedRouteId) {
                var map = Camel.camelContextMBeansByRouteId(profileWorkspace);
                var mbean = Core.pathGet(map, [selectedRouteId, "mbean"]);
                if (mbean) {
                    return mbean;
                }
            }
            if (componentName) {
                var map = Camel.camelContextMBeansByComponentName(profileWorkspace);
                var mbean = Core.pathGet(map, [componentName, "mbean"]);
                if (mbean) {
                    return mbean;
                }
            }

            // NOTE we don't really know which camel context to pick, so lets just find the first one?
            var answer = null;
            angular.forEach(contextsById, function (details, id) {
                var mbean = details.mbean;
                if (!answer && mbean)
                    answer = mbean;
            });
            return answer;
            /*
            // we could be remote to lets query jolokia
            var results = $scope.jolokia.search("org.apache.camel:*,type=context", onSuccess(null));
            //var results = $scope.jolokia.search("org.apache.camel:*", onSuccess(null));
            if (results && results.length) {
            console.log("===== Got results: " + results);
            return results[0];
            }
            
            var mbean = Camel.getSelectionCamelContextMBean(profileWorkspace);
            if (!mbean && $scope.findProfileCamelContext) {
            // TODO as a hack for now lets just find any camel context we can
            var folder = Core.getMBeanTypeFolder(profileWorkspace, Camel.jmxDomain, "context");
            mbean = Core.pathGet(folder, ["objectName"]);
            }
            return mbean;
            */
        }
    }
    Camel.initEndpointChooserScope = initEndpointChooserScope;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function BreadcrumbBarController($scope, $routeParams, workspace, jolokia) {
        $scope.workspace = workspace;
        $scope.contextId = $routeParams["contextId"];
        $scope.endpointPath = $routeParams["endpointPath"];
        $scope.endpointName = tidyJmxName($scope.endpointPath);
        $scope.routeId = $routeParams["routeId"];

        $scope.treeViewLink = linkToTreeView();

        var defaultChildEntity = $scope.endpointPath ? "endpoints" : "routes";
        var childEntityToolTips = {
            "endpoints": "Camel Endpoint",
            "routes": "Camel Route"
        };

        /**
        * The array of breadcrumbs so that each item in the list of bookmarks can be switched for fast navigation and
        * we can easily render the navigation path
        */
        $scope.breadcrumbs = [
            {
                name: $scope.contextId,
                items: findContexts(),
                tooltip: "Camel Context"
            },
            {
                name: defaultChildEntity,
                items: findChildEntityTypes($scope.contextId),
                tooltip: "Entity inside a Camel Context"
            },
            {
                name: $scope.endpointName || tidyJmxName($scope.routeId),
                items: findChildEntityLinks($scope.contextId, currentChildEntity()),
                tooltip: childEntityToolTips[defaultChildEntity]
            }
        ];

        // lets find all the camel contexts
        function findContexts() {
            var answer = [];
            var rootFolder = Camel.getRootCamelFolder(workspace);
            if (rootFolder) {
                angular.forEach(rootFolder.children, function (contextFolder) {
                    var id = contextFolder.title;
                    if (id && id !== $scope.contextId) {
                        var name = id;
                        var link = createLinkToFirstChildEntity(id, currentChildEntity());
                        answer.push({
                            name: name,
                            tooltip: "Camel Context",
                            link: link
                        });
                    }
                });
            }
            return answer;
        }

        // lets find all the the child entities of a camel context
        function findChildEntityTypes(contextId) {
            var answer = [];
            angular.forEach(["endpoints", "routes"], function (childEntityName) {
                if (childEntityName && childEntityName !== currentChildEntity()) {
                    var link = createLinkToFirstChildEntity(contextId, childEntityName);
                    answer.push({
                        name: childEntityName,
                        tooltip: "Entity inside a Camel Context",
                        link: link
                    });
                }
            });
            return answer;
        }

        function currentChildEntity() {
            var answer = Core.pathGet($scope, ["breadcrumbs", "childEntity"]);
            return answer || defaultChildEntity;
        }

        /**
        * Based on the current child entity type, find the child links for the given context id and
        * generate a link to the first child; used when changing context or child entity type
        */
        function createLinkToFirstChildEntity(id, childEntityValue) {
            var links = findChildEntityLinks(id, childEntityValue);

            // TODO here we should switch to a default context view if there's no endpoints available...
            var link = links.length > 0 ? links[0].link : Camel.linkToBrowseEndpointFullScreen(id, "noEndpoints");
            return link;
        }

        function findChildEntityLinks(contextId, childEntityValue) {
            if ("endpoints" === childEntityValue) {
                return findEndpoints(contextId);
            } else {
                return findRoutes(contextId);
            }
        }

        // lets find all the endpoints for the given context id
        function findEndpoints(contextId) {
            var answer = [];
            var contextFolder = Camel.getCamelContextFolder(workspace, contextId);
            if (contextFolder) {
                var endpoints = (contextFolder["children"] || []).find(function (n) {
                    return "endpoints" === n.title;
                });
                if (endpoints) {
                    angular.forEach(endpoints.children, function (endpointFolder) {
                        var entries = endpointFolder ? endpointFolder.entries : null;
                        if (entries) {
                            var endpointPath = entries["name"];
                            if (endpointPath) {
                                var name = tidyJmxName(endpointPath);
                                var link = Camel.linkToBrowseEndpointFullScreen(contextId, endpointPath);

                                answer.push({
                                    contextId: contextId,
                                    path: endpointPath,
                                    name: name,
                                    tooltip: "Endpoint",
                                    link: link
                                });
                            }
                        }
                    });
                }
            }
            return answer;
        }

        // lets find all the routes for the given context id
        function findRoutes(contextId) {
            var answer = [];
            var contextFolder = Camel.getCamelContextFolder(workspace, contextId);
            if (contextFolder) {
                var folders = (contextFolder["children"] || []).find(function (n) {
                    return "routes" === n.title;
                });
                if (folders) {
                    angular.forEach(folders.children, function (folder) {
                        var entries = folder ? folder.entries : null;
                        if (entries) {
                            var routeId = entries["name"];
                            if (routeId) {
                                var name = tidyJmxName(routeId);
                                var link = Camel.linkToRouteDiagramFullScreen(contextId, routeId);
                                answer.push({
                                    contextId: contextId,
                                    path: routeId,
                                    name: name,
                                    tooltip: "Camel Route",
                                    link: link
                                });
                            }
                        }
                    });
                }
            }
            return answer;
        }

        /**
        * Creates a link to the tree view version of this view
        */
        function linkToTreeView() {
            var answer = null;
            if ($scope.contextId) {
                var node = null;
                var tab = null;
                if ($scope.endpointPath) {
                    tab = "browseEndpoint";
                    node = workspace.findMBeanWithProperties(Camel.jmxDomain, {
                        context: $scope.contextId,
                        type: "endpoints",
                        name: $scope.endpointPath
                    });
                } else if ($scope.routeId) {
                    tab = "routes";
                    node = workspace.findMBeanWithProperties(Camel.jmxDomain, {
                        context: $scope.contextId,
                        type: "routes",
                        name: $scope.routeId
                    });
                }
                var key = node ? node["key"] : null;
                if (key && tab) {
                    answer = "#/camel/" + tab + "?tab=camel&nid=" + key;
                }
            }
            return answer;
        }

        function tidyJmxName(jmxName) {
            return jmxName ? trimQuotes(jmxName) : jmxName;
        }
    }
    Camel.BreadcrumbBarController = BreadcrumbBarController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function EndpointController($scope, $location, localStorage, workspace, jolokia) {
        Camel.initEndpointChooserScope($scope, $location, localStorage, workspace, jolokia);

        $scope.workspace = workspace;
        $scope.message = "";

        $scope.createEndpoint = function (name) {
            var jolokia = workspace.jolokia;
            if (jolokia) {
                var mbean = Camel.getSelectionCamelContextMBean(workspace);
                if (mbean) {
                    $scope.message = "Creating endpoint " + name;
                    var operation = "createEndpoint(java.lang.String)";
                    jolokia.execute(mbean, operation, name, onSuccess(operationSuccess));
                } else {
                    notification("error", "Could not find the CamelContext MBean!");
                }
            }
        };

        $scope.createEndpointFromData = function () {
            if ($scope.selectedComponentName && $scope.endpointPath) {
                var name = $scope.selectedComponentName + "://" + $scope.endpointPath;
                console.log("Have endpoint data " + JSON.stringify($scope.endpointParameters));

                var params = "";
                angular.forEach($scope.endpointParameters, function (value, key) {
                    var prefix = params ? "&" : "";
                    params += prefix + key + "=" + value;
                });
                if (params) {
                    name += "?" + params;
                }

                // TODO use form data too for URIs parameters...
                $scope.createEndpoint(name);
            }
        };

        $scope.deleteEndpoint = function () {
            var jolokia = workspace.jolokia;
            var selection = workspace.selection;
            var entries = selection.entries;
            if (selection && jolokia && entries) {
                var domain = selection.domain;
                var brokerName = entries["BrokerName"];
                var name = entries["Destination"];
                var isQueue = "Topic" !== entries["Type"];
                if (domain && brokerName) {
                    var mbean = "" + domain + ":BrokerName=" + brokerName + ",Type=Broker";
                    $scope.message = "Deleting " + (isQueue ? "queue" : "topic") + " " + name;
                    var operation = "removeEndpoint(java.lang.String)";
                    jolokia.execute(mbean, operation, name, onSuccess(deleteSuccess));
                }
            }
        };

        function operationSuccess() {
            $scope.endpointName = "";
            $scope.workspace.operationCounter += 1;
            Core.$apply($scope);
            notification("success", $scope.message);
        }

        function deleteSuccess() {
            // lets set the selection to the parent
            if (workspace.selection) {
                var parent = Core.pathGet(workspace, ["selection", "parent"]);
                if (parent) {
                    $scope.workspace.updateSelectionNode(parent);
                }
            }
            $scope.workspace.operationCounter += 1;
            Core.$apply($scope);
            notification("success", $scope.message);
        }
    }
    Camel.EndpointController = EndpointController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function TreeHeaderController($scope, $location) {
        $scope.contextFilterText = '';

        $scope.$watch('contextFilterText', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.$emit("camel-contextFilterText", newValue);
            }
        });

        $scope.expandAll = function () {
            Tree.expandAll("#cameltree");
        };

        $scope.contractAll = function () {
            Tree.contractAll("#cameltree");
        };
    }
    Camel.TreeHeaderController = TreeHeaderController;

    function TreeController($scope, $location, $timeout, workspace, $rootScope) {
        $scope.contextFilterText = $location.search()["cq"];
        $scope.fullScreenViewLink = Camel.linkToFullScreenView(workspace);

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        var reloadThrottled = Core.throttled(reloadFunction, 500);

        $scope.$watch('workspace.tree', function () {
            reloadThrottled();
        });

        var reloadOnContextFilterThrottled = Core.throttled(function () {
            reloadFunction(function () {
                $("#camelContextIdFilter").focus();
            });
        }, 500);

        $scope.$watch('contextFilterText', function () {
            if ($scope.contextFilterText != $scope.lastContextFilterText) {
                $timeout(reloadOnContextFilterThrottled, 250);
            }
        });

        $rootScope.$on('camel-contextFilterText', function (event, value) {
            $scope.contextFilterText = value;
        });

        $scope.$on('jmxTreeUpdated', function () {
            reloadThrottled();
        });

        function reloadFunction(afterSelectionFn) {
            if (typeof afterSelectionFn === "undefined") { afterSelectionFn = null; }
            $scope.fullScreenViewLink = Camel.linkToFullScreenView(workspace);

            var children = [];
            var domainName = Camel.jmxDomain;

            // lets pull out each context
            var tree = workspace.tree;
            if (tree) {
                var rootFolder = new Folder("Camel Contexts");
                rootFolder.addClass = "org-apache-camel-context-folder";
                rootFolder.children = children;
                rootFolder.typeName = "context";
                rootFolder.key = "camelContexts";
                rootFolder.domain = domainName;

                var contextFilterText = $scope.contextFilterText;
                $scope.lastContextFilterText = contextFilterText;
                Camel.log.debug("Reloading the tree for filter: " + contextFilterText);
                var folder = tree.get(domainName);
                if (folder) {
                    angular.forEach(folder.children, function (value, key) {
                        var entries = value.map;
                        if (entries) {
                            var contextsFolder = entries["context"];
                            var routesNode = entries["routes"];
                            var endpointsNode = entries["endpoints"];
                            if (contextsFolder) {
                                var contextNode = contextsFolder.children[0];
                                if (contextNode) {
                                    var title = contextNode.title;
                                    if (!contextFilterText || (title && title.indexOf(contextFilterText) >= 0)) {
                                        var folder = new Folder(title);
                                        folder.addClass = "org-apache-camel-context";
                                        folder.domain = domainName;
                                        folder.objectName = contextNode.objectName;
                                        folder.entries = contextNode.entries;
                                        folder.typeName = contextNode.typeName;
                                        folder.key = contextNode.key;
                                        if (routesNode) {
                                            var routesFolder = new Folder("Routes");
                                            routesFolder.addClass = "org-apache-camel-routes-folder";
                                            routesFolder.parent = contextsFolder;
                                            routesFolder.children = routesNode.children;
                                            angular.forEach(routesFolder.children, function (n) {
                                                return n.addClass = "org-apache-camel-routes";
                                            });
                                            folder.children.push(routesFolder);
                                            routesFolder.typeName = "routes";
                                            routesFolder.key = routesNode.key;
                                            routesFolder.domain = routesNode.domain;
                                        }
                                        if (endpointsNode) {
                                            var endpointsFolder = new Folder("Endpoints");
                                            endpointsFolder.addClass = "org-apache-camel-endpoints-folder";
                                            endpointsFolder.parent = contextsFolder;
                                            endpointsFolder.children = endpointsNode.children;
                                            angular.forEach(endpointsFolder.children, function (n) {
                                                n.addClass = "org-apache-camel-endpoints";
                                                if (!Camel.getContextId(n)) {
                                                    n.entries["context"] = contextNode.entries["context"];
                                                }
                                            });
                                            folder.children.push(endpointsFolder);
                                            endpointsFolder.entries = contextNode.entries;
                                            endpointsFolder.typeName = "endpoints";
                                            endpointsFolder.key = endpointsNode.key;
                                            endpointsFolder.domain = endpointsNode.domain;
                                        }
                                        var jmxNode = new Folder("MBeans");

                                        // lets add all the entries which are not one context/routes/endpoints
                                        angular.forEach(entries, function (jmxChild, name) {
                                            if (name !== "context" && name !== "routes" && name !== "endpoints") {
                                                jmxNode.children.push(jmxChild);
                                            }
                                        });

                                        if (jmxNode.children.length > 0) {
                                            jmxNode.sortChildren(false);
                                            folder.children.push(jmxNode);
                                        }
                                        folder.parent = rootFolder;
                                        children.push(folder);
                                    }
                                }
                            }
                        }
                    });
                }

                var treeElement = $("#cameltree");
                Jmx.enableTree($scope, $location, workspace, treeElement, [rootFolder], true);

                // lets do this asynchronously to avoid Error: $digest already in progress
                setTimeout(function () {
                    updateSelectionFromURL();
                    if (angular.isFunction(afterSelectionFn)) {
                        afterSelectionFn();
                    }
                }, 50);
            }
        }

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURLAndAutoSelect($location, $("#cameltree"), function (first) {
                // use function to auto select first Camel context routes if there is only one Camel context
                var contexts = first.getChildren();
                if (contexts && contexts.length === 1) {
                    first = contexts[0];
                    first.expand(true);
                    var children = first.getChildren();
                    if (children && children.length) {
                        var routes = children[0];
                        if (routes.data.typeName === 'routes') {
                            first = routes;
                            return first;
                        }
                    }
                }
                return null;
            }, true);
            $scope.fullScreenViewLink = Camel.linkToFullScreenView(workspace);
        }
    }
    Camel.TreeController = TreeController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function RouteController($scope, $routeParams, $element, $timeout, workspace, $location, jolokia, localStorage) {
        var log = Logger.get("Camel");

        $scope.routes = [];
        $scope.routeNodes = {};

        $scope.contextId = $routeParams["contextId"];
        $scope.routeId = trimQuotes($routeParams["routeId"]);

        $scope.isJmxTab = !$routeParams["contextId"] || !$routeParams["routeId"];

        $scope.camelIgnoreIdForLabel = Camel.ignoreIdForLabel(localStorage);
        $scope.camelMaximumLabelWidth = Camel.maximumLabelWidth(localStorage);

        var updateRoutes = Core.throttled(doUpdateRoutes, 1000);

        // lets delay a little updating the routes to avoid timing issues where we've not yet
        // fully loaded the workspace and/or the XML model
        var delayUpdatingRoutes = 300;

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            $timeout(updateRoutes, delayUpdatingRoutes);
        });

        $scope.$watch('workspace.selection', function () {
            if ($scope.isJmxTab && workspace.moveIfViewInvalid())
                return;
            $timeout(updateRoutes, delayUpdatingRoutes);
        });

        $scope.$on('jmxTreeUpdated', function () {
            $timeout(updateRoutes, delayUpdatingRoutes);
        });

        $scope.$watch('nodeXmlNode', function () {
            if ($scope.isJmxTab && workspace.moveIfViewInvalid())
                return;
            $timeout(updateRoutes, delayUpdatingRoutes);
        });

        function doUpdateRoutes() {
            var routeXmlNode = null;
            if (!$scope.ignoreRouteXmlNode) {
                routeXmlNode = Camel.getSelectedRouteNode(workspace);
                if (!routeXmlNode) {
                    routeXmlNode = $scope.nodeXmlNode;
                }
                if (routeXmlNode && routeXmlNode.localName !== "route") {
                    var wrapper = document.createElement("route");
                    wrapper.appendChild(routeXmlNode.cloneNode(true));
                    routeXmlNode = wrapper;
                }
            }
            $scope.mbean = Camel.getSelectionCamelContextMBean(workspace);
            if (!$scope.mbean && $scope.contextId) {
                $scope.mbean = Camel.getCamelContextMBean(workspace, $scope.contextId);
            }
            if (routeXmlNode) {
                // lets show the remaining parts of the diagram of this route node
                $scope.nodes = {};
                var nodes = [];
                var links = [];
                $scope.processorTree = Camel.camelProcessorMBeansById(workspace);
                Camel.addRouteXmlChildren($scope, routeXmlNode, nodes, links, null, 0, 0);
                showGraph(nodes, links);
            } else if ($scope.mbean) {
                jolokia.request({ type: 'exec', mbean: $scope.mbean, operation: 'dumpRoutesAsXml()' }, onSuccess(populateTable));
            } else {
                log.info("No camel context bean! Selection: " + workspace.selection);
            }
        }

        var populateTable = function (response) {
            var data = response.value;

            // routes is the xml data of the routes
            $scope.routes = data;

            // nodes and routeNodes is the GUI nodes for the processors and routes shown in the diagram
            $scope.nodes = {};
            $scope.routeNodes = {};
            var nodes = [];
            var links = [];
            var selectedRouteId = $scope.routeId;
            if (!selectedRouteId) {
                selectedRouteId = Camel.getSelectedRouteId(workspace);
            }
            if (data) {
                var doc = $.parseXML(data);
                $scope.processorTree = Camel.camelProcessorMBeansById(workspace);
                Camel.loadRouteXmlNodes($scope, doc, selectedRouteId, nodes, links, getWidth());
                showGraph(nodes, links);
            } else {
                console.log("No data from route XML!");
            }
            Core.$apply($scope);
        };

        var postfix = " selected";

        function isSelected(node) {
            if (node) {
                var className = node.getAttribute("class");
                return className && className.endsWith(postfix);
            }
            return false;
        }

        function setSelected(node, flag) {
            var answer = false;
            if (node) {
                var className = node.getAttribute("class");
                var selected = className && className.endsWith(postfix);
                if (selected) {
                    className = className.substring(0, className.length - postfix.length);
                } else {
                    if (!flag) {
                        // no need to change!
                        return answer;
                    }
                    className = className + postfix;
                    answer = true;
                }
                node.setAttribute("class", className);
            }
            return answer;
        }

        function showGraph(nodes, links) {
            var canvasDiv = $($element);
            var width = getWidth();
            var height = getHeight();
            var svg = canvasDiv.children("svg")[0];
            $scope.graphData = Core.dagreLayoutGraph(nodes, links, width, height, svg);

            var gNodes = canvasDiv.find("g.node");
            gNodes.click(function () {
                var selected = isSelected(this);

                // lets clear all selected flags
                gNodes.each(function (idx, element) {
                    setSelected(element, false);
                });

                var cid = null;
                if (!selected) {
                    cid = this.getAttribute("data-cid");
                    setSelected(this, true);
                }
                $scope.$emit("camel.diagram.selectedNodeId", cid);
                Core.$apply($scope);
            });

            // TODO: https://github.com/hawtio/hawtio/issues/1261
            // we need some kind of right-click menu on d3
            // disabled code below as its work in progress
            /*      gNodes.dblclick(function() {
            //var allStats = $(doc).find("processorStat");
            var cid = this.getAttribute("data-cid");
            log.info("You double clicked " + cid);
            
            // find the node of the cid we clicked, and then find the folder in the Camel tree
            // to grab the folder key, which is the nid for the location in the JMX plugin to
            // view the processor mbean
            var node = $scope.nodes[cid];
            if (node) {
            var pid = node.elementId;
            
            var processors = camelProcessorMBeansById(workspace);
            var processor = processors[pid];
            if (processor) {
            var key = processor.key;
            // change url to jmx attributes so we can see the jmx stats for the selected processor
            $location.search("nid", key);
            var url = "/jmx/attributes";
            var href = Core.createHref($location, url);
            // change path to the jmx attributes page so we can see the processor mbean
            log.info("Changing to path: " + href);
            $location.url(href);
            Core.$apply($scope);
            }
            }
            });*/
            if ($scope.mbean) {
                Core.register(jolokia, $scope, {
                    type: 'exec', mbean: $scope.mbean,
                    operation: 'dumpRoutesStatsAsXml',
                    arguments: [true, true]
                }, onSuccess(statsCallback, { silent: true, error: false }));
            }
            $scope.$emit("camel.diagram.layoutComplete");
            return width;
        }

        function getWidth() {
            var canvasDiv = $($element);
            return canvasDiv.width();
        }

        function getHeight() {
            var canvasDiv = $($element);
            return Camel.getCanvasHeight(canvasDiv);
        }

        function statsCallback(response) {
            var data = response.value;
            if (data) {
                var doc = $.parseXML(data);

                var allStats = $(doc).find("routeStat");
                allStats.each(function (idx, stat) {
                    addTooltipToNode(true, stat);
                });

                var allStats = $(doc).find("processorStat");
                allStats.each(function (idx, stat) {
                    addTooltipToNode(false, stat);
                });

                // now lets try update the graph
                Core.dagreUpdateGraphData($scope.graphData);
            }

            function addTooltipToNode(isRoute, stat) {
                // we could have used a function instead of the boolean isRoute parameter (but sometimes that is easier)
                var id = stat.getAttribute("id");
                var completed = stat.getAttribute("exchangesCompleted");
                var tooltip = "";
                if (id && completed) {
                    var container = isRoute ? $scope.routeNodes : $scope.nodes;
                    var node = container[id];
                    if (!node) {
                        angular.forEach(container, function (value, key) {
                            if (!node && id === value.elementId) {
                                node = value;
                            }
                        });
                    }
                    if (node) {
                        var total = 0 + parseInt(completed);
                        var failed = stat.getAttribute("exchangesFailed");
                        if (failed) {
                            total += parseInt(failed);
                        }
                        var last = stat.getAttribute("lastProcessingTime");
                        var mean = stat.getAttribute("meanProcessingTime");
                        var min = stat.getAttribute("minProcessingTime");
                        var max = stat.getAttribute("maxProcessingTime");
                        tooltip = "last: " + last + " (ms)\nmean: " + mean + " (ms)\nmin: " + min + " (ms)\nmax: " + max + " (ms)";

                        node["counter"] = total;
                        var labelSummary = node["labelSummary"];
                        if (labelSummary) {
                            tooltip = labelSummary + "\n\n" + tooltip;
                        }
                        node["tooltip"] = tooltip;
                    } else {
                        // we are probably not showing the route for these stats
                        /*
                        var keys = Object.keys(container).sort();
                        log.info("Warning, could not find node for " + id + " when keys were: " + keys);
                        */
                    }
                }
            }
        }
    }
    Camel.RouteController = RouteController;
})(Camel || (Camel = {}));
/**
* @module Camel
*/
var Camel;
(function (Camel) {
    function PreferencesController($scope, localStorage) {
        Core.initPreferenceScope($scope, localStorage, {
            'camelIgnoreIdForLabel': {
                'value': false,
                'converter': Core.parseBooleanValue
            },
            'camelMaximumLabelWidth': {
                'value': Camel.defaultMaximumLabelWidth,
                'converter': parseInt
            },
            'camelMaximumTraceOrDebugBodyLength': {
                'value': Camel.defaultCamelMaximumTraceOrDebugBodyLength,
                'converter': parseInt
            }
        });
    }
    Camel.PreferencesController = PreferencesController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    Camel.jmsHeaderSchema = {
        definitions: {
            headers: {
                properties: {
                    JMSCorrelationID: {
                        type: "java.lang.String"
                    },
                    JMSDeliveryMode: {
                        "type": "string",
                        "enum": [
                            "PERSISTENT",
                            "NON_PERSISTENT"
                        ]
                    },
                    JMSDestination: {
                        type: "javax.jms.Destination"
                    },
                    JMSExpiration: {
                        type: "long"
                    },
                    JMSPriority: {
                        type: "int"
                    },
                    JMSReplyTo: {
                        type: "javax.jms.Destination"
                    },
                    JMSType: {
                        type: "java.lang.String"
                    },
                    JMSXGroupId: {
                        type: "java.lang.String"
                    },
                    AMQ_SCHEDULED_CRON: {
                        type: "java.lang.String"
                    },
                    AMQ_SCHEDULED_DELAY: {
                        type: "java.lang.String"
                    },
                    AMQ_SCHEDULED_PERIOD: {
                        type: "java.lang.String"
                    },
                    AMQ_SCHEDULED_REPEAT: {
                        type: "java.lang.String"
                    }
                }
            },
            "javax.jms.Destination": {
                type: "java.lang.String"
            }
        }
    };
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function BrowseEndpointController($scope, $routeParams, workspace, jolokia) {
        $scope.workspace = workspace;

        $scope.forwardDialog = new UI.Dialog();

        $scope.showMessageDetails = false;
        $scope.mode = 'text';

        $scope.gridOptions = Camel.createBrowseGridOptions();

        $scope.contextId = $routeParams["contextId"];
        $scope.endpointPath = $routeParams["endpointPath"];

        $scope.isJmxTab = !$routeParams["contextId"] || !$routeParams["endpointPath"];

        $scope.$watch('workspace.selection', function () {
            if ($scope.isJmxTab && workspace.moveIfViewInvalid())
                return;
            loadData();
        });

        // TODO can we share these 2 methods from activemq browse / camel browse / came trace?
        $scope.openMessageDialog = function (message) {
            var idx = Core.pathGet(message, ["rowIndex"]);
            $scope.selectRowIndex(idx);
            if ($scope.row) {
                $scope.mode = CodeEditor.detectTextFormat($scope.row.body);
                $scope.showMessageDetails = true;
            }
        };

        $scope.selectRowIndex = function (idx) {
            $scope.rowIndex = idx;
            var selected = $scope.gridOptions.selectedItems;
            selected.splice(0, selected.length);
            if (idx >= 0 && idx < $scope.messages.length) {
                $scope.row = $scope.messages[idx];
                if ($scope.row) {
                    selected.push($scope.row);
                }
            } else {
                $scope.row = null;
            }
        };

        $scope.forwardMessagesAndCloseForwardDialog = function () {
            var mbean = Camel.getSelectionCamelContextMBean(workspace);
            var selectedItems = $scope.gridOptions.selectedItems;
            var uri = $scope.endpointUri;
            if (mbean && uri && selectedItems && selectedItems.length) {
                //console.log("Creating a new endpoint called: " + uri + " just in case!");
                jolokia.execute(mbean, "createEndpoint(java.lang.String)", uri, onSuccess(intermediateResult));

                $scope.message = "Forwarded " + Core.maybePlural(selectedItems.length, "message" + " to " + uri);
                angular.forEach(selectedItems, function (item, idx) {
                    var callback = (idx + 1 < selectedItems.length) ? intermediateResult : operationSuccess;
                    var body = item.body;
                    var headers = item.headers;

                    //console.log("sending to uri " + uri + " headers: " + JSON.stringify(headers) + " body: " + body);
                    jolokia.execute(mbean, "sendBodyAndHeaders(java.lang.String, java.lang.Object, java.util.Map)", uri, body, headers, onSuccess(callback));
                });
            }
            $scope.forwardDialog.close();
        };

        $scope.endpointUris = function () {
            var endpointFolder = Camel.getSelectionCamelContextEndpoints(workspace);
            return (endpointFolder) ? endpointFolder.children.map(function (n) {
                return n.title;
            }) : [];
        };

        $scope.refresh = loadData;

        function intermediateResult() {
        }

        function operationSuccess() {
            if ($scope.messageDialog) {
                $scope.messageDialog.close();
            }
            $scope.gridOptions.selectedItems.splice(0);
            notification("success", $scope.message);
            setTimeout(loadData, 50);
        }

        function loadData() {
            var mbean = null;
            if ($scope.contextId && $scope.endpointPath) {
                var node = workspace.findMBeanWithProperties(Camel.jmxDomain, {
                    context: $scope.contextId,
                    type: "endpoints",
                    name: $scope.endpointPath
                });
                if (node) {
                    mbean = node.objectName;
                }
            }
            if (!mbean) {
                mbean = workspace.getSelectedMBeanName();
            }
            if (mbean) {
                Camel.log.info("MBean: " + mbean);
                var options = onSuccess(populateTable);
                jolokia.execute(mbean, 'browseAllMessagesAsXml(java.lang.Boolean)', true, options);
            }
        }

        function populateTable(response) {
            var data = [];
            if (angular.isString(response)) {
                // lets parse the XML DOM here...
                var doc = $.parseXML(response);
                var allMessages = $(doc).find("message");

                allMessages.each(function (idx, message) {
                    var messageData = Camel.createMessageFromXml(message);
                    data.push(messageData);
                });
            }
            $scope.messages = data;
            Core.$apply($scope);
        }
    }
    Camel.BrowseEndpointController = BrowseEndpointController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function DebugRouteController($scope, $element, workspace, jolokia) {
        $scope.camelMaximumTraceOrDebugBodyLength = Camel.maximumTraceOrDebugBodyLength(localStorage);

        // ignore the cached stuff in camel.ts as it seems to bork the node ids for some reason...
        $scope.ignoreRouteXmlNode = true;

        $scope.startDebugging = function () {
            setDebugging(true);
        };

        $scope.stopDebugging = function () {
            setDebugging(false);
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(reloadData, 50);
        });

        $scope.$on("camel.diagram.selectedNodeId", function (event, value) {
            $scope.selectedDiagramNodeId = value;
            updateBreakpointFlag();
        });

        $scope.$on("camel.diagram.layoutComplete", function (event, value) {
            updateBreakpointIcons();

            $($element).find("g.node").dblclick(function (n) {
                var id = this.getAttribute("data-cid");
                $scope.toggleBreakpoint(id);
            });
        });

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;
            reloadData();
        });

        $scope.toggleBreakpoint = function (id) {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean && id) {
                var method = isBreakpointSet(id) ? "removeBreakpoint" : "addBreakpoint";
                jolokia.execute(mbean, method, id, onSuccess(breakpointsChanged));
            }
        };

        $scope.addBreakpoint = function () {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean && $scope.selectedDiagramNodeId) {
                jolokia.execute(mbean, "addBreakpoint", $scope.selectedDiagramNodeId, onSuccess(breakpointsChanged));
            }
        };

        $scope.removeBreakpoint = function () {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean && $scope.selectedDiagramNodeId) {
                jolokia.execute(mbean, "removeBreakpoint", $scope.selectedDiagramNodeId, onSuccess(breakpointsChanged));
            }
        };

        $scope.resume = function () {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "resumeAll", onSuccess(clearStoppedAndResume));
            }
        };

        $scope.suspend = function () {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean) {
                jolokia.execute(mbean, "suspendAll", onSuccess(clearStoppedAndResume));
            }
        };

        $scope.step = function () {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            var stepNode = getStoppedBreakpointId();
            if (mbean && stepNode) {
                jolokia.execute(mbean, "stepBreakpoint(java.lang.String)", stepNode, onSuccess(clearStoppedAndResume));
            }
        };

        // TODO refactor into common code with trace.ts?
        // START
        $scope.messages = [];
        $scope.mode = 'text';

        $scope.messageDialog = new UI.Dialog();

        $scope.gridOptions = Camel.createBrowseGridOptions();
        $scope.gridOptions.selectWithCheckboxOnly = false;
        $scope.gridOptions.showSelectionCheckbox = false;
        $scope.gridOptions.multiSelect = false;
        $scope.gridOptions.afterSelectionChange = onSelectionChanged;
        $scope.gridOptions.columnDefs.push({
            field: 'toNode',
            displayName: 'To Node'
        });

        $scope.openMessageDialog = function (message) {
            var idx = Core.pathGet(message, ["rowIndex"]);
            $scope.selectRowIndex(idx);
            if ($scope.row) {
                var body = $scope.row.body;
                $scope.mode = angular.isString(body) ? CodeEditor.detectTextFormat(body) : "text";
                $scope.messageDialog.open();
            }
        };

        $scope.selectRowIndex = function (idx) {
            $scope.rowIndex = idx;
            var selected = $scope.gridOptions.selectedItems;
            selected.splice(0, selected.length);
            if (idx >= 0 && idx < $scope.messages.length) {
                $scope.row = $scope.messages[idx];
                if ($scope.row) {
                    selected.push($scope.row);
                }
            } else {
                $scope.row = null;
            }
            onSelectionChanged();
        };

        // END
        function onSelectionChanged() {
            var toNode = getStoppedBreakpointId();
            if (toNode) {
                // lets highlight the node in the diagram
                var nodes = getDiagramNodes();
                Camel.highlightSelectedNode(nodes, toNode);
            }
        }

        function reloadData() {
            $scope.debugging = false;
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean) {
                $scope.debugging = jolokia.getAttribute(mbean, "Enabled", onSuccess(null));
                if ($scope.debugging) {
                    jolokia.execute(mbean, "getBreakpoints", onSuccess(onBreakpoints));

                    // get the breakpoints...
                    $scope.graphView = "app/camel/html/routes.html";
                    $scope.tableView = "app/camel/html/browseMessages.html";

                    Core.register(jolokia, $scope, {
                        type: 'exec', mbean: mbean,
                        operation: 'getDebugCounter' }, onSuccess(onBreakpointCounter));
                } else {
                    $scope.graphView = null;
                    $scope.tableView = null;
                }
            }
        }

        function onBreakpointCounter(response) {
            var counter = response.value;
            if (counter && counter !== $scope.breakpointCounter) {
                $scope.breakpointCounter = counter;
                loadCurrentStack();
            }
        }

        /*
        * lets load current 'stack' of which breakpoints are active
        * and what is the current message content
        */
        function loadCurrentStack() {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean) {
                console.log("getting suspended breakpoints!");
                jolokia.execute(mbean, "getSuspendedBreakpointNodeIds", onSuccess(onSuspendedBreakpointNodeIds));
            }
        }

        function onSuspendedBreakpointNodeIds(response) {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            $scope.suspendedBreakpoints = response;
            $scope.stopped = response && response.length;
            var stopNodeId = getStoppedBreakpointId();
            if (mbean && stopNodeId) {
                jolokia.execute(mbean, 'dumpTracedMessagesAsXml', stopNodeId, onSuccess(onMessages));

                // lets update the diagram selection to the newly stopped node
                $scope.selectedDiagramNodeId = stopNodeId;
            }
            updateBreakpointIcons();
            Core.$apply($scope);
        }

        function onMessages(response) {
            console.log("onMessage! ");
            $scope.messages = [];
            if (response) {
                var xml = response;
                if (angular.isString(xml)) {
                    // lets parse the XML DOM here...
                    var doc = $.parseXML(xml);
                    var allMessages = $(doc).find("fabricTracerEventMessage");
                    if (!allMessages || !allMessages.length) {
                        // lets try find another element name
                        allMessages = $(doc).find("backlogTracerEventMessage");
                    }

                    allMessages.each(function (idx, message) {
                        var messageData = Camel.createMessageFromXml(message);
                        var toNode = $(message).find("toNode").text();
                        if (toNode) {
                            messageData["toNode"] = toNode;
                        }
                        $scope.messages.push(messageData);
                    });
                }
            } else {
                console.log("WARNING: dumpTracedMessagesAsXml() returned no results!");
            }

            // lets update the selection and selected row for the message detail view
            updateMessageSelection();
            console.log("has messages " + $scope.messages.length + " selected row " + $scope.row + " index " + $scope.rowIndex);
            Core.$apply($scope);
            updateBreakpointIcons();
        }

        function updateMessageSelection() {
            $scope.selectRowIndex($scope.rowIndex);
            if (!$scope.row && $scope.messageDialog.show) {
                // lets make a dummy empty row
                // so we can keep the detail view while resuming
                $scope.row = {
                    headers: {},
                    body: ""
                };
            }
        }

        function clearStoppedAndResume() {
            $scope.messages = [];
            $scope.suspendedBreakpoints = [];
            $scope.stopped = false;
            updateMessageSelection();
            Core.$apply($scope);
            updateBreakpointIcons();
        }

        /*
        * Return the current node id we are stopped at
        */
        function getStoppedBreakpointId() {
            var stepNode = null;
            var stepNodes = $scope.suspendedBreakpoints;
            if (stepNodes && stepNodes.length) {
                stepNode = stepNodes[0];
                if (stepNodes.length > 1 && isSuspendedAt($scope.selectedDiagramNodeId)) {
                    // TODO should consider we stepping from different nodes based on the call thread or selection?
                    stepNode = $scope.selectedDiagramNodeId;
                }
            }
            return stepNode;
        }

        /*
        * Returns true if the execution is currently suspended at the given node
        */
        function isSuspendedAt(nodeId) {
            return containsNodeId($scope.suspendedBreakpoints, nodeId);
        }

        function onBreakpoints(response) {
            $scope.breakpoints = response;
            updateBreakpointFlag();

            // update the breakpoint icons...
            var nodes = getDiagramNodes();
            if (nodes.length) {
                updateBreakpointIcons(nodes);
            }
            Core.$apply($scope);
        }

        /*
        * Returns true if there is a breakpoint set at the given node id
        */
        function isBreakpointSet(nodeId) {
            return containsNodeId($scope.breakpoints, nodeId);
        }

        function updateBreakpointFlag() {
            $scope.hasBreakpoint = isBreakpointSet($scope.selectedDiagramNodeId);
        }

        function containsNodeId(breakpoints, nodeId) {
            return nodeId && breakpoints && breakpoints.some(nodeId);
        }

        function getDiagramNodes() {
            var svg = d3.select("svg");
            return svg.selectAll("g .node");
        }

        var breakpointImage = url("/app/camel/doc/img/debug/breakpoint.gif");
        var suspendedBreakpointImage = url("/app/camel/doc/img/debug/breakpoint-suspended.gif");

        function updateBreakpointIcons(nodes) {
            if (typeof nodes === "undefined") { nodes = getDiagramNodes(); }
            nodes.each(function (object) {
                // add breakpoint icon
                var nodeId = object.cid;
                var thisNode = d3.select(this);
                var icons = thisNode.selectAll("image.breakpoint");
                var isSuspended = isSuspendedAt(nodeId);
                var isBreakpoint = isBreakpointSet(nodeId);
                if (isBreakpoint || isSuspended) {
                    var imageUrl = isSuspended ? suspendedBreakpointImage : breakpointImage;

                    // lets add an icon image if we don't already have one
                    if (!icons.length || !icons[0].length) {
                        thisNode.append("image").attr("xlink:href", function (d) {
                            return imageUrl;
                        }).attr("class", "breakpoint").attr("x", -12).attr("y", -20).attr("height", 24).attr("width", 24);
                    } else {
                        icons.attr("xlink:href", function (d) {
                            return imageUrl;
                        });
                    }
                } else {
                    icons.remove();
                }
            });
        }

        function breakpointsChanged(response) {
            reloadData();
            Core.$apply($scope);
        }

        function setDebugging(flag) {
            var mbean = Camel.getSelectionCamelDebugMBean(workspace);
            if (mbean) {
                var method = flag ? "enableDebugger" : "disableDebugger";
                var max = $scope.camelMaximumTraceOrDebugBodyLength;
                jolokia.setAttribute(mbean, "BodyMaxChars", max);
                jolokia.execute(mbean, method, onSuccess(breakpointsChanged));
            }
        }
    }
    Camel.DebugRouteController = DebugRouteController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function TypeConverterController($scope, $location, workspace, jolokia) {
        $scope.data = [];
        $scope.selectedMBean = null;

        $scope.mbeanAttributes = {};

        var columnDefs = [
            {
                field: 'from',
                displayName: 'From',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'to',
                displayName: 'To',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'data',
            displayFooter: true,
            displaySelectionCheckbox: false,
            canSelectRows: false,
            enableSorting: true,
            columnDefs: columnDefs,
            selectedItems: [],
            filterOptions: {
                filterText: ''
            }
        };

        function onAttributes(response) {
            var obj = response.value;
            if (obj) {
                $scope.mbeanAttributes = obj;

                // ensure web page is updated
                Core.$apply($scope);
            }
        }

        function onConverters(response) {
            var obj = response.value;
            if (obj) {
                var arr = [];
                for (var key in obj) {
                    var values = obj[key];
                    for (var v in values) {
                        arr.push({ from: key, to: v });
                    }
                }
                arr = arr.sortBy("from");
                $scope.data = arr;

                // okay we have the data then set the selected mbean which allows UI to display data
                $scope.selectedMBean = response.request.mbean;

                // ensure web page is updated
                Core.$apply($scope);
            }
        }

        $scope.renderIcon = function (state) {
            return Camel.iconClass(state);
        };

        $scope.disableStatistics = function () {
            if ($scope.selectedMBean) {
                jolokia.setAttribute($scope.selectedMBean, "StatisticsEnabled", false);
            }
        };

        $scope.enableStatistics = function () {
            if ($scope.selectedMBean) {
                jolokia.setAttribute($scope.selectedMBean, "StatisticsEnabled", true);
            }
        };

        $scope.resetStatistics = function () {
            if ($scope.selectedMBean) {
                jolokia.request({ type: 'exec', mbean: $scope.selectedMBean, operation: 'resetTypeConversionCounters' }, onSuccess(null, { silent: true }));
            }
        };

        function loadConverters() {
            console.log("Loading TypeConverter data...");
            var mbean = Camel.getSelectionCamelTypeConverter(workspace);
            if (mbean) {
                // grab attributes in real time
                var query = {
                    type: "read", mbean: mbean,
                    attribute: ["AttemptCounter", "FailedCounter", "HitCounter", "MissCounter", "NumberOfTypeConverters", "StatisticsEnabled"] };

                jolokia.request(query, onSuccess(onAttributes));

                scopeStoreJolokiaHandle($scope, jolokia, jolokia.register(onAttributes, query));

                // and list of converters
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listTypeConverters' }, onSuccess(onConverters));
            }
        }

        // load converters
        loadConverters();
    }
    Camel.TypeConverterController = TypeConverterController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function SourceController($scope, workspace) {
        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateRoutes, 50);
        });

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid())
                return;
            updateRoutes();
        });

        $scope.mode = 'xml';

        function getSource(routeXmlNode) {
            function removeCrappyHeaders(idx, e) {
                var answer = e.getAttribute("customId");
                if (e.nodeName === 'route') {
                    // always keep id on <route> element
                    answer = "true";
                }
                if (!answer || answer !== "true") {
                    e.removeAttribute("id");
                }

                // just always remove customId, _cid, and group
                e.removeAttribute("customId");
                e.removeAttribute("_cid");
                e.removeAttribute("group");
            }
            var copy = $(routeXmlNode).clone();
            copy.each(removeCrappyHeaders);
            copy.find("*").each(removeCrappyHeaders);
            var newNode = (copy && copy.length) ? copy[0] : routeXmlNode;
            return Core.xmlNodeToString(newNode);
        }

        function updateRoutes() {
            // did we select a single route
            var routeXmlNode = Camel.getSelectedRouteNode(workspace);
            if (routeXmlNode) {
                $scope.source = getSource(routeXmlNode);
                Core.$apply($scope);
            } else {
                // no then try to find the camel context and get all the routes code
                $scope.mbean = Camel.getSelectionCamelContextMBean(workspace);
                if (!$scope.mbean) {
                    // maybe the parent is the camel context folder (when we have selected the routes folder),
                    // then grab the object name from parent
                    var parent = Core.pathGet(workspace, ["selection", "parent"]);
                    if (parent && parent.title === "context") {
                        $scope.mbean = parent.children[0].objectName;
                    }
                }
                if ($scope.mbean) {
                    var jolokia = workspace.jolokia;
                    jolokia.request({ type: 'exec', mbean: $scope.mbean, operation: 'dumpRoutesAsXml()' }, onSuccess(populateTable));
                }
            }
        }

        var populateTable = function (response) {
            var data = response.value;
            var selectedRouteId = Camel.getSelectedRouteId(workspace);
            if (data && selectedRouteId) {
                var doc = $.parseXML(data);
                var routes = $(doc).find('route[id="' + selectedRouteId + '"]');
                if (routes && routes.length) {
                    var selectedRoute = routes[0];

                    // TODO turn into XML?
                    var routeXml = getSource(selectedRoute);
                    if (routeXml) {
                        data = routeXml;
                    }
                }
            }
            $scope.source = data;
            Core.$apply($scope);
        };

        var saveWorked = function () {
            notification("success", "Route updated!");

            // lets clear the cached route XML so we reload the new value
            Camel.clearSelectedRouteNode(workspace);
            updateRoutes();
        };

        $scope.saveRouteXml = function () {
            var routeXml = $scope.source;
            if (routeXml) {
                var decoded = decodeURIComponent(routeXml);
                Camel.log.debug("addOrUpdateRoutesFromXml xml decoded: " + decoded);
                var jolokia = workspace.jolokia;
                var mbean = Camel.getSelectionCamelContextMBean(workspace);
                if (mbean) {
                    jolokia.execute(mbean, "addOrUpdateRoutesFromXml(java.lang.String)", decoded, onSuccess(saveWorked));
                } else {
                    notification("error", "Could not find CamelContext MBean!");
                }
            }
        };
    }
    Camel.SourceController = SourceController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function SendMessageController($route, $scope, $element, $timeout, workspace, jolokia, localStorage, $location) {
        var log = Logger.get("Camel");

        log.info("Loaded page!");

        $scope.noCredentials = false;
        $scope.showChoose = false;
        $scope.profileFileNames = [];
        $scope.profileFileNameToProfileId = {};
        $scope.selectedFiles = {};
        $scope.container = {};

        // bind model values to search params...
        Core.bindModelToSearchParam($scope, $location, "tab", "subtab", "compose");
        Core.bindModelToSearchParam($scope, $location, "searchText", "q", "");

        // only reload the page if certain search parameters change
        Core.reloadWhenParametersChange($route, $scope, $location);

        $scope.checkCredentials = function () {
            $scope.noCredentials = (Core.isBlank(localStorage['activemqUserName']) || Core.isBlank(localStorage['activemqPassword']));
        };

        if ($location.path().has('activemq')) {
            $scope.localStorage = localStorage;
            $scope.$watch('localStorage.activemqUserName', $scope.checkCredentials);
            $scope.$watch('localStorage.activemqPassword', $scope.checkCredentials);
        }

        $scope.openPrefs = function () {
            $location.search('pref', 'ActiveMQ');
            $scope.$emit("hawtioOpenPrefs");
        };

        var LANGUAGE_FORMAT_PREFERENCE = "defaultLanguageFormat";
        var sourceFormat = workspace.getLocalStorage(LANGUAGE_FORMAT_PREFERENCE) || "javascript";
        $scope.message = "\n\n\n\n";

        // TODO Remove this if possible
        $scope.codeMirror = undefined;
        var options = {
            mode: {
                name: sourceFormat
            },
            // Quick hack to get the codeMirror instance.
            onChange: function (codeMirror) {
                if (!$scope.codeMirror) {
                    $scope.codeMirror = codeMirror;
                }
            }
        };
        $scope.codeMirrorOptions = CodeEditor.createEditorSettings(options);

        $scope.headers = [];

        $scope.addHeader = function () {
            $scope.headers.push({ name: "", value: "" });

            // lets set the focus to the last header
            if ($element) {
                $timeout(function () {
                    var lastHeader = $element.find("input.headerName").last();
                    lastHeader.focus();
                }, 100);
            }
        };

        $scope.removeHeader = function (header) {
            $scope.headers = $scope.headers.remove(header);
        };

        $scope.defaultHeaderNames = function () {
            var answer = [];

            function addHeaderSchema(schema) {
                angular.forEach(schema.definitions.headers.properties, function (value, name) {
                    answer.push(name);
                });
            }

            if (isJmsEndpoint()) {
                addHeaderSchema(Camel.jmsHeaderSchema);
            }
            if (isCamelEndpoint()) {
                addHeaderSchema(Camel.camelHeaderSchema);
            }
            return answer;
        };

        $scope.$watch('workspace.selection', function () {
            // if the current JMX selection does not support sending messages then lets redirect the page
            workspace.moveIfViewInvalid();

            if (Fabric.fabricCreated(workspace)) {
                loadProfileConfigurationFiles();
            }
        });

        /* save the sourceFormat in preferences for later
        * Note, this would be controller specific preferences and not the global, overriding, preferences */
        // TODO Use ng-selected="changeSourceFormat()" - Although it seemed to fire multiple times..
        $scope.$watch('codeMirrorOptions.mode.name', function (newValue, oldValue) {
            workspace.setLocalStorage(LANGUAGE_FORMAT_PREFERENCE, newValue);
        });

        var sendWorked = function () {
            $scope.message = "";
            notification("success", "Message sent!");
        };

        $scope.autoFormat = function () {
            setTimeout(function () {
                CodeEditor.autoFormatEditor($scope.codeMirror);
            }, 50);
        };

        $scope.sendMessage = function () {
            var body = $scope.message;
            doSendMessage(body, sendWorked);
        };

        function doSendMessage(body, onSendCompleteFn) {
            var selection = workspace.selection;
            if (selection) {
                var mbean = selection.objectName;
                if (mbean) {
                    var headers = null;
                    if ($scope.headers.length) {
                        headers = {};
                        angular.forEach($scope.headers, function (object) {
                            var key = object.name;
                            if (key) {
                                headers[key] = object.value;
                            }
                        });
                        log.info("About to send headers: " + JSON.stringify(headers));
                    }

                    var callback = onSuccess(onSendCompleteFn);
                    if (selection.domain === "org.apache.camel") {
                        var target = Camel.getContextAndTargetEndpoint(workspace);
                        var uri = target['uri'];
                        mbean = target['mbean'];
                        if (mbean && uri) {
                            if (headers) {
                                jolokia.execute(mbean, "sendBodyAndHeaders(java.lang.String, java.lang.Object, java.util.Map)", uri, body, headers, callback);
                            } else {
                                jolokia.execute(mbean, "sendStringBody(java.lang.String, java.lang.String)", uri, body, callback);
                            }
                        } else {
                            if (!mbean) {
                                notification("error", "Could not find CamelContext MBean!");
                            } else {
                                notification("error", "Failed to determine endpoint name!");
                            }
                            log.debug("Parsed context and endpoint: ", target);
                        }
                    } else {
                        var user = localStorage["activemqUserName"];
                        var pwd = localStorage["activemqPassword"];
                        if (headers) {
                            jolokia.execute(mbean, "sendTextMessage(java.util.Map, java.lang.String, java.lang.String, java.lang.String)", headers, body, user, pwd, callback);
                        } else {
                            jolokia.execute(mbean, "sendTextMessage(java.lang.String, java.lang.String, java.lang.String)", body, user, pwd, callback);
                        }
                    }
                }
            }
        }

        $scope.fileSelection = function () {
            var answer = [];
            angular.forEach($scope.selectedFiles, function (value, key) {
                if (value) {
                    answer.push(key);
                }
            });
            return answer;
        };

        $scope.sendSelectedFiles = function () {
            var filesToSend = $scope.fileSelection();
            var fileCount = filesToSend.length;
            var version = $scope.container.versionId || "1.0";

            function onSendFileCompleted(response) {
                if (filesToSend.length) {
                    var fileName = filesToSend.pop();
                    if (fileName) {
                        // lets load the file data...
                        var profile = $scope.profileFileNameToProfileId[fileName];
                        if (profile) {
                            var body = Fabric.getConfigFile(jolokia, version, profile, fileName);
                            if (!body) {
                                log.warn("No body for message " + fileName);
                                body = "";
                            }
                            doSendMessage(body, onSendFileCompleted);
                        }
                    }
                } else {
                    var text = Core.maybePlural(fileCount, "Message") + " sent!";
                    notification("success", text);
                }
            }

            // now lets start sending
            onSendFileCompleted(null);
        };

        function isCamelEndpoint() {
            // TODO check for the camel or if its an activemq endpoint
            return true;
        }

        function isJmsEndpoint() {
            // TODO check for the jms/activemq endpoint in camel or if its an activemq endpoint
            return true;
        }

        function loadProfileConfigurationFiles() {
            if (Fabric.fabricCreated(workspace)) {
                $scope.container = Fabric.getCurrentContainer(jolokia, ['versionId', 'profileIds']);
                jolokia.execute(Fabric.managerMBean, "currentContainerConfigurationFiles", onSuccess(onFabricConfigFiles));
            }
        }

        function onFabricConfigFiles(response) {
            $scope.profileFileNameToProfileId = response;
            $scope.profileFileNames = Object.keys(response).sort();
            $scope.showChoose = $scope.profileFileNames.length ? true : false;
            $scope.selectedFiles = {};
            Core.$apply($scope);
        }
    }
    Camel.SendMessageController = SendMessageController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function ProfileRouteController($scope, $location, workspace, jolokia) {
        $scope.data = [];
        $scope.calcManually = true;
        $scope.icons = {};
        $scope.selectedRouteId = "";

        var columnDefs = [
            {
                field: 'id',
                displayName: 'Id',
                cellTemplate: '<div class="ngCellText" ng-bind-html-unsafe="rowIcon(row.entity.id)"></div>',
                cellFilter: null,
                width: "**",
                resizable: true
            },
            {
                field: 'count',
                displayName: 'Count',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'last',
                displayName: 'Last',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'delta',
                displayName: 'Delta',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'mean',
                displayName: 'Mean',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'min',
                displayName: 'Min',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'max',
                displayName: 'Max',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'total',
                displayName: 'Total',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'self',
                displayName: 'Self',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.rowIcon = function (id) {
            var entry = $scope.icons[id];
            if (entry) {
                return entry.img + " " + id;
            } else {
                return id;
            }
        };

        $scope.gridOptions = {
            data: 'data',
            displayFooter: true,
            displaySelectionCheckbox: false,
            canSelectRows: false,
            enableSorting: false,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        var populateProfileMessages = function (response) {
            var updatedData = [];

            // its xml structure so we need to parse it
            var xml = response.value;
            if (angular.isString(xml)) {
                // lets parse the XML DOM here...
                var doc = $.parseXML(xml);

                var routeMessages = $(doc).find("routeStat");

                routeMessages.each(function (idx, message) {
                    var messageData = {
                        id: {},
                        count: {},
                        last: {},
                        delta: {},
                        mean: {},
                        min: {},
                        max: {},
                        total: {},
                        self: {}
                    };

                    // compare counters, as we only update if we have new data
                    messageData.id = message.getAttribute("id");

                    var total = 0;
                    total += +message.getAttribute("exchangesCompleted");
                    total += +message.getAttribute("exchangesFailed");
                    messageData.count = total;
                    messageData.last = message.getAttribute("lastProcessingTime");

                    // delta is only avail from Camel 2.11 onwards
                    var delta = message.getAttribute("deltaProcessingTime");
                    if (delta) {
                        messageData.delta = delta;
                    } else {
                        messageData.delta = 0;
                    }
                    messageData.mean = message.getAttribute("meanProcessingTime");
                    messageData.min = message.getAttribute("minProcessingTime");
                    messageData.max = message.getAttribute("maxProcessingTime");
                    messageData.total = message.getAttribute("totalProcessingTime");

                    // self is pre calculated from Camel 2.11 onwards
                    var self = message.getAttribute("selfProcessingTime");
                    if (self) {
                        messageData.self = self;
                    } else {
                        // we need to calculate this manually
                        $scope.calcManually = true;
                        messageData.self = "0";
                    }

                    updatedData.push(messageData);
                });

                var processorMessages = $(doc).find("processorStat");

                processorMessages.each(function (idx, message) {
                    var messageData = {
                        id: {},
                        count: {},
                        last: {},
                        delta: {},
                        mean: {},
                        min: {},
                        max: {},
                        total: {},
                        self: {}
                    };

                    messageData.id = message.getAttribute("id");
                    var total = 0;
                    total += +message.getAttribute("exchangesCompleted");
                    total += +message.getAttribute("exchangesFailed");
                    messageData.count = total;
                    messageData.last = message.getAttribute("lastProcessingTime");

                    // delta is only avail from Camel 2.11 onwards
                    var delta = message.getAttribute("deltaProcessingTime");
                    if (delta) {
                        messageData.delta = delta;
                    } else {
                        messageData.delta = 0;
                    }
                    messageData.mean = message.getAttribute("meanProcessingTime");
                    messageData.min = message.getAttribute("minProcessingTime");
                    messageData.max = message.getAttribute("maxProcessingTime");

                    // total time for processors is pre calculated as accumulated from Camel 2.11 onwards
                    var apt = message.getAttribute("accumulatedProcessingTime");
                    if (apt) {
                        messageData.total = apt;
                    } else {
                        messageData.total = "0";
                    }

                    // self time for processors is their total time
                    messageData.self = message.getAttribute("totalProcessingTime");

                    updatedData.push(messageData);
                });
            }

            // for Camel 2.10 or older we need to run through the data and calculate the self/total times manually
            if ($scope.calcManually) {
                // sort the data accordingly to order in the icons map
                updatedData.sort(function (e1, e2) {
                    var entry1 = $scope.icons[e1.id];
                    var entry2 = $scope.icons[e2.id];
                    if (entry1 && entry2) {
                        return entry1.index - entry2.index;
                    } else {
                        return 0;
                    }
                });

                var accTotal = 0;
                updatedData.reverse().forEach(function (data, idx) {
                    // update accTotal with self time
                    if (idx < updatedData.length - 1) {
                        // each processor should have the total updated with the accumulated total
                        accTotal += +data.self;
                        data.total = accTotal;
                    } else {
                        // the last row is the route, which should have self calculated as follows
                        data.self = +(data.total - accTotal);

                        // just to be safe we dont want negative values self value for the route
                        if (data.self < 0) {
                            data.self = 0;
                        }
                    }
                });

                // reverse back again
                updatedData.reverse();
            }

            // TODO: need a way to update data without flickering
            // if we do as below with the forEach then the data does not update
            // replace data with updated data
            $scope.data = updatedData;

            Core.$apply($scope);
        };

        // function to trigger reloading page
        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
            loadData();
        };

        $scope.$watch('workspace.tree', function () {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        });

        function initIdToIcon() {
            console.log("initializing id and icons");

            $scope.icons = {};
            var routeXml = Core.pathGet(workspace.selection, ["routeXmlNode"]);
            if (routeXml) {
                // add route id first
                var entry = {
                    img: "",
                    index: 0
                };
                entry.index = -1;
                entry.img = "<img src='img/icons/camel/camel_route.png'>";
                $scope.icons[$scope.selectedRouteId] = entry;

                // then each processor id and icons
                $(routeXml).find('*').each(function (idx, element) {
                    var id = element.getAttribute("id");
                    if (id) {
                        var entry = {
                            img: "",
                            index: 0
                        };
                        entry.index = idx;
                        var icon = Camel.getRouteNodeIcon(element);
                        if (icon) {
                            entry.img = "<img src='" + icon + "'>";
                        } else {
                            entry.img = "";
                        }
                        $scope.icons[id] = entry;
                    }
                });
            }
        }

        function loadData() {
            console.log("Loading Camel route profile data...");
            $scope.selectedRouteId = Camel.getSelectedRouteId(workspace);
            var routeMBean = Camel.getSelectionRouteMBean(workspace, $scope.selectedRouteId);
            console.log("Selected route is " + $scope.selectedRouteId);

            if (Camel.isCamelVersionEQGT(2, 11, workspace, jolokia)) {
                // this is Camel 2.11 or better so we dont need to calculate data manually
                console.log("Camel 2.11 or better detected");
                $scope.calcManually = false;
            } else {
                console.log("Camel 2.10 or older detected");
                $scope.calcManually = true;
            }

            initIdToIcon();
            console.log("Initialized icons, with " + $scope.icons.length + " icons");

            // schedule update the profile data, based on the configured interval
            // TOOD: the icons is not initialized the first time, for some reason, the routeXmlNode is empty/undefined
            // TODO: have cellFilter with bar grey-scale for highlighting the scales between the numbers
            // TODO: have the icons indent, there is some CSS ninja crack to do this
            var query = { type: 'exec', mbean: routeMBean, operation: 'dumpRouteStatsAsXml(boolean,boolean)', arguments: [false, true] };
            scopeStoreJolokiaHandle($scope, jolokia, jolokia.register(populateProfileMessages, query));
        }
    }
    Camel.ProfileRouteController = ProfileRouteController;
})(Camel || (Camel = {}));
var Core;
(function (Core) {
    var PageTitle = (function () {
        function PageTitle() {
            this.titleElements = [];
        }
        PageTitle.prototype.addTitleElement = function (element) {
            this.titleElements.push(element);
        };

        PageTitle.prototype.getTitle = function () {
            return this.getTitleExcluding([], ' ');
        };

        PageTitle.prototype.getTitleWithSeparator = function (separator) {
            return this.getTitleExcluding([], separator);
        };

        PageTitle.prototype.getTitleExcluding = function (excludes, separator) {
            return this.getTitleArrayExcluding(excludes).join(separator);
        };

        PageTitle.prototype.getTitleArrayExcluding = function (excludes) {
            return this.titleElements.map(function (element) {
                var answer = '';
                if (element) {
                    answer = element();
                    if (answer === null) {
                        return '';
                    }
                }
                return answer;
            }).exclude(excludes).exclude('');
        };
        return PageTitle;
    })();
    Core.PageTitle = PageTitle;
})(Core || (Core = {}));
/**
* @module Camel
*/
var Camel;
(function (Camel) {
    Camel.log = Logger.get("Camel");

    Camel.jmxDomain = 'org.apache.camel';

    Camel.defaultMaximumLabelWidth = 34;
    Camel.defaultCamelMaximumTraceOrDebugBodyLength = 5000;

    /**
    * Looks up the route XML for the given context and selected route and
    * processes the selected route's XML with the given function
    * @method processRouteXml
    * @param {Workspace} workspace
    * @param {Object} jolokia
    * @param {Folder} folder
    * @param {Function} onRoute
    */
    function processRouteXml(workspace, jolokia, folder, onRoute) {
        var selectedRouteId = getSelectedRouteId(workspace, folder);
        var mbean = getSelectionCamelContextMBean(workspace);

        function onRouteXml(response) {
            var route = null;
            var data = response ? response.value : null;
            if (data) {
                var doc = $.parseXML(data);
                var routes = $(doc).find("route[id='" + selectedRouteId + "']");
                if (routes && routes.length) {
                    route = routes[0];
                }
            }
            onRoute(route);
        }

        if (mbean && selectedRouteId) {
            jolokia.request({ type: 'exec', mbean: mbean, operation: 'dumpRoutesAsXml()' }, onSuccess(onRouteXml, { error: onRouteXml }));
        } else {
            if (!selectedRouteId) {
                console.log("No selectedRouteId when trying to lazy load the route!");
            }
            onRoute(null);
        }
    }
    Camel.processRouteXml = processRouteXml;

    /**
    * Returns the URI string for the given EIP pattern node or null if it is not applicable
    * @method getRouteNodeUri
    * @param {Object} node
    * @return {String}
    */
    function getRouteNodeUri(node) {
        var uri = null;
        if (node) {
            uri = node.getAttribute("uri");
            if (!uri) {
                var ref = node.getAttribute("ref");
                if (ref) {
                    var method = node.getAttribute("method");
                    if (method) {
                        uri = ref + "." + method + "()";
                    } else {
                        uri = "ref:" + ref;
                    }
                }
            }
        }
        return uri;
    }
    Camel.getRouteNodeUri = getRouteNodeUri;

    /**
    * Returns the JSON data for the camel folder; extracting it from the associated
    * routeXmlNode or using the previously extracted and/or edited JSON
    * @method getRouteFolderJSON
    * @param {Folder} folder
    * @param {Object} answer
    * @return {Object}
    */
    function getRouteFolderJSON(folder, answer) {
        if (typeof answer === "undefined") { answer = {}; }
        var nodeData = folder["camelNodeData"];
        if (!nodeData) {
            var routeXmlNode = folder["routeXmlNode"];
            if (routeXmlNode) {
                nodeData = Camel.getRouteNodeJSON(routeXmlNode);
            }
            if (!nodeData) {
                nodeData = answer;
            }
            folder["camelNodeData"] = nodeData;
        }
        return nodeData;
    }
    Camel.getRouteFolderJSON = getRouteFolderJSON;

    function getRouteNodeJSON(routeXmlNode, answer) {
        if (typeof answer === "undefined") { answer = {}; }
        if (routeXmlNode) {
            angular.forEach(routeXmlNode.attributes, function (attr) {
                answer[attr.name] = attr.value;
            });

            // lets not iterate into routes or top level tags
            var localName = routeXmlNode.localName;
            if (localName !== "route" && localName !== "routes" && localName !== "camelContext") {
                // lets look for nested elements and convert those
                // explicitly looking for expressions
                $(routeXmlNode).children("*").each(function (idx, element) {
                    var nodeName = element.localName;
                    var langSettings = Camel.camelLanguageSettings(nodeName);
                    if (langSettings) {
                        // TODO the expression key could be anything really; how should we know?
                        answer["expression"] = {
                            language: nodeName,
                            expression: element.textContent
                        };
                    } else {
                        if (!isCamelPattern(nodeName)) {
                            var nested = getRouteNodeJSON(element);
                            if (nested) {
                                answer[nodeName] = nested;
                            }
                        }
                    }
                });
            }
        }
        return answer;
    }
    Camel.getRouteNodeJSON = getRouteNodeJSON;

    function increaseIndent(currentIndent, indentAmount) {
        if (typeof indentAmount === "undefined") { indentAmount = "  "; }
        return currentIndent + indentAmount;
    }
    Camel.increaseIndent = increaseIndent;

    function setRouteNodeJSON(routeXmlNode, newData, indent) {
        if (routeXmlNode) {
            var childIndent = increaseIndent(indent);

            function doUpdate(value, key, append) {
                if (typeof append === "undefined") { append = false; }
                if (angular.isArray(value)) {
                    // remove previous nodes
                    $(routeXmlNode).children(key).remove();
                    angular.forEach(value, function (item) {
                        doUpdate(item, key, true);
                    });
                } else if (angular.isObject(value)) {
                    // convert languages to the right xml
                    var textContent = null;
                    if (key === "expression") {
                        var languageName = value["language"];
                        if (languageName) {
                            key = languageName;
                            textContent = value["expression"];
                            value = angular.copy(value);
                            delete value["expression"];
                            delete value["language"];
                        }
                    }

                    // TODO deal with nested objects...
                    var nested = $(routeXmlNode).children(key);
                    var element = null;
                    if (append || !nested || !nested.length) {
                        var doc = routeXmlNode.ownerDocument || document;
                        routeXmlNode.appendChild(doc.createTextNode("\n" + childIndent));
                        element = doc.createElementNS(routeXmlNode.namespaceURI, key);
                        if (textContent) {
                            element.appendChild(doc.createTextNode(textContent));
                        }
                        routeXmlNode.appendChild(element);
                    } else {
                        element = nested[0];
                    }
                    setRouteNodeJSON(element, value, childIndent);
                    if (textContent) {
                        nested.text(textContent);
                    }
                } else {
                    if (value) {
                        if (key.startsWith("_")) {
                            // ignore
                        } else {
                            var text = value.toString();
                            routeXmlNode.setAttribute(key, text);
                        }
                    } else {
                        routeXmlNode.removeAttribute(key);
                    }
                }
            }

            angular.forEach(newData, function (value, key) {
                return doUpdate(value, key, false);
            });
        }
    }
    Camel.setRouteNodeJSON = setRouteNodeJSON;

    function getRouteNodeIcon(nodeSettingsOrXmlNode) {
        var nodeSettings = null;
        if (nodeSettingsOrXmlNode) {
            var nodeName = nodeSettingsOrXmlNode.localName;
            if (nodeName) {
                nodeSettings = getCamelSchema(nodeName);
            } else {
                nodeSettings = nodeSettingsOrXmlNode;
            }
        }
        if (nodeSettings) {
            var imageName = nodeSettings["icon"] || "generic24.png";
            return url("/img/icons/camel/" + imageName);
        } else {
            return null;
        }
    }
    Camel.getRouteNodeIcon = getRouteNodeIcon;

    /**
    * Parse out the currently selected endpoint's name to be used when invoking on a
    * context operation that wants an endpoint name
    * @method getSelectedEndpointName
    * @param {Workspace} workspace
    * @return {any} either a string that is the endpoint name or null if it couldn't be parsed
    */
    function getSelectedEndpointName(workspace) {
        var selection = workspace.selection;
        if (selection && selection['objectName'] && selection['typeName'] && selection['typeName'] === 'endpoints') {
            var mbean = Core.parseMBean(selection['objectName']);
            if (!mbean) {
                return null;
            }
            var attributes = mbean['attributes'];
            if (!attributes) {
                return null;
            }

            if (!('name' in attributes)) {
                return null;
            }

            var uri = attributes['name'];
            uri = uri.replace("\\?", "?");
            if (uri.startsWith("\"")) {
                uri = uri.last(uri.length - 1);
            }
            if (uri.endsWith("\"")) {
                uri = uri.first(uri.length - 1);
            }
            return uri;
        } else {
            return null;
        }
    }
    Camel.getSelectedEndpointName = getSelectedEndpointName;

    /**
    * Escapes the given URI text so it can be used in a JMX name
    */
    function escapeEndpointUriNameForJmx(uri) {
        if (angular.isString(uri)) {
            var answer = uri.replace("?", "\\?");

            // lets ensure that we have a "//" after each ":"
            answer = answer.replace(/\:(\/[^\/])/, "://$1");
            answer = answer.replace(/\:([^\/])/, "://$1");
            return answer;
        } else {
            return uri;
        }
    }
    Camel.escapeEndpointUriNameForJmx = escapeEndpointUriNameForJmx;

    /**
    * Returns the mbean for the currently selected camel context and the name of the currently
    * selected endpoint for JMX operations on a context that require an endpoint name.
    * @method
    * @param workspace
    * @return {{uri: string, mbean: string}} either value could be null if there's a parse failure
    */
    function getContextAndTargetEndpoint(workspace) {
        return {
            uri: Camel.getSelectedEndpointName(workspace),
            mbean: Camel.getSelectionCamelContextMBean(workspace)
        };
    }
    Camel.getContextAndTargetEndpoint = getContextAndTargetEndpoint;

    /**
    * Returns the cached Camel XML route node stored in the current tree selection Folder
    * @method
    */
    function getSelectedRouteNode(workspace) {
        var selection = workspace.selection;
        return (selection && Camel.jmxDomain === selection.domain) ? selection["routeXmlNode"] : null;
    }
    Camel.getSelectedRouteNode = getSelectedRouteNode;

    /**
    * Flushes the cached Camel XML route node stored in the selected tree Folder
    * @method
    * @param workspace
    */
    function clearSelectedRouteNode(workspace) {
        var selection = workspace.selection;
        if (selection && Camel.jmxDomain === selection.domain) {
            delete selection["routeXmlNode"];
        }
    }
    Camel.clearSelectedRouteNode = clearSelectedRouteNode;

    /**
    * Looks up the given node name in the Camel schema
    * @method
    */
    function getCamelSchema(nodeIdOrDefinition) {
        return (angular.isObject(nodeIdOrDefinition)) ? nodeIdOrDefinition : Forms.lookupDefinition(nodeIdOrDefinition, _apacheCamelModel);
    }
    Camel.getCamelSchema = getCamelSchema;

    /**
    * Returns true if the given nodeId is a route, endpoint or pattern
    * (and not some nested type like a data format)
    * @method
    */
    function isCamelPattern(nodeId) {
        return Forms.isJsonType(nodeId, _apacheCamelModel, "org.apache.camel.model.OptionalIdentifiedDefinition");
    }
    Camel.isCamelPattern = isCamelPattern;

    /**
    * Returns true if the given node type prefers adding the next sibling as a child
    * @method
    */
    function isNextSiblingAddedAsChild(nodeIdOrDefinition) {
        var definition = getCamelSchema(nodeIdOrDefinition);
        if (definition) {
            return definition["nextSiblingAddedAsChild"] || false;
        }
        return null;
    }
    Camel.isNextSiblingAddedAsChild = isNextSiblingAddedAsChild;

    function acceptInput(nodeIdOrDefinition) {
        var definition = getCamelSchema(nodeIdOrDefinition);
        if (definition) {
            return definition["acceptInput"] || false;
        }
        return null;
    }
    Camel.acceptInput = acceptInput;

    function acceptOutput(nodeIdOrDefinition) {
        var definition = getCamelSchema(nodeIdOrDefinition);
        if (definition) {
            return definition["acceptOutput"] || false;
        }
        return null;
    }
    Camel.acceptOutput = acceptOutput;

    /**
    * Looks up the Camel language settings for the given language name
    * @method
    */
    function camelLanguageSettings(nodeName) {
        return _apacheCamelModel.languages[nodeName];
    }
    Camel.camelLanguageSettings = camelLanguageSettings;

    function isCamelLanguage(nodeName) {
        return (camelLanguageSettings(nodeName) || nodeName === "expression") ? true : false;
    }
    Camel.isCamelLanguage = isCamelLanguage;

    /**
    * Converts the XML string or DOM node to a camel tree
    * @method
    */
    function loadCamelTree(xml, key) {
        var doc = xml;
        if (angular.isString(xml)) {
            doc = $.parseXML(xml);
        }

        // TODO get id from camelContext
        var id = "camelContext";
        var folder = new Folder(id);
        folder.addClass = "org-apache-camel-context";
        folder.domain = Camel.jmxDomain;
        folder.typeName = "context";

        folder.key = Core.toSafeDomID(key);

        var context = $(doc).find("camelContext");
        if (!context || !context.length) {
            context = $(doc).find("routes");
        }

        if (context && context.length) {
            folder["xmlDocument"] = doc;
            folder["routeXmlNode"] = context;
            $(context).children("route").each(function (idx, route) {
                var id = route.getAttribute("id");
                if (!id) {
                    id = "route" + idx;
                    route.setAttribute("id", id);
                }
                var routeFolder = new Folder(id);
                routeFolder.addClass = "org-apache-camel-route";
                routeFolder.typeName = "routes";
                routeFolder.domain = Camel.jmxDomain;
                routeFolder.key = folder.key + "_" + Core.toSafeDomID(id);
                routeFolder.parent = folder;
                var nodeSettings = getCamelSchema("route");
                if (nodeSettings) {
                    var imageUrl = getRouteNodeIcon(nodeSettings);
                    routeFolder.tooltip = nodeSettings["tooltip"] || nodeSettings["description"] || id;
                    routeFolder.icon = imageUrl;
                }
                folder.children.push(routeFolder);

                addRouteChildren(routeFolder, route);
            });
        }
        return folder;
    }
    Camel.loadCamelTree = loadCamelTree;

    /**
    * Adds the route children to the given folder for each step in the route
    * @method
    */
    function addRouteChildren(folder, route) {
        folder.children = [];
        folder["routeXmlNode"] = route;
        route.setAttribute("_cid", folder.key);
        $(route).children("*").each(function (idx, n) {
            addRouteChild(folder, n);
        });
    }
    Camel.addRouteChildren = addRouteChildren;

    /**
    * Adds a child to the given folder / route
    * @method
    */
    function addRouteChild(folder, n) {
        var nodeName = n.localName;
        if (nodeName) {
            var nodeSettings = getCamelSchema(nodeName);
            if (nodeSettings) {
                var imageUrl = getRouteNodeIcon(nodeSettings);

                var child = new Folder(nodeName);
                child.domain = Camel.jmxDomain;
                child.typeName = "routeNode";
                updateRouteNodeLabelAndTooltip(child, n, nodeSettings);

                // TODO should maybe auto-generate these?
                child.parent = folder;
                child.folderNames = folder.folderNames;
                var id = n.getAttribute("id") || nodeName;
                var key = folder.key + "_" + Core.toSafeDomID(id);

                // lets find the next key thats unique
                var counter = 1;
                var notFound = true;
                while (notFound) {
                    var tmpKey = key + counter;
                    if (folder.children.some({ key: tmpKey })) {
                        counter += 1;
                    } else {
                        notFound = false;
                        key = tmpKey;
                    }
                }
                child.key = key;
                child.icon = imageUrl;
                child["routeXmlNode"] = n;
                if (!folder.children) {
                    folder.children = [];
                }
                folder.children.push(child);
                addRouteChildren(child, n);
                return child;
            }
        }
        return null;
    }
    Camel.addRouteChild = addRouteChild;

    /**
    * Returns the root JMX Folder of the camel mbeans
    */
    function getRootCamelFolder(workspace) {
        var tree = workspace ? workspace.tree : null;
        if (tree) {
            return tree.get(Camel.jmxDomain);
        }
        return null;
    }
    Camel.getRootCamelFolder = getRootCamelFolder;

    /**
    * Returns the JMX folder for the camel context
    */
    function getCamelContextFolder(workspace, camelContextId) {
        var answer = null;
        var root = getRootCamelFolder(workspace);
        if (root && camelContextId) {
            angular.forEach(root.children, function (contextFolder) {
                if (!answer && camelContextId === contextFolder.title) {
                    answer = contextFolder;
                }
            });
        }
        return answer;
    }
    Camel.getCamelContextFolder = getCamelContextFolder;

    /**
    * Returns the mbean for the given camel context ID or null if it cannot be found
    */
    function getCamelContextMBean(workspace, camelContextId) {
        var contextsFolder = getCamelContextFolder(workspace, camelContextId);
        if (contextsFolder) {
            var contextFolder = contextsFolder.navigate("context");
            if (contextFolder && contextFolder.children && contextFolder.children.length) {
                var contextItem = contextFolder.children[0];
                return contextItem.objectName;
            }
        }
        return null;
    }
    Camel.getCamelContextMBean = getCamelContextMBean;

    /**
    * Given a selection in the workspace try figure out the URL to the
    * full screen view
    */
    function linkToFullScreenView(workspace) {
        var answer = null;
        var selection = workspace.selection;
        if (selection) {
            var entries = selection.entries;
            if (entries) {
                var contextId = entries["context"];
                var name = entries["name"];
                var type = entries["type"];
                if ("endpoints" === type) {
                    return linkToBrowseEndpointFullScreen(contextId, name);
                }
                if ("routes" === type) {
                    return linkToRouteDiagramFullScreen(contextId, name);
                }
                // TODO a default page for a context?
            }
        }
        return answer;
    }
    Camel.linkToFullScreenView = linkToFullScreenView;

    /**
    * Returns the link to browse the endpoint full screen
    */
    function linkToBrowseEndpointFullScreen(contextId, endpointPath) {
        var answer = null;
        if (contextId && endpointPath) {
            answer = "#/camel/endpoint/browse/" + contextId + "/" + endpointPath;
        }
        return answer;
    }
    Camel.linkToBrowseEndpointFullScreen = linkToBrowseEndpointFullScreen;

    /**
    * Returns the link to the route diagram full screen
    */
    function linkToRouteDiagramFullScreen(contextId, routeId) {
        var answer = null;
        if (contextId && routeId) {
            answer = "#/camel/route/diagram/" + contextId + "/" + routeId;
        }
        return answer;
    }
    Camel.linkToRouteDiagramFullScreen = linkToRouteDiagramFullScreen;

    function getFolderCamelNodeId(folder) {
        var answer = Core.pathGet(folder, ["routeXmlNode", "localName"]);
        return ("from" === answer || "to" === answer) ? "endpoint" : answer;
    }
    Camel.getFolderCamelNodeId = getFolderCamelNodeId;

    /**
    * Rebuilds the DOM tree from the tree node and performs all the various hacks
    * to turn the folder / JSON / model into valid camel XML
    * such as renaming language elements from <language expression="foo" language="bar/>
    * to <bar>foo</bar>
    * and changing <endpoint> into either <from> or <to>
    * @method
    * @param treeNode is either the Node from the tree widget (with the real Folder in the data property) or a Folder
    */
    function createFolderXmlTree(treeNode, xmlNode, indent) {
        if (typeof indent === "undefined") { indent = Camel.increaseIndent(""); }
        var folder = treeNode.data || treeNode;
        var count = 0;
        var parentName = getFolderCamelNodeId(folder);
        if (folder) {
            if (!xmlNode) {
                xmlNode = document.createElement(parentName);
                var rootJson = Camel.getRouteFolderJSON(folder);
                if (rootJson) {
                    Camel.setRouteNodeJSON(xmlNode, rootJson, indent);
                }
            }
            var doc = xmlNode.ownerDocument || document;
            var namespaceURI = xmlNode.namespaceURI;

            var from = parentName !== "route";
            var childIndent = Camel.increaseIndent(indent);
            angular.forEach(treeNode.children || treeNode.getChildren(), function (childTreeNode) {
                var childFolder = childTreeNode.data || childTreeNode;
                var name = Camel.getFolderCamelNodeId(childFolder);
                var json = Camel.getRouteFolderJSON(childFolder);
                if (name && json) {
                    var language = false;
                    if (name === "endpoint") {
                        if (from) {
                            name = "to";
                        } else {
                            name = "from";
                            from = true;
                        }
                    }
                    if (name === "expression") {
                        var languageName = json["language"];
                        if (languageName) {
                            name = languageName;
                            language = true;
                        }
                    }

                    // lets create the XML
                    xmlNode.appendChild(doc.createTextNode("\n" + childIndent));
                    var newNode = doc.createElementNS(namespaceURI, name);

                    Camel.setRouteNodeJSON(newNode, json, childIndent);
                    xmlNode.appendChild(newNode);
                    count += 1;
                    createFolderXmlTree(childTreeNode, newNode, childIndent);
                }
            });
            if (count) {
                xmlNode.appendChild(doc.createTextNode("\n" + indent));
            }
        }
        return xmlNode;
    }
    Camel.createFolderXmlTree = createFolderXmlTree;

    function updateRouteNodeLabelAndTooltip(folder, routeXmlNode, nodeSettings) {
        var localName = routeXmlNode.localName;
        var id = routeXmlNode.getAttribute("id");
        var label = nodeSettings["title"] || localName;

        // lets use the ID for routes and other things we give an id
        var tooltip = nodeSettings["tooltip"] || nodeSettings["description"] || label;
        if (id) {
            label = id;
        } else {
            var uri = getRouteNodeUri(routeXmlNode);
            if (uri) {
                // Don't use from/to as it gets odd if you drag/drop and reorder
                // label += " " + uri;
                label = uri;
                var split = uri.split("?");
                if (split && split.length > 1) {
                    label = split[0];
                }
                tooltip += " " + uri;
            } else {
                var children = $(routeXmlNode).children("*");
                if (children && children.length) {
                    var child = children[0];
                    var childName = child.localName;
                    var expression = null;
                    if (Camel.isCamelLanguage(childName)) {
                        expression = child.textContent;
                        if (!expression) {
                            expression = child.getAttribute("expression");
                        }
                    }
                    if (expression) {
                        label += " " + expression;
                        tooltip += " " + childName + " expression";
                    }
                }
            }
        }
        folder.title = label;
        folder.tooltip = tooltip;
        return label;
    }
    Camel.updateRouteNodeLabelAndTooltip = updateRouteNodeLabelAndTooltip;

    /**
    * Returns the selected camel context mbean for the given selection or null if it cannot be found
    * @method
    */
    // TODO should be a service
    function getSelectionCamelContextMBean(workspace) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    var result = tree.navigate(domain, contextId, "context");
                    if (result && result.children) {
                        var contextBean = result.children.first();
                        if (contextBean.title) {
                            var contextName = contextBean.title;
                            return "" + domain + ":context=" + contextId + ',type=context,name="' + contextName + '"';
                        }
                    }
                }
            }
        }
        return null;
    }
    Camel.getSelectionCamelContextMBean = getSelectionCamelContextMBean;

    function getSelectionCamelContextEndpoints(workspace) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    return tree.navigate(domain, contextId, "endpoints");
                }
            }
        }
        return null;
    }
    Camel.getSelectionCamelContextEndpoints = getSelectionCamelContextEndpoints;

    /**
    * Returns the selected camel trace mbean for the given selection or null if it cannot be found
    * @method
    */
    // TODO Should be a service
    function getSelectionCamelTraceMBean(workspace) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    // look for the Camel 2.11 mbean which we prefer
                    var result = tree.navigate(domain, contextId, "tracer");
                    if (result && result.children) {
                        var mbean = result.children.find(function (m) {
                            return m.title.startsWith("BacklogTracer");
                        });
                        if (mbean) {
                            return mbean.objectName;
                        }
                    }

                    // look for the fuse camel fabric mbean
                    var fabricResult = tree.navigate(domain, contextId, "fabric");
                    if (fabricResult && fabricResult.children) {
                        var mbean = fabricResult.children.first();
                        return mbean.objectName;
                    }
                }
            }
        }
        return null;
    }
    Camel.getSelectionCamelTraceMBean = getSelectionCamelTraceMBean;

    function getSelectionCamelDebugMBean(workspace) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    var result = tree.navigate(domain, contextId, "tracer");
                    if (result && result.children) {
                        var mbean = result.children.find(function (m) {
                            return m.title.startsWith("BacklogDebugger");
                        });
                        if (mbean) {
                            return mbean.objectName;
                        }
                    }
                }
            }
        }
        return null;
    }
    Camel.getSelectionCamelDebugMBean = getSelectionCamelDebugMBean;

    function getSelectionCamelTypeConverter(workspace) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    var result = tree.navigate(domain, contextId, "services");
                    if (result && result.children) {
                        var mbean = result.children.find(function (m) {
                            return m.title.startsWith("DefaultTypeConverter");
                        });
                        if (mbean) {
                            return mbean.objectName;
                        }
                    }
                }
            }
        }
        return null;
    }
    Camel.getSelectionCamelTypeConverter = getSelectionCamelTypeConverter;

    // TODO should be a service
    function getContextId(workspace) {
        var selection = workspace.selection;
        if (selection) {
            var tree = workspace.tree;
            var folderNames = selection.folderNames;
            var entries = selection.entries;
            var contextId;
            if (tree) {
                if (folderNames && folderNames.length > 1) {
                    contextId = folderNames[1];
                } else if (entries) {
                    contextId = entries["context"];
                }
            }
        }
        return contextId;
    }
    Camel.getContextId = getContextId;

    /**
    * Returns true if the state of the item begins with the given state - or one of the given states
    * @method
    * @param item the item which has a State
    * @param state a value or an array of states
    */
    function isState(item, state) {
        var value = (item.State || "").toLowerCase();
        if (angular.isArray(state)) {
            return state.any(function (stateText) {
                return value.startsWith(stateText);
            });
        } else {
            return value.startsWith(state);
        }
    }
    Camel.isState = isState;

    function iconClass(state) {
        if (state) {
            switch (state.toLowerCase()) {
                case 'started':
                    return "green icon-play-circle";
                case 'suspended':
                    return "icon-pause";
            }
        }
        return "orange icon-off";
    }
    Camel.iconClass = iconClass;

    function getSelectedRouteId(workspace, folder) {
        if (typeof folder === "undefined") { folder = null; }
        var selection = folder || workspace.selection;
        var selectedRouteId = null;
        if (selection) {
            if (selection && selection.entries) {
                var typeName = selection.entries["type"];
                var name = selection.entries["name"];
                if ("routes" === typeName && name) {
                    selectedRouteId = trimQuotes(name);
                }
            }
        }
        return selectedRouteId;
    }
    Camel.getSelectedRouteId = getSelectedRouteId;

    /**
    * Returns the selected camel route mbean for the given route id
    * @method
    */
    // TODO Should be a service
    function getSelectionRouteMBean(workspace, routeId) {
        if (workspace) {
            var contextId = getContextId(workspace);
            var selection = workspace.selection;
            var tree = workspace.tree;
            if (tree && selection) {
                var domain = selection.domain;
                if (domain && contextId) {
                    var result = tree.navigate(domain, contextId, "routes");
                    if (result && result.children) {
                        var mbean = result.children.find(function (m) {
                            return m.title === routeId;
                        });
                        if (mbean) {
                            return mbean.objectName;
                        }
                    }
                }
            }
        }
        return null;
    }
    Camel.getSelectionRouteMBean = getSelectionRouteMBean;

    function getCamelVersion(workspace, jolokia) {
        var mbean = getSelectionCamelContextMBean(workspace);
        if (mbean) {
            // must use onSuccess(null) that means sync as we need the version asap
            return jolokia.getAttribute(mbean, "CamelVersion", onSuccess(null));
        } else {
            return null;
        }
    }
    Camel.getCamelVersion = getCamelVersion;

    function createMessageFromXml(exchange) {
        var exchangeElement = $(exchange);
        var uid = exchangeElement.children("uid").text();
        var timestamp = exchangeElement.children("timestamp").text();
        var messageData = {
            headers: {},
            headerTypes: {},
            id: null,
            uid: uid,
            timestamp: timestamp,
            headerHtml: ""
        };
        var message = exchangeElement.children("message")[0];
        if (!message) {
            message = exchange;
        }
        var messageElement = $(message);
        var headers = messageElement.find("header");
        var headerHtml = "";
        headers.each(function (idx, header) {
            var key = header.getAttribute("key");
            var typeName = header.getAttribute("type");
            var value = header.textContent;
            if (key) {
                if (value)
                    messageData.headers[key] = value;
                if (typeName)
                    messageData.headerTypes[key] = typeName;

                headerHtml += "<tr><td class='property-name'>" + key + "</td>" + "<td class='property-value'>" + (value || "") + "</td></tr>";
            }
        });

        messageData.headerHtml = headerHtml;
        var id = messageData.headers["breadcrumbId"];
        if (!id) {
            var postFixes = ["MessageID", "ID", "Path", "Name"];
            angular.forEach(postFixes, function (postfix) {
                if (!id) {
                    angular.forEach(messageData.headers, function (value, key) {
                        if (!id && key.endsWith(postfix)) {
                            id = value;
                        }
                    });
                }
            });

            // lets find the first header with a name or Path in it
            // if still no value, lets use the first :)
            angular.forEach(messageData.headers, function (value, key) {
                if (!id)
                    id = value;
            });
        }
        messageData.id = id;
        var body = messageElement.children("body")[0];
        if (body) {
            var bodyText = body.textContent;
            var bodyType = body.getAttribute("type");
            messageData["body"] = bodyText;
            messageData["bodyType"] = bodyType;
        }
        return messageData;
    }
    Camel.createMessageFromXml = createMessageFromXml;

    function createBrowseGridOptions() {
        return {
            selectedItems: [],
            data: 'messages',
            displayFooter: false,
            showFilter: false,
            showColumnMenu: true,
            enableColumnResize: true,
            enableColumnReordering: true,
            filterOptions: {
                filterText: ''
            },
            selectWithCheckboxOnly: true,
            showSelectionCheckbox: true,
            maintainColumnRatios: false,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'ID',
                    // for ng-grid
                    //width: '50%',
                    // for hawtio-datatable
                    // width: "22em",
                    cellTemplate: '<div class="ngCellText"><a ng-click="openMessageDialog(row)">{{row.entity.id}}</a></div>'
                }
            ]
        };
    }
    Camel.createBrowseGridOptions = createBrowseGridOptions;

    function loadRouteXmlNodes($scope, doc, selectedRouteId, nodes, links, width) {
        var allRoutes = $(doc).find("route");
        var routeDelta = width / allRoutes.length;
        var rowX = 0;
        allRoutes.each(function (idx, route) {
            var routeId = route.getAttribute("id");
            if (!selectedRouteId || !routeId || selectedRouteId === routeId) {
                Camel.addRouteXmlChildren($scope, route, nodes, links, null, rowX, 0);
                rowX += routeDelta;
            }
        });
    }
    Camel.loadRouteXmlNodes = loadRouteXmlNodes;

    function addRouteXmlChildren($scope, parent, nodes, links, parentId, parentX, parentY, parentNode) {
        if (typeof parentNode === "undefined") { parentNode = null; }
        var delta = 150;
        var x = parentX;
        var y = parentY + delta;
        var rid = parent.getAttribute("id");
        var siblingNodes = [];
        var parenNodeName = parent.localName;
        $(parent).children().each(function (idx, route) {
            var id = nodes.length;

            // from acts as a parent even though its a previous sibling :)
            var nodeId = route.localName;
            if (nodeId === "from" && !parentId) {
                parentId = id;
            }
            var nodeSettings = getCamelSchema(nodeId);
            var node = null;
            if (nodeSettings) {
                var label = nodeSettings["title"] || nodeId;
                var uri = getRouteNodeUri(route);
                if (uri) {
                    label += " " + uri.split("?")[0];
                }
                var tooltip = nodeSettings["tooltip"] || nodeSettings["description"] || label;
                if (uri) {
                    tooltip += " " + uri;
                }
                var elementID = route.getAttribute("id");
                var labelSummary = label;
                if (elementID) {
                    var customId = route.getAttribute("customId");
                    if ($scope.camelIgnoreIdForLabel || (!customId || customId === "false")) {
                        labelSummary = "id: " + elementID;
                    } else {
                        label = elementID;
                    }
                }

                // lets check if we need to trim the label
                var labelLimit = $scope.camelMaximumLabelWidth || Camel.defaultMaximumLabelWidth;
                var length = label.length;
                if (length > labelLimit) {
                    labelSummary = label + "\n\n" + labelSummary;
                    label = label.substring(0, labelLimit) + "..";
                }

                var imageUrl = getRouteNodeIcon(nodeSettings);
                if ((nodeId === "from" || nodeId === "to") && uri) {
                    var uriIdx = uri.indexOf(":");
                    if (uriIdx > 0) {
                        var componentScheme = uri.substring(0, uriIdx);

                        //console.log("lets find the endpoint icon for " + componentScheme);
                        if (componentScheme) {
                            var value = Camel.getEndpointIcon(componentScheme);
                            if (value) {
                                imageUrl = url(value);
                            }
                        }
                    }
                }

                //console.log("Image URL is " + imageUrl);
                var cid = route.getAttribute("_cid") || route.getAttribute("id");
                node = {
                    "name": name, "label": label, "labelSummary": labelSummary, "group": 1, "id": id, "elementId": elementID,
                    "x": x, "y:": y, "imageUrl": imageUrl, "cid": cid, "tooltip": tooltip, "type": nodeId };
                if (rid) {
                    node["rid"] = rid;
                    if (!$scope.routeNodes)
                        $scope.routeNodes = {};
                    $scope.routeNodes[rid] = node;
                }
                if (!cid) {
                    cid = nodeId + (nodes.length + 1);
                }
                if (cid) {
                    node["cid"] = cid;
                    if (!$scope.nodes)
                        $scope.nodes = {};
                    $scope.nodes[cid] = node;
                }

                // only use the route id on the first from node
                rid = null;
                nodes.push(node);
                if (parentId !== null && parentId !== id) {
                    if (siblingNodes.length === 0 || parenNodeName === "choice") {
                        links.push({ "source": parentId, "target": id, "value": 1 });
                    } else {
                        siblingNodes.forEach(function (nodeId) {
                            links.push({ "source": nodeId, "target": id, "value": 1 });
                        });
                        siblingNodes.length = 0;
                    }
                }
            } else {
                // ignore non EIP nodes, though we should add expressions...
                var langSettings = Camel.camelLanguageSettings(nodeId);
                if (langSettings && parentNode) {
                    // lets add the language kind
                    var name = langSettings["name"] || nodeId;
                    var text = route.textContent;
                    if (text) {
                        parentNode["tooltip"] = parentNode["label"] + " " + name + " " + text;
                        parentNode["label"] = text;
                    } else {
                        parentNode["label"] = parentNode["label"] + " " + name;
                    }
                }
            }
            var siblings = addRouteXmlChildren($scope, route, nodes, links, id, x, y, node);
            if (parenNodeName === "choice") {
                siblingNodes = siblingNodes.concat(siblings);
                x += delta;
            } else if (nodeId === "choice") {
                siblingNodes = siblings;
                y += delta;
            } else {
                siblingNodes = [nodes.length - 1];
                y += delta;
            }
        });
        return siblingNodes;
    }
    Camel.addRouteXmlChildren = addRouteXmlChildren;

    function getCanvasHeight(canvasDiv) {
        var height = canvasDiv.height();
        if (height < 300) {
            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) {
                var top = offset['top'];
                if (top) {
                    height -= top;
                }
            }
        }
        return height;
    }
    Camel.getCanvasHeight = getCanvasHeight;

    /**
    * Recursively add all the folders which have a cid value into the given map
    * @method
    */
    function addFoldersToIndex(folder, map) {
        if (typeof map === "undefined") { map = {}; }
        if (folder) {
            var key = folder.key;
            if (key) {
                map[key] = folder;
            }
            angular.forEach(folder.children, function (child) {
                return addFoldersToIndex(child, map);
            });
        }
        return map;
    }
    Camel.addFoldersToIndex = addFoldersToIndex;

    /**
    * Re-generates the XML document using the given Tree widget Node or Folder as the source
    * @method
    */
    function generateXmlFromFolder(treeNode) {
        var folder = (treeNode && treeNode.data) ? treeNode.data : treeNode;
        if (!folder)
            return null;
        var doc = folder["xmlDocument"];
        var context = folder["routeXmlNode"];

        if (context && context.length) {
            var element = context[0];
            var children = element.childNodes;
            var routeIndices = [];
            for (var i = 0; i < children.length; i++) {
                var node = children[i];
                var name = node.localName;
                if ("route" === name && parent) {
                    routeIndices.push(i);
                }
            }

            while (routeIndices.length) {
                var idx = routeIndices.pop();
                var nextIndex = idx + 1;
                while (true) {
                    var node = element.childNodes[nextIndex];
                    if (Core.isTextNode(node)) {
                        element.removeChild(node);
                    } else {
                        break;
                    }
                }
                if (idx < element.childNodes.length) {
                    element.removeChild(element.childNodes[idx]);
                }
                for (var i = idx - 1; i >= 0; i--) {
                    var node = element.childNodes[i];
                    if (Core.isTextNode(node)) {
                        element.removeChild(node);
                    } else {
                        break;
                    }
                }
            }
            Camel.createFolderXmlTree(treeNode, context[0]);
        }
        return doc;
    }
    Camel.generateXmlFromFolder = generateXmlFromFolder;

    /**
    * Returns an object of all the CamelContext MBeans keyed by their id
    * @method
    */
    function camelContextMBeansById(workspace) {
        var answer = {};
        var tree = workspace.tree;
        if (tree) {
            var camelTree = tree.navigate(Camel.jmxDomain);
            if (camelTree) {
                angular.forEach(camelTree.children, function (contextsFolder) {
                    var contextFolder = contextsFolder.navigate("context");
                    if (contextFolder && contextFolder.children && contextFolder.children.length) {
                        var contextItem = contextFolder.children[0];
                        var id = Core.pathGet(contextItem, ["entries", "name"]) || contextItem.key;
                        if (id) {
                            answer[id] = {
                                folder: contextItem,
                                mbean: contextItem.objectName
                            };
                        }
                    }
                });
            }
        }
        return answer;
    }
    Camel.camelContextMBeansById = camelContextMBeansById;

    /**
    * Returns an object of all the CamelContext MBeans keyed by the component name
    * @method
    */
    function camelContextMBeansByComponentName(workspace) {
        return camelContextMBeansByRouteOrComponentId(workspace, "components");
    }
    Camel.camelContextMBeansByComponentName = camelContextMBeansByComponentName;

    /**
    * Returns an object of all the CamelContext MBeans keyed by the route ID
    * @method
    */
    function camelContextMBeansByRouteId(workspace) {
        return camelContextMBeansByRouteOrComponentId(workspace, "routes");
    }
    Camel.camelContextMBeansByRouteId = camelContextMBeansByRouteId;

    function camelContextMBeansByRouteOrComponentId(workspace, componentsOrRoutes) {
        var answer = {};
        var tree = workspace.tree;
        if (tree) {
            var camelTree = tree.navigate(Camel.jmxDomain);
            if (camelTree) {
                angular.forEach(camelTree.children, function (contextsFolder) {
                    var contextFolder = contextsFolder.navigate("context");
                    var componentsFolder = contextsFolder.navigate(componentsOrRoutes);
                    if (contextFolder && componentsFolder && contextFolder.children && contextFolder.children.length) {
                        var contextItem = contextFolder.children[0];
                        var mbean = contextItem.objectName;
                        if (mbean) {
                            var contextValues = {
                                folder: contextItem,
                                mbean: mbean
                            };
                            angular.forEach(componentsFolder.children, function (componentFolder) {
                                var id = componentFolder.title;
                                if (id) {
                                    answer[id] = contextValues;
                                }
                            });
                        }
                    }
                });
            }
        }
        return answer;
    }

    /**
    * Returns an object for the given processor from the Camel tree
    * @method
    */
    function camelProcessorMBeansById(workspace) {
        var answer = {};
        var tree = workspace.tree;
        if (tree) {
            var camelTree = tree.navigate(Camel.jmxDomain);
            if (camelTree) {
                angular.forEach(camelTree.children, function (contextsFolder) {
                    var processorsFolder = contextsFolder.navigate("processors");
                    if (processorsFolder && processorsFolder.children && processorsFolder.children.length) {
                        angular.forEach(processorsFolder.children, function (processorFolder) {
                            var id = processorFolder.title;
                            if (id) {
                                var processorValues = {
                                    folder: processorsFolder,
                                    key: processorFolder.key
                                };
                                answer[id] = processorValues;
                            }
                        });
                    }
                });
            }
        }
        return answer;
    }
    Camel.camelProcessorMBeansById = camelProcessorMBeansById;

    /**
    * Returns true if we should ignore ID values for labels in camel diagrams
    * @method
    */
    function ignoreIdForLabel(localStorage) {
        var value = localStorage["camelIgnoreIdForLabel"];
        return value && (value === "true" || value === true);
    }
    Camel.ignoreIdForLabel = ignoreIdForLabel;

    /**
    * Returns the maximum width of a label before we start to truncate
    * @method
    */
    function maximumLabelWidth(localStorage) {
        var value = localStorage["camelMaximumLabelWidth"];
        if (angular.isString(value)) {
            value = parseInt(value);
        }
        if (!value) {
            value = Camel.defaultMaximumLabelWidth;
        }
        return value;
    }
    Camel.maximumLabelWidth = maximumLabelWidth;

    /**
    * Returns the max body length for tracer and debugger
    * @method
    */
    function maximumTraceOrDebugBodyLength(localStorage) {
        var value = localStorage["camelMaximumTraceOrDebugBodyLength"];
        if (angular.isString(value)) {
            value = parseInt(value);
        }
        if (!value) {
            value = Camel.defaultCamelMaximumTraceOrDebugBodyLength;
        }
        return value;
    }
    Camel.maximumTraceOrDebugBodyLength = maximumTraceOrDebugBodyLength;

    /**
    * Function to highlight the selected toNode in the nodes graph
    *
    * @param nodes the nodes
    * @param toNode the node to highlight
    */
    function highlightSelectedNode(nodes, toNode) {
        // lets clear the selected node first
        nodes.attr("class", "node");

        nodes.filter(function (item) {
            if (item) {
                var cid = item["cid"];
                var rid = item["rid"];
                var type = item["type"];
                var elementId = item["elementId"];

                // if its from then match on rid
                if ("from" === type) {
                    return toNode === rid;
                }

                // okay favor using element id as the cids can become
                // undefined or mangled with mbean object names, causing this to not work
                // where as elementId when present works fine
                if (elementId) {
                    // we should match elementId if defined
                    return toNode === elementId;
                }

                // then fallback to cid
                if (cid) {
                    return toNode === cid;
                } else {
                    // and last rid
                    return toNode === rid;
                }
            }
            return null;
        }).attr("class", "node selected");
    }
    Camel.highlightSelectedNode = highlightSelectedNode;

    /**
    * Is the currently selected Camel version equal or greater than
    *
    * @param major   major version as number
    * @param minor   minor version as number
    */
    function isCamelVersionEQGT(major, minor, workspace, jolokia) {
        var camelVersion = getCamelVersion(workspace, jolokia);
        if (camelVersion) {
            console.log("Camel version " + camelVersion);
            camelVersion += "camel-";
            var numbers = Core.parseVersionNumbers(camelVersion);
            if (Core.compareVersionNumberArrays(numbers, [major, minor]) >= 0) {
                return true;
            } else {
                return false;
            }
        }
        return false;
    }
    Camel.isCamelVersionEQGT = isCamelVersionEQGT;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function FabricDiagramController($scope, $compile, $location, localStorage, jolokia, workspace) {
        Fabric.initScope($scope, $location, jolokia, workspace);

        var isFmc = Fabric.isFMCContainer(workspace);
        $scope.isFmc = isFmc;

        $scope.selectedNode = null;

        var defaultFlags = {
            panel: true,
            popup: false,
            label: true,
            container: false,
            endpoint: true,
            route: true,
            context: false,
            consumer: true,
            producer: true
        };

        $scope.viewSettings = {};

        $scope.shapeSize = {
            context: 12,
            route: 10,
            endpoint: 7
        };

        var graphBuilder = new ForceGraph.GraphBuilder();

        Core.bindModelToSearchParam($scope, $location, "searchFilter", "q", "");

        angular.forEach(defaultFlags, function (defaultValue, key) {
            var modelName = "viewSettings." + key;

            // bind model values to search params...
            function currentValue() {
                var answer = $location.search()[paramName] || defaultValue;
                return answer === "false" ? false : answer;
            }

            var paramName = key;
            var value = currentValue();
            Core.pathSet($scope, modelName, value);

            $scope.$watch(modelName, function () {
                var current = Core.pathGet($scope, modelName);
                var old = currentValue();
                if (current !== old) {
                    var defaultValue = defaultFlags[key];
                    if (current !== defaultValue) {
                        if (!current) {
                            current = "false";
                        }
                        $location.search(paramName, current);
                    } else {
                        $location.search(paramName, null);
                    }
                }
                redrawGraph();
            });
        });

        $scope.connectToContext = function () {
            var selectedNode = $scope.selectedNode;
            if (selectedNode) {
                var container = selectedNode["container"] || selectedNode;
                var postfix = null;
                connectToContainer(container, postfix);
            }
        };

        $scope.connectToEndpoint = function () {
            var selectedNode = $scope.selectedNode;
            if (selectedNode) {
                var container = selectedNode["container"] || selectedNode;
                var postfix = null;
                connectToContainer(container, postfix);
            }
        };

        function connectToContainer(container, postfix, viewPrefix) {
            if (typeof viewPrefix === "undefined") { viewPrefix = "/jmx/attributes?tab=camel"; }
            var view = viewPrefix;
            if (postfix) {
                view += postfix;
            }

            // TODO if local just link to local view!
            $scope.doConnect(container, view);
        }

        $scope.$on('$destroy', function (event) {
            stopOldJolokia();
        });

        function stopOldJolokia() {
            var oldJolokia = $scope.selectedNodeJolokia;
            if (oldJolokia && oldJolokia !== jolokia) {
                oldJolokia.stop();
            }
        }

        $scope.$watch("selectedNode", function (newValue, oldValue) {
            // lets cancel any previously registered thingy
            if ($scope.unregisterFn) {
                $scope.unregisterFn();
                $scope.unregisterFn = null;
            }
            var node = $scope.selectedNode;
            if (node) {
                var mbean = node.objectName;
                var container = node.container || {};
                var nodeJolokia = node.jolokia || container.jolokia || jolokia;
                if (nodeJolokia !== $scope.selectedNodeJolokia) {
                    stopOldJolokia();
                    $scope.selectedNodeJolokia = nodeJolokia;
                    if (nodeJolokia !== jolokia) {
                        var rate = Core.parseIntValue(localStorage['updateRate'] || "2000", "update rate");
                        if (rate) {
                            nodeJolokia.start(rate);
                        }
                    }
                }
                var dummyResponse = { value: node.panelProperties || {} };
                if (mbean && nodeJolokia) {
                    $scope.unregisterFn = Core.register(nodeJolokia, $scope, {
                        type: 'read', mbean: mbean
                    }, onSuccess(renderNodeAttributes, { error: function (response) {
                            // probably we've got a wrong mbean name?
                            // so lets render at least
                            renderNodeAttributes(dummyResponse);
                            Core.defaultJolokiaErrorHandler(response);
                        } }));
                } else {
                    renderNodeAttributes(dummyResponse);
                }
            }
        });

        var ignoreNodeAttributes = [
            "CamelId", "CamelManagementName"
        ];

        var ignoreNodeAttributesByType = {
            context: ["ApplicationContextClassName", "CamelId", "ClassResolver", "ManagementName", "PackageScanClassResolver", "Properties"],
            endpoint: ["Camel", "Endpoint"],
            route: ["Description"]
        };

        var onlyShowAttributesByType = {
            broker: []
        };

        function renderNodeAttributes(response) {
            var properties = [];
            if (response) {
                var value = response.value || {};
                $scope.selectedNodeAttributes = value;
                var selectedNode = $scope.selectedNode || {};
                var container = selectedNode['container'] || {};
                var nodeType = selectedNode["type"];
                var brokerName = selectedNode["brokerName"];
                var containerId = container["id"];
                var group = selectedNode["group"] || container["group"];
                var jolokiaUrl = selectedNode["jolokiaUrl"] || container["jolokiaUrl"];
                var profile = selectedNode["profile"] || container["profile"];
                var version = selectedNode["version"] || container["version"];

                var isBroker = nodeType && nodeType.startsWith("broker");
                var ignoreKeys = ignoreNodeAttributes.concat(ignoreNodeAttributesByType[nodeType] || []);
                var onlyShowKeys = onlyShowAttributesByType[nodeType];

                angular.forEach(value, function (v, k) {
                    if (onlyShowKeys ? onlyShowKeys.indexOf(k) >= 0 : ignoreKeys.indexOf(k) < 0) {
                        var formattedValue = Core.humanizeValueHtml(v);
                        properties.push({ key: humanizeValue(k), value: formattedValue });
                    }
                });
                properties = properties.sortBy("key");

                if (containerId && isFmc) {
                    var containerModel = "selectedNode.container";
                    properties.splice(0, 0, { key: "Container", value: $compile('<div fabric-container-link="' + containerModel + '"></div>')($scope) });
                }

                var typeLabel = selectedNode["typeLabel"];
                var name = selectedNode["name"] || selectedNode["id"] || selectedNode['objectName'];
                if (typeLabel) {
                    var html = name;
                    if (nodeType === "queue" || nodeType === "topic") {
                        html = createDestinationLink(name, nodeType);
                    }
                    var typeProperty = { key: typeLabel, value: html };
                    properties.splice(0, 0, typeProperty);
                }
            }
            $scope.selectedNodeProperties = properties;
            Core.$apply($scope);
        }

        /**
        * Generates the HTML for a link to the destination
        */
        function createDestinationLink(destinationName, destinationType) {
            if (typeof destinationType === "undefined") { destinationType = "queue"; }
            return $compile('<a target="destination" title="' + destinationName + '" ng-click="connectToEndpoint()">' + destinationName + '</a>')($scope);
        }

        $scope.$watch("searchFilter", function (newValue, oldValue) {
            redrawGraph();
        });

        if (isFmc) {
            $scope.versionId = Fabric.getDefaultVersionId(jolokia);
            var fields = ["id", "alive", "parentId", "profileIds", "versionId", "provisionResult", "jolokiaUrl", "jmxDomains"];
            Fabric.getContainersFields(jolokia, fields, onFabricContainerData);
        } else {
            // lets just use the current stuff from the workspace
            $scope.$watch('workspace.tree', function () {
                reloadLocalJmxTree();
            });

            $scope.$on('jmxTreeUpdated', function () {
                reloadLocalJmxTree();
            });
        }

        function reloadLocalJmxTree() {
            var localContainer = {
                jolokia: jolokia
            };
            $scope.activeContainers = {
                "local": localContainer
            };
            redrawGraph();
            $scope.containerCount = 1;
        }

        function onFabricContainerData(response) {
            if (response) {
                var responseJson = angular.toJson(response);
                if ($scope.responseJson === responseJson) {
                    return;
                }
                $scope.responseJson = responseJson;

                var containersToDelete = $scope.activeContainers || {};
                $scope.activeContainers = (response || {}).filter(function (c) {
                    return c.jmxDomains.any(Camel.jmxDomain);
                });
                $scope.containerCount = $scope.activeContainers.length;

                // query containers which have camel...
                redrawGraph();
            } else {
                $scope.containerCount = 0;
            }
        }

        function redrawGraph() {
            graphBuilder = new ForceGraph.GraphBuilder();

            // TODO delete any nodes from dead containers in containersToDelete
            angular.forEach($scope.activeContainers, function (container, id) {
                var containerJolokia = container.jolokia;
                if (!containerJolokia) {
                    var jolokiaUrl = container["jolokiaUrl"];
                    if (jolokiaUrl) {
                        var url = Core.useProxyIfExternal(jolokiaUrl);
                        containerJolokia = Fabric.createJolokia(url);
                    }
                }
                if (containerJolokia) {
                    onContainerJolokia(containerJolokia, container);
                } else {
                    Fabric.containerJolokia(jolokia, id, function (containerJolokia) {
                        return onContainerJolokia(containerJolokia, container);
                    });
                }
            });

            //$scope.graph = graphBuilder.buildGraph();
            Core.$apply($scope);
        }

        /**
        * Returns true if the given CamelContext ID matches the current search filter
        */
        function matchesContextId(contextId) {
            if (contextId) {
                return !$scope.searchFilter || contextId.indexOf($scope.searchFilter) >= 0;
            }
            return false;
        }

        function onContainerJolokia(containerJolokia, container) {
            if (containerJolokia) {
                container.jolokia = containerJolokia;
                var containerId = container.id || "local";
                var idPrefix = containerId + ":";

                var endpointUriToObject = {};
                var startedLoadMetaDataFromEndpointMBeans = false;

                function getOrCreateRoute(objectName, properties, addEndpointLink, routeId, contextId, camelContext) {
                    if (typeof routeId === "undefined") { routeId = null; }
                    if (typeof contextId === "undefined") { contextId = null; }
                    if (typeof camelContext === "undefined") { camelContext = null; }
                    if (!objectName) {
                        // lets try guess the mbean name
                        objectName = Camel.jmxDomain + ':context=' + contextId + ',type=routes,name="' + routeId + '"';
                    }
                    var details = Core.parseMBean(objectName);
                    var attributes = details['attributes'];
                    var contextId = attributes["context"];
                    if (!routeId) {
                        routeId = trimQuotes(attributes["name"]);
                    }
                    attributes["routeId"] = routeId;
                    attributes["mbean"] = objectName;
                    attributes["container"] = container;
                    attributes["type"] = "route";

                    var route = null;
                    if (routeId && matchesContextId(contextId)) {
                        route = getOrAddNode("route", idPrefix + routeId, attributes, function () {
                            return {
                                name: routeId,
                                typeLabel: "Route",
                                container: container,
                                objectName: objectName,
                                jolokia: containerJolokia,
                                popup: {
                                    title: "Route: " + routeId,
                                    content: "<p>context: " + contextId + "</p>"
                                }
                            };
                        });
                        if (addEndpointLink) {
                            var uri = properties["EndpointUri"];
                            if (uri && route) {
                                var endpoint = null;
                                var escaledUrl = Camel.escapeEndpointUriNameForJmx(uri);
                                var urlsToTry = [uri, escaledUrl];

                                angular.forEach(urlsToTry, function (key) {
                                    if (!endpoint) {
                                        endpoint = endpointUriToObject[key];
                                    }
                                });
                                if (!endpoint) {
                                    angular.forEach(urlsToTry, function (key) {
                                        if (!endpoint) {
                                            var idx = key.lastIndexOf("?");
                                            if (idx > 0) {
                                                var prefix = key.substring(0, idx);
                                                endpoint = endpointUriToObject[prefix];
                                            }
                                        }
                                    });
                                }
                                addLink(route, endpoint, "consumer");
                            }
                        }
                        if ($scope.viewSettings.route && $scope.viewSettings.context) {
                            if (!camelContext) {
                                camelContext = getOrCreateCamelContext(contextId);
                            }
                            addLink(camelContext, route, "route");
                        }
                    }
                    return route;
                }

                function getOrCreateEndpoint(objectName, uri, contextId) {
                    if (typeof uri === "undefined") { uri = null; }
                    if (typeof contextId === "undefined") { contextId = null; }
                    if (!objectName) {
                        // lets try guess the mbean name
                        objectName = Camel.jmxDomain + ':context=' + contextId + ',type=endpoints,name="' + Camel.escapeEndpointUriNameForJmx(uri) + '"';
                    }
                    var details = Core.parseMBean(objectName);
                    var attributes = details['attributes'];

                    //log.info("attributes: " + angular.toJson(attributes));
                    var contextId = attributes["context"];
                    if (!uri) {
                        uri = trimQuotes(attributes["name"]);
                    }
                    attributes["uri"] = uri;
                    attributes["mbean"] = objectName;
                    attributes["container"] = container;
                    attributes["contextId"] = contextId;

                    var endpoint = null;
                    if (uri && matchesContextId(contextId)) {
                        endpoint = getOrAddNode("endpoint", idPrefix + uri, attributes, function () {
                            return {
                                name: uri,
                                typeLabel: "Endpoint",
                                container: container,
                                objectName: objectName,
                                jolokia: containerJolokia,
                                popup: {
                                    title: "Endpoint: " + uri,
                                    content: "<p>context: " + contextId + "</p>"
                                }
                            };
                        });
                        if (endpoint) {
                            endpointUriToObject[uri] = endpoint;
                        }
                    }
                    return endpoint;
                }

                // lets use the old way for pre-camel 2.13 versions
                function loadMetaDataFromEndpointMBeans() {
                    // find routes
                    if ($scope.viewSettings.route) {
                        containerJolokia.request({ type: "read", mbean: "org.apache.camel:type=routes,*", attribute: ["EndpointUri"] }, onSuccess(function (response) {
                            angular.forEach(response.value, function (properties, objectName) {
                                getOrCreateRoute(objectName, properties, true);
                            });
                            graphModelUpdated();
                        }));
                    }

                    if ($scope.viewSettings.endpoint) {
                        containerJolokia.search("org.apache.camel:type=endpoints,*", onSuccess(function (response) {
                            angular.forEach(response, function (objectName) {
                                var endpoint = getOrCreateEndpoint(objectName);
                                var camelContext = getOrCreateCamelContext(null, objectName);
                                addLink(camelContext, endpoint, "endpoint");
                            });
                            graphModelUpdated();
                        }));
                    }
                }

                function getOrCreateCamelContext(contextId, contextMBean) {
                    if (typeof contextMBean === "undefined") { contextMBean = null; }
                    var answer = null;
                    if (matchesContextId(contextId)) {
                        if (!contextMBean) {
                            // try guess the mbean name
                            contextMBean = Camel.jmxDomain + ':context=' + contextId + ',type=context,name="' + contextId + '"';
                        }
                        if (!contextId && contextMBean) {
                            var details = Core.parseMBean(contextMBean);
                            var attributes = details['attributes'];
                            contextId = attributes["context"];
                        }
                        var contextAttributes = {
                            contextId: contextId
                        };

                        if ($scope.viewSettings.context) {
                            answer = getOrAddNode("context", idPrefix + contextId, contextAttributes, function () {
                                return {
                                    name: contextId,
                                    typeLabel: "CamelContext",
                                    container: container,
                                    objectName: contextMBean,
                                    jolokia: containerJolokia,
                                    popup: {
                                        title: "CamelContext: " + contextId,
                                        content: ""
                                    }
                                };
                            });
                        }

                        // lets try out the new Camel 2.13 API to find endpoint usage...
                        containerJolokia.execute(contextMBean, "createRouteStaticEndpointJson", onSuccess(function (response) {
                            if (angular.isString(response)) {
                                var text = response;
                                var data = null;
                                try  {
                                    data = JSON.parse(text);
                                } catch (e) {
                                    // there's a bug in 2.13.0 - lets try trimming the final '}' to see if that makes it valid json ;)
                                    text = Core.trimTrailing(text.trim(), "}");
                                    try  {
                                        data = JSON.parse(text);
                                    } catch (e2) {
                                        Camel.log.debug("Ignored invalid json: " + e + " from text: " + response);
                                    }
                                }
                            }
                            if (data) {
                                angular.forEach(data["routes"], function (routeData, routeId) {
                                    angular.forEach(routeData["inputs"], function (inputEndpoint) {
                                        var inputUri = inputEndpoint["uri"];
                                        if (inputUri) {
                                            var route = getOrCreateRoute(null, {}, false, routeId, contextId, answer);
                                            var input = getOrCreateEndpoint(null, inputUri, contextId);
                                            var nextStep = route;
                                            addLink(input, route, "endpoint");
                                            angular.forEach(routeData["outputs"], function (outputEndpoint) {
                                                var outputUri = outputEndpoint["uri"];
                                                if (outputUri) {
                                                    var output = getOrCreateEndpoint(null, outputUri, contextId);
                                                    addLink(nextStep, output, "endpoint");
                                                    nextStep = output;
                                                }
                                            });
                                        }
                                    });
                                });
                                Camel.log.info("Updating graph model!");
                                graphModelUpdated();
                            }
                        }, {
                            error: function (response) {
                                // probably a pre-2.13 Camel implementation so lets use the old way
                                if (!startedLoadMetaDataFromEndpointMBeans) {
                                    startedLoadMetaDataFromEndpointMBeans = true;
                                    loadMetaDataFromEndpointMBeans();
                                }
                            }
                        }));
                    }
                    return answer;
                }

                containerJolokia.search("org.apache.camel:type=context,*", onSuccess(function (response) {
                    angular.forEach(response, function (objectName) {
                        var details = Core.parseMBean(objectName);
                        var attributes = details['attributes'];
                        var contextId = attributes["context"];
                        var uri = trimQuotes(attributes["name"]);
                        getOrCreateCamelContext(contextId, objectName);
                    });
                    //graphModelUpdated();
                }));
            }
        }

        function graphModelUpdated() {
            $scope.graph = graphBuilder.buildGraph();
            Core.$apply($scope);
        }

        function getOrAddNode(typeName, id, properties, createFn) {
            var node = null;
            if (id) {
                var nodeId = typeName + ":" + id;
                node = graphBuilder.getNode(nodeId);
                if (!node) {
                    var nodeValues = createFn();
                    node = angular.copy(properties);
                    angular.forEach(nodeValues, function (value, key) {
                        return node[key] = value;
                    });

                    node['id'] = nodeId;
                    if (!node['type']) {
                        node['type'] = typeName;
                    }
                    if (!node['name']) {
                        node['name'] = id;
                    }
                    if (node) {
                        var size = $scope.shapeSize[typeName];
                        if (size && !node['size']) {
                            node['size'] = size;
                        }
                        if (!node['summary']) {
                            node['summary'] = node['popup'] || "";
                        }
                        if (!$scope.viewSettings.popup) {
                            delete node['popup'];
                        }
                        if (!$scope.viewSettings.label) {
                            delete node['name'];
                        }

                        // lets not add nodes which are defined as being disabled
                        var enabled = $scope.viewSettings[typeName];
                        if (enabled || !angular.isDefined(enabled)) {
                            //log.info("==== Adding node " + nodeId + " of type + " + typeName);
                            graphBuilder.addNode(node);
                        } else {
                            //log.info("Ignoring node " + nodeId + " of type + " + typeName);
                        }
                    }
                }
            }
            return node;
        }

        function addLink(object1, object2, linkType) {
            if (object1 && object2) {
                addLinkIds(object1.id, object2.id, linkType);
            }
        }

        function addLinkIds(id1, id2, linkType) {
            if (id1 && id2) {
                //log.info("==== Linking " + id1 + " to " + id2);
                graphBuilder.addLink(id1, id2, linkType);
            }
        }

        /**
        * Avoid the JMX type property clashing with the ForceGraph type property; used for associating css classes with nodes on the graph
        *
        * @param properties
        */
        function renameTypeProperty(properties) {
            properties.mbeanType = properties['type'];
            delete properties['type'];
        }

        function configureEndpointProperties(properties) {
            renameTypeProperty(properties);
            var destinationType = properties.destinationType || "Queue";
            var typeName = destinationType.toLowerCase();
            properties.isQueue = !typeName.startsWith("t");
            properties['destType'] = typeName;
        }
    }
    Camel.FabricDiagramController = FabricDiagramController;
})(Camel || (Camel = {}));
/// <reference path="../../jmx/js/jmxHelpers.ts"/>
/**
* That magic <reference> in the top of this file must be there!!!
*
* @module Camel
* @main Camel
*/
var Camel;
(function (Camel) {
    var jmxModule = Jmx;

    var pluginName = 'camel';

    var routeToolBar = "app/camel/html/attributeToolBarRoutes.html";
    var contextToolBar = "app/camel/html/attributeToolBarContext.html";

    angular.module(pluginName, [
        'bootstrap', 'ui.bootstrap',
        'ui.bootstrap.dialog', 'ui.bootstrap.tabs', 'ui.bootstrap.typeahead', 'ngResource', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/camel/browseEndpoint', { templateUrl: 'app/camel/html/browseEndpoint.html' }).when('/camel/endpoint/browse/:contextId/*endpointPath', { templateUrl: 'app/camel/html/browseEndpoint.html' }).when('/camel/createEndpoint', { templateUrl: 'app/camel/html/createEndpoint.html' }).when('/camel/route/diagram/:contextId/:routeId', { templateUrl: 'app/camel/html/routes.html' }).when('/camel/routes', { templateUrl: 'app/camel/html/routes.html' }).when('/camel/fabricDiagram', { templateUrl: 'app/camel/html/fabricDiagram.html', reloadOnSearch: false }).when('/camel/typeConverter', { templateUrl: 'app/camel/html/typeConverter.html', reloadOnSearch: false }).when('/camel/sendMessage', { templateUrl: 'app/camel/html/sendMessage.html', reloadOnSearch: false }).when('/camel/source', { templateUrl: 'app/camel/html/source.html' }).when('/camel/traceRoute', { templateUrl: 'app/camel/html/traceRoute.html' }).when('/camel/debugRoute', { templateUrl: 'app/camel/html/debug.html' }).when('/camel/profileRoute', { templateUrl: 'app/camel/html/profileRoute.html' }).when('/camel/properties', { templateUrl: 'app/camel/html/properties.html' });
    }).factory('tracerStatus', function () {
        return {
            jhandle: null,
            messages: []
        };
    }).filter('camelIconClass', function () {
        return Camel.iconClass;
    }).run(function (workspace, jolokia, viewRegistry, layoutFull, helpRegistry, preferencesRegistry) {
        viewRegistry['camel/endpoint/'] = layoutFull;
        viewRegistry['camel/route/'] = layoutFull;
        viewRegistry['camel/fabricDiagram'] = layoutFull;
        viewRegistry['camel'] = 'app/camel/html/layoutCamelTree.html';

        helpRegistry.addUserDoc('camel', 'app/camel/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties(Camel.jmxDomain);
        });
        preferencesRegistry.addTab('Camel', 'app/camel/html/preferences.html', function () {
            return workspace.treeContainsDomainAndProperties(Camel.jmxDomain);
        });

        Jmx.addAttributeToolBar(pluginName, Camel.jmxDomain, function (selection) {
            // TODO there should be a nicer way to do this!
            var typeName = selection.typeName;
            if (typeName) {
                if (typeName.startsWith("context"))
                    return contextToolBar;
                if (typeName.startsWith("route"))
                    return routeToolBar;
            }
            var folderNames = selection.folderNames;
            if (folderNames && selection.domain === Camel.jmxDomain) {
                var last = folderNames.last();
                if ("routes" === last)
                    return routeToolBar;
                if ("context" === last)
                    return contextToolBar;
            }
            return null;
        });

        // register default attribute views
        var stateField = 'State';
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(\'' + stateField + '\') | camelIconClass}}"></i></div>';
        var stateColumn = {
            field: stateField, displayName: stateField,
            cellTemplate: stateTemplate,
            width: 56,
            minWidth: 56,
            maxWidth: 56,
            resizable: false,
            defaultSort: false
        };

        var attributes = workspace.attributeColumnDefs;
        attributes[Camel.jmxDomain + "/context/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'Uptime', displayName: 'Uptime', visible: false },
            { field: 'CamelVersion', displayName: 'Version', visible: false },
            { field: 'ExchangesCompleted', displayName: 'Completed #' },
            { field: 'ExchangesFailed', displayName: 'Failed #' },
            { field: 'ExchangesTotal', displayName: 'Total #', visible: false },
            { field: 'InflightExchanges', displayName: 'Inflight #' },
            { field: 'MeanProcessingTime', displayName: 'Mean Time' },
            { field: 'MinProcessingTime', displayName: 'Min Time' },
            { field: 'MaxProcessingTime', displayName: 'Max Time' },
            { field: 'TotalProcessingTime', displayName: 'Total Time', visible: false },
            { field: 'DeltaProcessingTime', displayName: 'Delta Time', visible: false },
            { field: 'LastExchangeCompletedTimestamp', displayName: 'Last completed', visible: false },
            { field: 'LastExchangeFailedTimestamp', displayName: 'Last failed', visible: false }
        ];
        attributes[Camel.jmxDomain + "/routes/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'RouteId', displayName: 'Route' },
            { field: 'ExchangesCompleted', displayName: 'Completed #' },
            { field: 'ExchangesFailed', displayName: 'Failed #' },
            { field: 'ExchangesTotal', displayName: 'Total #', visible: false },
            { field: 'InflightExchanges', displayName: 'Inflight #' },
            { field: 'MeanProcessingTime', displayName: 'Mean Time' },
            { field: 'MinProcessingTime', displayName: 'Min Time' },
            { field: 'MaxProcessingTime', displayName: 'Max Time' },
            { field: 'TotalProcessingTime', displayName: 'Total Time', visible: false },
            { field: 'DeltaProcessingTime', displayName: 'Delta Time', visible: false },
            { field: 'LastExchangeCompletedTimestamp', displayName: 'Last completed', visible: false },
            { field: 'LastExchangeFailedTimestamp', displayName: 'Last failed', visible: false }
        ];
        attributes[Camel.jmxDomain + "/processors/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'RouteId', displayName: 'Route' },
            { field: 'ProcessorId', displayName: 'Processor' },
            { field: 'ExchangesCompleted', displayName: 'Completed #' },
            { field: 'ExchangesFailed', displayName: 'Failed #' },
            { field: 'ExchangesTotal', displayName: 'Total #', visible: false },
            { field: 'InflightExchanges', displayName: 'Inflight #' },
            { field: 'MeanProcessingTime', displayName: 'Mean Time' },
            { field: 'MinProcessingTime', displayName: 'Min Time' },
            { field: 'MaxProcessingTime', displayName: 'Max Time' },
            { field: 'TotalProcessingTime', displayName: 'Total Time', visible: false },
            { field: 'DeltaProcessingTime', displayName: 'Delta Time', visible: false },
            { field: 'LastExchangeCompletedTimestamp', displayName: 'Last completed', visible: false },
            { field: 'LastExchangeFailedTimestamp', displayName: 'Last failed', visible: false }
        ];
        attributes[Camel.jmxDomain + "/components/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'ComponentName', displayName: 'Name' }
        ];
        attributes[Camel.jmxDomain + "/consumers/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'RouteId', displayName: 'Route' },
            { field: 'EndpointUri', displayName: 'Endpoint URI', width: "**" },
            { field: 'Suspended', displayName: 'Suspended', resizable: false },
            { field: 'InflightExchanges', displayName: 'Inflight #' }
        ];
        attributes[Camel.jmxDomain + "/services/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'RouteId', displayName: 'Route' },
            { field: 'Suspended', displayName: 'Suspended', resizable: false },
            { field: 'SupportsSuspended', displayName: 'Can Suspend', resizable: false }
        ];
        attributes[Camel.jmxDomain + "/endpoints/folder"] = [
            stateColumn,
            { field: 'CamelId', displayName: 'Context' },
            { field: 'EndpointUri', displayName: 'Endpoint URI', width: "***" },
            { field: 'Singleton', displayName: 'Singleton', resizable: false }
        ];
        attributes[Camel.jmxDomain + "/threadpools/folder"] = [
            { field: 'Id', displayName: 'Id', width: "**" },
            { field: 'ActiveCount', displayName: 'Active #' },
            { field: 'PoolSize', displayName: 'Pool Size' },
            { field: 'CorePoolSize', displayName: 'Core Pool Size' },
            { field: 'TaskQueueSize', displayName: 'Task Queue Size' },
            { field: 'TaskCount', displayName: 'Task #' },
            { field: 'CompletedTaskCount', displayName: 'Completed Task #' }
        ];
        attributes[Camel.jmxDomain + "/errorhandlers/folder"] = [
            { field: 'CamelId', displayName: 'Context' },
            { field: 'DeadLetterChannel', displayName: 'Dead Letter' },
            { field: 'DeadLetterChannelEndpointUri', displayName: 'Endpoint URI', width: "**", resizable: true },
            { field: 'MaximumRedeliveries', displayName: 'Max Redeliveries' },
            { field: 'RedeliveryDelay', displayName: 'Redelivery Delay' },
            { field: 'MaximumRedeliveryDelay', displayName: 'Max Redeliveries Delay' }
        ];

        workspace.topLevelTabs.push({
            id: "camel",
            content: "Camel",
            title: "Manage your Apache Camel applications",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties(Camel.jmxDomain);
            },
            href: function () {
                return "#/jmx/attributes?tab=camel";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("camel");
            }
        });

        // add sub level tabs
        workspace.subLevelTabs.push({
            content: '<i class="icon-picture"></i> Diagram',
            title: "View a diagram of the Camel routes",
            isValid: function (workspace) {
                return workspace.isRoute();
            },
            href: function () {
                return "#/camel/routes";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-picture"></i> Diagram',
            title: "View the entire JVMs Camel flows",
            isValid: function (workspace) {
                return workspace.isTopTabActive("camel") && !workspace.isRoute();
            },
            href: function () {
                return "#/camel/fabricDiagram";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class=" icon-file-alt"></i> Source',
            title: "View the source of the Camel routes",
            isValid: function (workspace) {
                return !workspace.isEndpointsFolder() && (workspace.isRoute() || workspace.isRoutesFolder() || workspace.isCamelContext());
            },
            href: function () {
                return "#/camel/source";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class=" icon-edit"></i> Properties',
            title: "View the pattern properties",
            isValid: function (workspace) {
                return Camel.getSelectedRouteNode(workspace);
            },
            href: function () {
                return "#/camel/properties";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-list"></i> Type Converters',
            title: "List all the type converters registered in the context",
            isValid: function (workspace) {
                return workspace.isTopTabActive("camel") && !workspace.isRoute() && Camel.isCamelVersionEQGT(2, 13, workspace, jolokia);
            },
            href: function () {
                return "#/camel/typeConverter";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-envelope"></i> Browse',
            title: "Browse the messages on the endpoint",
            isValid: function (workspace) {
                return workspace.isEndpoint();
            },
            href: function () {
                return "#/camel/browseEndpoint";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-stethoscope"></i> Debug',
            title: "Debug the Camel route",
            isValid: function (workspace) {
                return workspace.isRoute() && Camel.getSelectionCamelDebugMBean(workspace);
            },
            href: function () {
                return "#/camel/debugRoute";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-envelope"></i> Trace',
            title: "Trace the messages flowing through the Camel route",
            isValid: function (workspace) {
                return workspace.isRoute() && Camel.getSelectionCamelTraceMBean(workspace);
            },
            href: function () {
                return "#/camel/traceRoute";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-bar-chart"></i> Profile',
            title: "Profile the messages flowing through the Camel route",
            isValid: function (workspace) {
                return workspace.isRoute() && Camel.getSelectionCamelTraceMBean(workspace);
            },
            href: function () {
                return "#/camel/profileRoute";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-pencil"></i> Send',
            title: "Send a message to this endpoint",
            isValid: function (workspace) {
                return workspace.isEndpoint();
            },
            href: function () {
                return "#/camel/sendMessage";
            }
        });
        workspace.subLevelTabs.push({
            content: '<i class="icon-plus"></i> Endpoint',
            title: "Create a new endpoint",
            isValid: function (workspace) {
                return workspace.isEndpointsFolder();
            },
            href: function () {
                return "#/camel/createEndpoint";
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);

    // register the jmx lazy loader here as it won't have been invoked in the run method
    hawtioPluginLoader.registerPreBootstrapTask(function (task) {
        jmxModule.registerLazyLoadHandler(Camel.jmxDomain, function (folder) {
            if (Camel.jmxDomain === folder.domain && "routes" === folder.typeName) {
                return function (workspace, folder, onComplete) {
                    if ("routes" === folder.typeName) {
                        Camel.processRouteXml(workspace, workspace.jolokia, folder, function (route) {
                            if (route) {
                                Camel.addRouteChildren(folder, route);
                            }
                            onComplete();
                        });
                    } else {
                        onComplete();
                    }
                };
            }
            return null;
        });
        task();
    });
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    function TraceRouteController($scope, workspace, jolokia, localStorage, tracerStatus) {
        var log = Logger.get("CamelTracer");

        $scope.camelMaximumTraceOrDebugBodyLength = Camel.maximumTraceOrDebugBodyLength(localStorage);
        $scope.tracing = false;
        $scope.messages = [];
        $scope.graphView = null;
        $scope.tableView = null;
        $scope.mode = 'text';

        $scope.messageDialog = new UI.Dialog();

        $scope.gridOptions = Camel.createBrowseGridOptions();
        $scope.gridOptions.selectWithCheckboxOnly = false;
        $scope.gridOptions.showSelectionCheckbox = false;
        $scope.gridOptions.multiSelect = false;
        $scope.gridOptions.afterSelectionChange = onSelectionChanged;
        $scope.gridOptions.columnDefs.push({
            field: 'toNode',
            displayName: 'To Node'
        });

        $scope.startTracing = function () {
            log.info("Start tracing");
            setTracing(true);
        };

        $scope.stopTracing = function () {
            log.info("Stop tracing");
            setTracing(false);
        };

        $scope.clear = function () {
            log.debug("Clear messages");
            tracerStatus.messages = [];
            $scope.messages = [];
            Core.$apply($scope);
        };

        $scope.$watch('workspace.selection', function () {
            if (workspace.moveIfViewInvalid()) {
                return;
            }
            $scope.messages = tracerStatus.messages;
            reloadTracingFlag();
        });

        // TODO can we share these 2 methods from activemq browse / camel browse / came trace?
        $scope.openMessageDialog = function (message) {
            var idx = Core.pathGet(message, ["rowIndex"]);
            $scope.selectRowIndex(idx);
            if ($scope.row) {
                $scope.mode = CodeEditor.detectTextFormat($scope.row.body);
                $scope.messageDialog.open();
            }
        };

        $scope.selectRowIndex = function (idx) {
            $scope.rowIndex = idx;
            var selected = $scope.gridOptions.selectedItems;
            selected.splice(0, selected.length);
            if (idx >= 0 && idx < $scope.messages.length) {
                $scope.row = $scope.messages[idx];
                if ($scope.row) {
                    selected.push($scope.row);
                }
            } else {
                $scope.row = null;
            }
            onSelectionChanged();
        };

        function reloadTracingFlag() {
            $scope.tracing = false;

            // clear any previous polls
            if (tracerStatus.jhandle != null) {
                log.debug("Unregistering jolokia handle");
                jolokia.unregister(tracerStatus.jhandle);
                tracerStatus.jhandle = null;
            }

            var mbean = Camel.getSelectionCamelTraceMBean(workspace);
            if (mbean) {
                $scope.tracing = jolokia.getAttribute(mbean, "Enabled", onSuccess(null));

                if ($scope.tracing) {
                    var traceMBean = mbean;
                    if (traceMBean) {
                        // register callback for doing live update of tracing
                        if (tracerStatus.jhandle === null) {
                            log.debug("Registering jolokia handle");
                            tracerStatus.jhandle = jolokia.register(populateRouteMessages, {
                                type: 'exec', mbean: traceMBean,
                                operation: 'dumpAllTracedMessagesAsXml()',
                                ignoreErrors: true,
                                arguments: []
                            });
                        }
                    }
                    $scope.graphView = "app/camel/html/routes.html";
                    $scope.tableView = "app/camel/html/browseMessages.html";
                } else {
                    tracerStatus.messages = [];
                    $scope.messages = [];
                    $scope.graphView = null;
                    $scope.tableView = null;
                }
            }
        }

        function populateRouteMessages(response) {
            log.debug("Populating response " + response);

            // filter messages due CAMEL-7045 but in camel-core
            // see https://github.com/hawtio/hawtio/issues/292
            var selectedRouteId = Camel.getSelectedRouteId(workspace);

            var xml = response.value;
            if (angular.isString(xml)) {
                // lets parse the XML DOM here...
                var doc = $.parseXML(xml);
                var allMessages = $(doc).find("fabricTracerEventMessage");
                if (!allMessages || !allMessages.length) {
                    // lets try find another element name
                    allMessages = $(doc).find("backlogTracerEventMessage");
                }

                allMessages.each(function (idx, message) {
                    var routeId = $(message).find("routeId").text();
                    if (routeId === selectedRouteId) {
                        var messageData = Camel.createMessageFromXml(message);
                        var toNode = $(message).find("toNode").text();
                        if (toNode) {
                            messageData["toNode"] = toNode;
                        }
                        log.debug("Adding new message to trace table with id " + messageData["id"]);
                        $scope.messages.push(messageData);
                    }
                });

                // keep state of the traced messages on tracerStatus
                tracerStatus.messages = $scope.messages;
                Core.$apply($scope);
            }
        }

        function onSelectionChanged() {
            angular.forEach($scope.gridOptions.selectedItems, function (selected) {
                if (selected) {
                    var toNode = selected["toNode"];
                    if (toNode) {
                        // lets highlight the node in the diagram
                        var nodes = d3.select("svg").selectAll("g .node");
                        Camel.highlightSelectedNode(nodes, toNode);
                    }
                }
            });
        }

        function tracingChanged(response) {
            reloadTracingFlag();
            Core.$apply($scope);
        }

        function setTracing(flag) {
            var mbean = Camel.getSelectionCamelTraceMBean(workspace);
            if (mbean) {
                // set max only supported on BacklogTracer
                // (the old fabric tracer does not support max length)
                if (mbean.toString().endsWith("BacklogTracer")) {
                    var max = $scope.camelMaximumTraceOrDebugBodyLength;
                    jolokia.setAttribute(mbean, "BodyMaxChars", max);
                }
                jolokia.setAttribute(mbean, "Enabled", flag, onSuccess(tracingChanged));
            }
        }

        log.info("Re-activating tracer with " + tracerStatus.messages.length + " existing messages");
        $scope.messages = tracerStatus.messages;
        $scope.tracing = tracerStatus.jhandle != null;
    }
    Camel.TraceRouteController = TraceRouteController;
})(Camel || (Camel = {}));
var Camel;
(function (Camel) {
    // NOTE this file is code generated by the ide-codegen module in Fuse IDE
    Camel.camelHeaderSchema = {
        definitions: {
            headers: {
                properties: {
                    "CamelAuthentication": {
                        type: "java.lang.String"
                    },
                    "CamelAuthenticationFailurePolicyId": {
                        type: "java.lang.String"
                    },
                    "CamelAcceptContentType": {
                        type: "java.lang.String"
                    },
                    "CamelAggregatedSize": {
                        type: "java.lang.String"
                    },
                    "CamelAggregatedTimeout": {
                        type: "java.lang.String"
                    },
                    "CamelAggregatedCompletedBy": {
                        type: "java.lang.String"
                    },
                    "CamelAggregatedCorrelationKey": {
                        type: "java.lang.String"
                    },
                    "CamelAggregationStrategy": {
                        type: "java.lang.String"
                    },
                    "CamelAggregationCompleteAllGroups": {
                        type: "java.lang.String"
                    },
                    "CamelAggregationCompleteAllGroupsInclusive": {
                        type: "java.lang.String"
                    },
                    "CamelAsyncWait": {
                        type: "java.lang.String"
                    },
                    "CamelBatchIndex": {
                        type: "java.lang.String"
                    },
                    "CamelBatchSize": {
                        type: "java.lang.String"
                    },
                    "CamelBatchComplete": {
                        type: "java.lang.String"
                    },
                    "CamelBeanMethodName": {
                        type: "java.lang.String"
                    },
                    "CamelBeanMultiParameterArray": {
                        type: "java.lang.String"
                    },
                    "CamelBinding": {
                        type: "java.lang.String"
                    },
                    "breadcrumbId": {
                        type: "java.lang.String"
                    },
                    "CamelCharsetName": {
                        type: "java.lang.String"
                    },
                    "CamelCreatedTimestamp": {
                        type: "java.lang.String"
                    },
                    "Content-Encoding": {
                        type: "java.lang.String"
                    },
                    "Content-Length": {
                        type: "java.lang.String"
                    },
                    "Content-Type": {
                        type: "java.lang.String"
                    },
                    "CamelCorrelationId": {
                        type: "java.lang.String"
                    },
                    "CamelDataSetIndex": {
                        type: "java.lang.String"
                    },
                    "org.apache.camel.default.charset": {
                        type: "java.lang.String"
                    },
                    "CamelDestinationOverrideUrl": {
                        type: "java.lang.String"
                    },
                    "CamelDisableHttpStreamCache": {
                        type: "java.lang.String"
                    },
                    "CamelDuplicateMessage": {
                        type: "java.lang.String"
                    },
                    "CamelExceptionCaught": {
                        type: "java.lang.String"
                    },
                    "CamelExceptionHandled": {
                        type: "java.lang.String"
                    },
                    "CamelEvaluateExpressionResult": {
                        type: "java.lang.String"
                    },
                    "CamelErrorHandlerHandled": {
                        type: "java.lang.String"
                    },
                    "CamelExternalRedelivered": {
                        type: "java.lang.String"
                    },
                    "CamelFailureHandled": {
                        type: "java.lang.String"
                    },
                    "CamelFailureEndpoint": {
                        type: "java.lang.String"
                    },
                    "CamelFailureRouteId": {
                        type: "java.lang.String"
                    },
                    "CamelFilterNonXmlChars": {
                        type: "java.lang.String"
                    },
                    "CamelFileLocalWorkPath": {
                        type: "java.lang.String"
                    },
                    "CamelFileName": {
                        type: "java.lang.String"
                    },
                    "CamelFileNameOnly": {
                        type: "java.lang.String"
                    },
                    "CamelFileNameProduced": {
                        type: "java.lang.String"
                    },
                    "CamelFileNameConsumed": {
                        type: "java.lang.String"
                    },
                    "CamelFilePath": {
                        type: "java.lang.String"
                    },
                    "CamelFileParent": {
                        type: "java.lang.String"
                    },
                    "CamelFileLastModified": {
                        type: "java.lang.String"
                    },
                    "CamelFileLength": {
                        type: "java.lang.String"
                    },
                    "CamelFilterMatched": {
                        type: "java.lang.String"
                    },
                    "CamelFileLockFileAcquired": {
                        type: "java.lang.String"
                    },
                    "CamelFileLockFileName": {
                        type: "java.lang.String"
                    },
                    "CamelGroupedExchange": {
                        type: "java.lang.String"
                    },
                    "CamelHttpBaseUri": {
                        type: "java.lang.String"
                    },
                    "CamelHttpCharacterEncoding": {
                        type: "java.lang.String"
                    },
                    "CamelHttpMethod": {
                        type: "java.lang.String"
                    },
                    "CamelHttpPath": {
                        type: "java.lang.String"
                    },
                    "CamelHttpProtocolVersion": {
                        type: "java.lang.String"
                    },
                    "CamelHttpQuery": {
                        type: "java.lang.String"
                    },
                    "CamelHttpResponseCode": {
                        type: "java.lang.String"
                    },
                    "CamelHttpUri": {
                        type: "java.lang.String"
                    },
                    "CamelHttpUrl": {
                        type: "java.lang.String"
                    },
                    "CamelHttpChunked": {
                        type: "java.lang.String"
                    },
                    "CamelHttpServletRequest": {
                        type: "java.lang.String"
                    },
                    "CamelHttpServletResponse": {
                        type: "java.lang.String"
                    },
                    "CamelInterceptedEndpoint": {
                        type: "java.lang.String"
                    },
                    "CamelInterceptSendToEndpointWhenMatched": {
                        type: "java.lang.String"
                    },
                    "CamelLanguageScript": {
                        type: "java.lang.String"
                    },
                    "CamelLogDebugBodyMaxChars": {
                        type: "java.lang.String"
                    },
                    "CamelLogDebugStreams": {
                        type: "java.lang.String"
                    },
                    "CamelLoopIndex": {
                        type: "java.lang.String"
                    },
                    "CamelLoopSize": {
                        type: "java.lang.String"
                    },
                    "CamelMaximumCachePoolSize": {
                        type: "java.lang.String"
                    },
                    "CamelMaximumEndpointCacheSize": {
                        type: "java.lang.String"
                    },
                    "CamelMessageHistory": {
                        type: "java.lang.String"
                    },
                    "CamelMulticastIndex": {
                        type: "java.lang.String"
                    },
                    "CamelMulticastComplete": {
                        type: "java.lang.String"
                    },
                    "CamelNotifyEvent": {
                        type: "java.lang.String"
                    },
                    "CamelOnCompletion": {
                        type: "java.lang.String"
                    },
                    "CamelOverruleFileName": {
                        type: "java.lang.String"
                    },
                    "CamelParentUnitOfWork": {
                        type: "java.lang.String"
                    },
                    "CamelRecipientListEndpoint": {
                        type: "java.lang.String"
                    },
                    "CamelReceivedTimestamp": {
                        type: "java.lang.String"
                    },
                    "CamelRedelivered": {
                        type: "java.lang.String"
                    },
                    "CamelRedeliveryCounter": {
                        type: "java.lang.String"
                    },
                    "CamelRedeliveryMaxCounter": {
                        type: "java.lang.String"
                    },
                    "CamelRedeliveryExhausted": {
                        type: "java.lang.String"
                    },
                    "CamelRedeliveryDelay": {
                        type: "java.lang.String"
                    },
                    "CamelRollbackOnly": {
                        type: "java.lang.String"
                    },
                    "CamelRollbackOnlyLast": {
                        type: "java.lang.String"
                    },
                    "CamelRouteStop": {
                        type: "java.lang.String"
                    },
                    "CamelSoapAction": {
                        type: "java.lang.String"
                    },
                    "CamelSkipGzipEncoding": {
                        type: "java.lang.String"
                    },
                    "CamelSlipEndpoint": {
                        type: "java.lang.String"
                    },
                    "CamelSplitIndex": {
                        type: "java.lang.String"
                    },
                    "CamelSplitComplete": {
                        type: "java.lang.String"
                    },
                    "CamelSplitSize": {
                        type: "java.lang.String"
                    },
                    "CamelTimerCounter": {
                        type: "java.lang.String"
                    },
                    "CamelTimerFiredTime": {
                        type: "java.lang.String"
                    },
                    "CamelTimerName": {
                        type: "java.lang.String"
                    },
                    "CamelTimerPeriod": {
                        type: "java.lang.String"
                    },
                    "CamelTimerTime": {
                        type: "java.lang.String"
                    },
                    "CamelToEndpoint": {
                        type: "java.lang.String"
                    },
                    "CamelTraceEvent": {
                        type: "java.lang.String"
                    },
                    "CamelTraceEventNodeId": {
                        type: "java.lang.String"
                    },
                    "CamelTraceEventTimestamp": {
                        type: "java.lang.String"
                    },
                    "CamelTraceEventExchange": {
                        type: "java.lang.String"
                    },
                    "Transfer-Encoding": {
                        type: "java.lang.String"
                    },
                    "CamelUnitOfWorkExhausted": {
                        type: "java.lang.String"
                    },
                    "CamelUnitOfWorkProcessSync": {
                        type: "java.lang.String"
                    },
                    "CamelXsltFileName": {
                        type: "java.lang.String"
                    }
                }
            }
        }
    };
})(Camel || (Camel = {}));
/**
* @module Log
*/
var Log;
(function (Log) {
    var log = Logger.get("Log");

    function LogController($scope, $routeParams, $location, localStorage, workspace, jolokia, $window, $document, $templateCache) {
        $scope.sortAsc = true;
        var value = localStorage["logSortAsc"];
        if (angular.isString(value)) {
            $scope.sortAsc = "true" === value;
        }
        $scope.autoScroll = true;
        value = localStorage["logAutoScroll"];
        if (angular.isString(value)) {
            $scope.autoScroll = "true" === value;
        }

        value = localStorage["logBatchSize"];
        $scope.logBatchSize = angular.isNumber(value) ? value : 20;

        $scope.logs = [];
        $scope.showRowDetails = false;
        $scope.showRaw = {
            expanded: false
        };

        var logQueryMBean = Log.findLogQueryMBean(workspace);

        $scope.init = function () {
            $scope.searchText = $routeParams['s'];

            if (!angular.isDefined($scope.searchText)) {
                $scope.searchText = '';
            }

            $scope.filter = {
                // The default logging level to show, empty string => show all
                logLevelQuery: $routeParams['l'],
                // The default value of the exact match logging filter
                logLevelExactMatch: Core.parseBooleanValue($routeParams['e']),
                // The default value of the search only in message field filter
                messageOnly: Core.parseBooleanValue($routeParams['o'])
            };

            if (!angular.isDefined($scope.filter.logLevelQuery)) {
                $scope.filter.logLevelQuery = '';
            }
            if (!angular.isDefined($scope.filter.logLevelExactMatch)) {
                $scope.filter.logLevelExactMatch = false;
            }
            if (!angular.isDefined($scope.filter.messageOnly)) {
                $scope.filter.messageOnly = false;
            }
        };

        $scope.$on('$routeUpdate', $scope.init);

        $scope.$watch('searchText', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search('s', newValue);
            }
        });

        $scope.$watch('filter.logLevelQuery', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search('l', newValue);
            }
        });

        $scope.$watch('filter.logLevelExactMatch', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search('e', newValue);
            }
        });

        $scope.$watch('filter.messageOnly', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search('o', newValue);
            }
        });

        $scope.init();

        $scope.toTime = 0;
        $scope.logFilter = {
            afterTimestamp: $scope.toTime,
            count: $scope.logBatchSize
        };
        $scope.logFilterJson = JSON.stringify($scope.logFilter);
        $scope.queryJSON = { type: "EXEC", mbean: logQueryMBean, operation: "jsonQueryLogResults", arguments: [$scope.logFilterJson], ignoreErrors: true };

        $scope.logLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"];
        $scope.logLevelMap = {};
        $scope.skipFields = ['seq'];

        angular.forEach($scope.logLevels, function (name, idx) {
            $scope.logLevelMap[name] = idx;
            $scope.logLevelMap[name.toLowerCase()] = idx;
        });

        $scope.selectedClass = function ($index) {
            if ($index === $scope.selectedRowIndex) {
                return 'selected';
            }
            return '';
        };

        $scope.$watch('selectedRowIndex', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (newValue < 0 || newValue > $scope.logs.length) {
                    $scope.selectedRow = null;
                    $scope.showRowDetails = false;
                    return;
                }
                Log.log.debug("New index: ", newValue);
                $scope.selectedRow = $scope.logs[newValue];
                if (!$scope.showRowDetails) {
                    $scope.showRowDetails = true;
                }
            }
        });

        $scope.hasOSGiProps = function (row) {
            if (!row) {
                return false;
            }
            if (!('properties' in row)) {
                return false;
            }
            var props = row.properties;
            var answer = Object.extended(props).keys().any(function (key) {
                return key.startsWith('bundle');
            });
            return answer;
        };

        $scope.selectRow = function ($index) {
            // in case the user clicks a row, closes the slideout and clicks
            // the row again
            if ($scope.selectedRowIndex == $index) {
                $scope.showRowDetails = true;
                return;
            }
            $scope.selectedRowIndex = $index;
        };

        $scope.getSelectedRowJson = function () {
            return angular.toJson($scope.selectedRow, true);
        };

        $scope.logClass = function (log) {
            if (!log) {
                return '';
            }
            return logLevelClass(log['level']);
        };

        $scope.logIcon = function (log) {
            if (!log) {
                return '';
            }
            var style = $scope.logClass(log);
            if (style === "error") {
                return "red icon-warning-sign";
            }
            if (style === "warning") {
                return "orange icon-exclamation-sign";
            }
            if (style === "info") {
                return "icon-info-sign";
            }
            return "icon-cog";
        };

        $scope.logSourceHref = Log.logSourceHref;

        $scope.hasLogSourceHref = function (row) {
            if (!row) {
                return false;
            }
            return Log.hasLogSourceHref(row);
        };

        $scope.hasLogSourceLineHref = function (row) {
            if (!row) {
                return false;
            }
            return Log.hasLogSourceLineHref(row);
        };

        $scope.dateFormat = 'yyyy-MM-dd HH:mm:ss';

        $scope.formatException = function (line) {
            return Log.formatStackLine(line);
        };

        $scope.getSupport = function () {
            if (!$scope.selectedRow) {
                return;
            }
            var uri = "https://access.redhat.com/knowledge/solutions";
            var text = $scope.selectedRow.message;
            var logger = $scope.selectedRow.logger;
            uri = uri + "?logger=" + logger + "&text=" + text;
            window.location.href = uri;
        };

        $scope.addToDashboardLink = function () {
            var href = "#/logs";
            var routeParams = angular.toJson($routeParams);
            var size = angular.toJson({
                size_x: 8,
                size_y: 1
            });
            var title = "Logs";
            if ($scope.filter.logLevelQuery !== "") {
                title = title + " LogLevel: " + $scope.filter.logLevelQuery;
            }
            if ($scope.filter.logLevelExactMatch) {
                title = title + " Exact Match";
            }
            if ($scope.searchText !== "") {
                title = title + " Filter: " + $scope.searchText;
            }
            if ($scope.filter.messageOnly) {
                title = title + " Message Only";
            }

            return "#/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&routeParams=" + encodeURIComponent(routeParams) + "&title=" + encodeURIComponent(title) + "&size=" + encodeURIComponent(size);
        };

        $scope.isInDashboardClass = function () {
            if (angular.isDefined($scope.inDashboard && $scope.inDashboard)) {
                return "log-table-dashboard";
            }
            return "";
        };

        $scope.sortIcon = function () {
            if ($scope.sortAsc) {
                return "icon-arrow-down";
            } else {
                return "icon-arrow-up";
            }
        };

        $scope.filterLogMessage = function (log) {
            var messageOnly = $scope.filter.messageOnly;

            if ($scope.filter.logLevelQuery !== "") {
                var logLevelExactMatch = $scope.filter.logLevelExactMatch;
                var logLevelQuery = $scope.filter.logLevelQuery;
                var logLevelQueryOrdinal = (logLevelExactMatch) ? 0 : $scope.logLevelMap[logLevelQuery];

                if (logLevelExactMatch) {
                    if (log.level !== logLevelQuery) {
                        return false;
                    }
                } else {
                    var idx = $scope.logLevelMap[log.level];
                    if (!(idx >= logLevelQueryOrdinal || idx < 0)) {
                        return false;
                    }
                }
            }

            if ($scope.searchText.startsWith("l=")) {
                return log.logger.has($scope.searchText.last($scope.searchText.length - 2));
            }
            if ($scope.searchText.startsWith("m=")) {
                return log.message.has($scope.searchText.last($scope.searchText.length - 2));
            }
            if (messageOnly) {
                return log.message.has($scope.searchText);
            }
            return log.logger.has($scope.searchText) || log.message.has($scope.searchText);
        };

        $scope.formatStackTrace = function (exception) {
            if (!exception) {
                return "";
            }
            var answer = '<ul class="unstyled">\n';
            exception.forEach(function (line) {
                answer = answer + '<li>' + $scope.formatException(line) + '</li>';
            });
            answer += '\n</ul>';
            return answer;
        };

        var updateValues = function (response) {
            var scrollToTopOrBottom = false;

            if (!$scope.inDashboard) {
                var window = $($window);

                if ($scope.logs.length === 0) {
                    // initial page load, let's scroll to the bottom
                    scrollToTopOrBottom = true;
                }

                if ($scope.sortAsc) {
                    var pos = window.scrollTop() + window.height();
                    var threshold = Core.getDocHeight() - 100;
                } else {
                    var pos = window.scrollTop() + window.height();
                    var threshold = 100;
                }
                if (pos > threshold) {
                    // page is scrolled near the bottom
                    scrollToTopOrBottom = true;
                }
            }

            var logs = response.events;

            //log.info("log returned " + (logs ? logs.length : 0) + " results for query: " + $scope.toTime  + " from json: " + $scope.logFilterJson);
            var toTime = response.toTimestamp;
            if (toTime && angular.isNumber(toTime)) {
                if (toTime < 0) {
                    // on JBoss we get odd values and never seem to get any log events!
                    console.log("ignoring dodgy value of toTime: " + toTime);
                } else {
                    $scope.toTime = toTime;
                    $scope.logFilter.afterTimestamp = $scope.toTime;
                    $scope.logFilterJson = JSON.stringify($scope.logFilter);
                    $scope.queryJSON.arguments = [$scope.logFilterJson];
                    // log.info("log returned " + (logs ? logs.length : 0) + " results for query: " + $scope.toTime  + " from json: " + $scope.logFilterJson);
                }
            }
            if (logs) {
                var maxSize = Log.getLogCacheSize(localStorage);

                //don't really need many logs in a widget...
                if ($scope.inDashboard) {
                    maxSize = 10;
                }
                var counter = 0;
                logs.forEach(function (log) {
                    if (log) {
                        // TODO Why do we compare 'item.seq === log.message' ?
                        if (!$scope.logs.any(function (key, item) {
                            return item.message === log.message && item.seq === log.message && item.timestamp === log.timestamp;
                        })) {
                            counter += 1;

                            // if there is a seq in the reply, then its the timestamp with milli seconds
                            if (log.seq != null) {
                                log['timestampMs'] = log.seq;
                            }
                            if ($scope.sortAsc) {
                                $scope.logs.push(log);
                            } else {
                                $scope.logs.unshift(log);
                            }
                        }
                    }
                });
                if (maxSize > 0) {
                    var size = $scope.logs.length;
                    if (size > maxSize) {
                        // lets trim the log size
                        var count = size - maxSize;
                        var pos = 0;
                        if (!$scope.sortAsc) {
                            pos = size - count;
                        }

                        $scope.logs.splice(pos, count);

                        if ($scope.showRowDetails) {
                            if ($scope.sortAsc) {
                                $scope.selectedRowIndex -= count;
                            } else {
                                $scope.selectedRowIndex += count;
                            }
                        }
                    }
                }
                if (counter) {
                    if ($scope.autoScroll && scrollToTopOrBottom) {
                        setTimeout(function () {
                            var pos = 0;
                            if ($scope.sortAsc) {
                                pos = $document.height() - window.height();
                            }
                            log.debug("Scrolling to position: " + pos);
                            $document.scrollTop(pos);
                        }, 20);
                    }
                    Core.$apply($scope);
                }
            }
        };

        // listen for updates adding the since
        var asyncUpdateValues = function (response) {
            var value = response.value;
            if (value) {
                updateValues(value);
            } else {
                notification("error", "Failed to get a response! " + JSON.stringify(response, null, 4));
            }
        };

        var callbackOptions = onSuccess(asyncUpdateValues, {
            error: function (response) {
                asyncUpdateValues(response);
            },
            silent: true
        });

        if (logQueryMBean) {
            var firstCallback = function (results) {
                updateValues(results);

                // now lets register to perform incremental updates
                Core.register(jolokia, $scope, $scope.queryJSON, callbackOptions);
            };

            jolokia.execute(logQueryMBean, "getLogResults(int)", $scope.logBatchSize, onSuccess(firstCallback));
        }
    }
    Log.LogController = LogController;
})(Log || (Log = {}));
/**
* @module Log
*/
var Log;
(function (Log) {
    Log.log = Logger.get("Logs");

    function logSourceHref(row) {
        if (!row) {
            return "";
        }
        var log = row.entity;
        if (log) {
            return logSourceHrefEntity(log);
        } else {
            return logSourceHrefEntity(row);
        }
    }
    Log.logSourceHref = logSourceHref;

    function treeContainsLogQueryMBean(workspace) {
        return workspace.treeContainsDomainAndProperties('io.fabric8.insight', { type: 'LogQuery' }) || workspace.treeContainsDomainAndProperties('org.fusesource.insight', { type: 'LogQuery' });
    }
    Log.treeContainsLogQueryMBean = treeContainsLogQueryMBean;

    function isSelectionLogQueryMBean(workspace) {
        return workspace.hasDomainAndProperties('io.fabric8.insight', { type: 'LogQuery' }) || workspace.hasDomainAndProperties('org.fusesource.insight', { type: 'LogQuery' });
    }
    Log.isSelectionLogQueryMBean = isSelectionLogQueryMBean;

    function findLogQueryMBean(workspace) {
        var node = workspace.findMBeanWithProperties('io.fabric8.insight', { type: 'LogQuery' });
        if (!node) {
            node = workspace.findMBeanWithProperties('org.fusesource.insight', { type: 'LogQuery' });
        }
        return node ? node.objectName : null;
    }
    Log.findLogQueryMBean = findLogQueryMBean;

    function logSourceHrefEntity(log) {
        var fileName = Log.removeQuestion(log.fileName);
        var className = Log.removeQuestion(log.className);
        var properties = log.properties;
        var mavenCoords = "";
        if (properties) {
            mavenCoords = properties["maven.coordinates"];
        }
        if (mavenCoords && fileName) {
            var link = "#/source/view/" + mavenCoords + "/class/" + className + "/" + fileName;
            var line = log.lineNumber;
            if (line) {
                link += "?line=" + line;
            }
            return link;
        } else {
            return "";
        }
    }
    Log.logSourceHrefEntity = logSourceHrefEntity;

    function hasLogSourceHref(log) {
        var properties = log.properties;
        if (!properties) {
            return false;
        }
        var mavenCoords = "";
        if (properties) {
            mavenCoords = properties["maven.coordinates"];
        }
        return angular.isDefined(mavenCoords) && mavenCoords !== "";
    }
    Log.hasLogSourceHref = hasLogSourceHref;

    function hasLogSourceLineHref(log) {
        var line = log["lineNumber"];
        return angular.isDefined(line) && line !== "" && line !== "?";
    }
    Log.hasLogSourceLineHref = hasLogSourceLineHref;

    function removeQuestion(text) {
        return (!text || text === "?") ? null : text;
    }
    Log.removeQuestion = removeQuestion;

    var _stackRegex = /\s*at\s+([\w\.$_]+(\.([\w$_]+))*)\((.*)?:(\d+)\).*\[(.*)\]/;

    function formatStackTrace(exception) {
        if (!exception) {
            return '';
        }

        // turn exception into an array
        if (!angular.isArray(exception) && angular.isString(exception)) {
            exception = exception.split('\n');
        }

        if (!angular.isArray(exception)) {
            return "";
        }

        var answer = '<ul class="unstyled">\n';
        exception.each(function (line) {
            answer += "<li>" + Log.formatStackLine(line) + "</li>\n";
        });
        answer += "</ul>\n";
        return answer;
    }
    Log.formatStackTrace = formatStackTrace;

    function formatStackLine(line) {
        var match = _stackRegex.exec(line);
        if (match && match.length > 6) {
            var classAndMethod = match[1];
            var fileName = match[4];
            var line = match[5];
            var mvnCoords = match[6];

            // we can ignore line if its not present...
            if (classAndMethod && fileName && mvnCoords) {
                var className = classAndMethod;
                var idx = classAndMethod.lastIndexOf('.');
                if (idx > 0) {
                    className = classAndMethod.substring(0, idx);
                }
                var link = "#/source/view/" + mvnCoords + "/class/" + className + "/" + fileName;
                if (angular.isDefined(line)) {
                    link += "?line=" + line;
                }

                /*
                console.log("classAndMethod: " + classAndMethod);
                console.log("fileName: " + fileName);
                console.log("line: " + line);
                console.log("mvnCoords: " + mvnCoords);
                console.log("Matched " + JSON.stringify(match));
                */
                return "<div class='stack-line'>  at <a href='" + link + "'>" + classAndMethod + "</a>(<span class='fileName'>" + fileName + "</span>:<span class='lineNumber'>" + line + "</span>)[<span class='mavenCoords'>" + mvnCoords + "</span>]</div>";
            }
        }
        var bold = true;
        if (line) {
            line = line.trim();
            if (line.startsWith('at')) {
                line = '  ' + line;
                bold = false;
            }
        }
        if (bold) {
            return '<pre class="stack-line bold">' + line + '</pre>';
        } else {
            return '<pre class="stack-line">' + line + '</pre>';
        }
    }
    Log.formatStackLine = formatStackLine;

    function getLogCacheSize(localStorage) {
        var text = localStorage['logCacheSize'];
        if (text) {
            return parseInt(text);
        }
        return 1000;
    }
    Log.getLogCacheSize = getLogCacheSize;
})(Log || (Log = {}));
/**
* @module Logs
*/
var Logs;
(function (Logs) {
    function PreferencesController($scope, localStorage) {
        Core.initPreferenceScope($scope, localStorage, {
            'logCacheSize': {
                'value': 1000,
                'converter': parseInt
            },
            'logSortAsc': {
                'value': true,
                'converter': Core.parseBooleanValue
            },
            'logAutoScroll': {
                'value': true,
                'converter': Core.parseBooleanValue
            }
        });
    }
    Logs.PreferencesController = PreferencesController;
    ;
})(Logs || (Logs = {}));
/**
* @module Log
* @main Log
*/
var Log;
(function (Log) {
    var pluginName = 'log';

    var hasMBean = false;

    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'datatable', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/logs', { templateUrl: 'app/log/html/logs.html', reloadOnSearch: false }).when('/openlogs', { redirectTo: function () {
                // use a redirect, as the log plugin may not be valid, if we connect to a JVM which does not have the log mbean
                // in the JMX tree, and if that happens, we need to redirect to home, so another tab is selected
                if (hasMBean) {
                    return '/logs';
                } else {
                    return '/home';
                }
            }, reloadOnSearch: false });
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry, preferencesRegistry) {
        hasMBean = Log.treeContainsLogQueryMBean(workspace);

        viewRegistry['log'] = layoutFull;
        helpRegistry.addUserDoc('log', 'app/log/doc/help.md', function () {
            return Log.treeContainsLogQueryMBean(workspace);
        });

        preferencesRegistry.addTab("Server Logs", "app/log/html/preferences.html", function () {
            return Log.treeContainsLogQueryMBean(workspace);
        });

        workspace.topLevelTabs.push({
            id: "logs",
            content: "Logs",
            title: "View and search the logs of this container",
            isValid: function (workspace) {
                return Log.treeContainsLogQueryMBean(workspace);
            },
            href: function () {
                return "#/logs";
            }
        });

        workspace.subLevelTabs.push({
            content: '<i class="icon-list-alt"></i> Log',
            title: "View the logs in this process",
            isValid: function (workspace) {
                return Log.isSelectionLogQueryMBean(workspace);
            },
            href: function () {
                return "#/logs";
            }
        });
    }).filter('logDateFilter', function ($filter) {
        var standardDateFilter = $filter('date');
        return function (log) {
            if (!log) {
                return null;
            }
            if (log.timestampMs) {
                return standardDateFilter(log.timestampMs, 'yyyy-MM-dd HH:mm:ss.sss');
            } else {
                return standardDateFilter(log.timestamp, 'yyyy-MM-dd HH:mm:ss');
            }
        };
    });

    hawtioPluginLoader.addModule(pluginName);
})(Log || (Log = {}));
/**
* @module Core
*/
var Core;
(function (Core) {
    if (!Object.keys) {
        Object.keys = function (obj) {
            var keys = [], k;
            for (k in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, k)) {
                    keys.push(k);
                }
            }
            return keys;
        };
    }

    /**
    * Adds the specified CSS file to the document's head, handy
    * for external plugins that might bring along their own CSS
    * @param path
    */
    function addCSS(path) {
        if ('createStyleSheet' in document) {
            // IE9
            document.createStyleSheet(path);
        } else {
            // Everyone else
            var link = $("<link>");
            $("head").append(link);

            link.attr({
                rel: 'stylesheet',
                type: 'text/css',
                href: path
            });
        }
    }
    Core.addCSS = addCSS;

    var dummyStorage = {};

    /**
    * Wrapper to get the window local storage object
    * @returns {WindowLocalStorage}
    */
    function getLocalStorage() {
        // TODO Create correct implementation of windowLocalStorage
        var storage = window.localStorage || (function () {
            return dummyStorage;
        })();
        return storage;
    }
    Core.getLocalStorage = getLocalStorage;

    /**
    * If the value is not an array then wrap it in one
    * @method asArray
    * @for Core
    * @static
    * @param {any} value
    * @return {Array}
    */
    function asArray(value) {
        return angular.isArray(value) ? value : [value];
    }
    Core.asArray = asArray;

    /**
    * Ensure whatever value is passed in is converted to a boolean
    *
    * In the branding module for now as it's needed before bootstrap
    *
    * @method parseBooleanValue
    * @for Core
    * @param {any} value
    * @return {Boolean}
    */
    function parseBooleanValue(value) {
        if (!angular.isDefined(value)) {
            return false;
        }

        if (value.constructor === Boolean) {
            return value;
        }

        if (angular.isString(value)) {
            switch (value.toLowerCase()) {
                case "true":
                case "1":
                case "yes":
                    return true;
                default:
                    return false;
            }
        }

        if (angular.isNumber(value)) {
            return value !== 0;
        }

        throw new Error("Can't convert value " + value + " to boolean");
    }
    Core.parseBooleanValue = parseBooleanValue;

    function parseIntValue(value, description) {
        if (angular.isString(value)) {
            try  {
                return parseInt(value);
            } catch (e) {
                console.log("Failed to parse " + description + " with text '" + value + "'");
            }
        }
        return null;
    }
    Core.parseIntValue = parseIntValue;

    function parseFloatValue(value, description) {
        if (angular.isString(value)) {
            try  {
                return parseFloat(value);
            } catch (e) {
                console.log("Failed to parse " + description + " with text '" + value + "'");
            }
        }
        return null;
    }
    Core.parseFloatValue = parseFloatValue;

    /**
    * Navigates the given set of paths in turn on the source object
    * and returns the last most value of the path or null if it could not be found.
    * @method pathGet
    * @for Core
    * @static
    * @param {Object} object the start object to start navigating from
    * @param {Array} paths an array of path names to navigate or a string of dot separated paths to navigate
    * @return {*} the last step on the path which is updated
    */
    function pathGet(object, paths) {
        var pathArray = (angular.isArray(paths)) ? paths : (paths || "").split(".");
        var value = object;
        angular.forEach(pathArray, function (name) {
            if (value) {
                try  {
                    value = value[name];
                } catch (e) {
                    // ignore errors
                    return null;
                }
            } else {
                return null;
            }
        });
        return value;
    }
    Core.pathGet = pathGet;

    /**
    * Navigates the given set of paths in turn on the source object
    * and updates the last path value to the given newValue
    * @method pathSet
    * @for Core
    * @static
    * @param {Object} object the start object to start navigating from
    * @param {Array} paths an array of path names to navigate or a string of dot separated paths to navigate
    * @param {Object} newValue the value to update
    * @return {*} the last step on the path which is updated
    */
    function pathSet(object, paths, newValue) {
        var pathArray = (angular.isArray(paths)) ? paths : (paths || "").split(".");
        var value = object;
        var lastIndex = pathArray.length - 1;
        angular.forEach(pathArray, function (name, idx) {
            var next = value[name];
            if (idx >= lastIndex || !angular.isObject(next)) {
                next = (idx < lastIndex) ? {} : newValue;
                value[name] = next;
            }
            value = next;
        });
        return value;
    }
    Core.pathSet = pathSet;

    /**
    * Performs a $scope.$apply() if not in a digest right now otherwise it will fire a digest later
    * @method $applyNowOrLater
    * @for Core
    * @static
    * @param {*} $scope
    */
    function $applyNowOrLater($scope) {
        if ($scope.$$phase || $scope.$root.$$phase) {
            setTimeout(function () {
                Core.$apply($scope);
            }, 50);
        } else {
            $scope.$apply();
        }
    }
    Core.$applyNowOrLater = $applyNowOrLater;

    /**
    * Performs a $scope.$apply() after the given timeout period
    * @method $applyLater
    * @for Core
    * @static
    * @param {*} $scope
    * @param {Integer} timeout
    */
    function $applyLater($scope, timeout) {
        if (typeof timeout === "undefined") { timeout = 50; }
        setTimeout(function () {
            Core.$apply($scope);
        }, timeout);
    }
    Core.$applyLater = $applyLater;

    /**
    * Performs a $scope.$apply() if not in a digest or apply phase on the given scope
    * @method $apply
    * @for Core
    * @static
    * @param {*} $scope
    */
    function $apply($scope) {
        var phase = $scope.$$phase || $scope.$root.$$phase;
        if (!phase) {
            $scope.$apply();
        }
    }
    Core.$apply = $apply;

    function $digest($scope) {
        var phase = $scope.$$phase || $scope.$root.$$phase;
        if (!phase) {
            $scope.$digest();
        }
    }
    Core.$digest = $digest;

    /**
    * Look up a list of child element names or lazily create them.
    *
    * Useful for example to get the <tbody> <tr> element from a <table> lazily creating one
    * if not present.
    *
    * Usage: var trElement = getOrCreateElements(tableElement, ["tbody", "tr"])
    * @method getOrCreateElements
    * @for Core
    * @static
    * @param {Object} domElement
    * @param {Array} arrayOfElementNames
    * @return {Object}
    */
    function getOrCreateElements(domElement, arrayOfElementNames) {
        var element = domElement;
        angular.forEach(arrayOfElementNames, function (name) {
            if (element) {
                var children = $(element).children(name);
                if (!children || !children.length) {
                    $("<" + name + "></" + name + ">").appendTo(element);
                    children = $(element).children(name);
                }
                element = children;
            }
        });
        return element;
    }
    Core.getOrCreateElements = getOrCreateElements;

    var _escapeHtmlChars = {
        "#": "&#35;",
        "'": "&#39;",
        "<": "&lt;",
        ">": "&gt;",
        "\"": "&quot;"
    };

    function unescapeHtml(str) {
        angular.forEach(_escapeHtmlChars, function (value, key) {
            var regex = new RegExp(value, "g");
            str = str.replace(regex, key);
        });
        str = str.replace(/&gt;/g, ">");
        return str;
    }
    Core.unescapeHtml = unescapeHtml;

    function escapeHtml(str) {
        if (angular.isString(str)) {
            var newStr = "";
            for (var i = 0; i < str.length; i++) {
                var ch = str.charAt(i);
                var ch = _escapeHtmlChars[ch] || ch;
                newStr += ch;
                /*
                var nextCode = str.charCodeAt(i);
                if (nextCode > 0 && nextCode < 48) {
                newStr += "&#" + nextCode + ";";
                }
                else {
                newStr += ch;
                }
                */
            }
            return newStr;
        } else {
            return str;
        }
    }
    Core.escapeHtml = escapeHtml;

    /**
    * Returns true if the string is either null or empty
    *
    * @method isBlank
    * @for Core
    * @static
    * @param {String} str
    * @return {Boolean}
    */
    function isBlank(str) {
        if (!str) {
            return true;
        }
        return str.isBlank();
    }
    Core.isBlank = isBlank;

    /**
    * Displays an alert message which is typically the result of some asynchronous operation
    *
    * @method notification
    * @static
    * @param type which is usually "success" or "error" and matches css alert-* css styles
    * @param message the text to display
    *
    */
    function notification(type, message, options) {
        if (typeof options === "undefined") { options = null; }
        var w = window;

        if (options === null) {
            options = {};
        }

        if (type === 'error' || type === 'warning') {
            if (!angular.isDefined(options.onclick)) {
                options.onclick = window['showLogPanel'];
            }
        }

        w.toastr[type](message, '', options);
    }
    Core.notification = notification;

    /**
    * Clears all the pending notifications
    * @method clearNotifications
    * @static
    */
    function clearNotifications() {
        var w = window;
        w.toastr.clear();
    }
    Core.clearNotifications = clearNotifications;

    function humanizeValue(value) {
        if (value) {
            var text = value.toString();
            try  {
                text = text.underscore();
            } catch (e) {
                // ignore
            }
            try  {
                text = text.humanize();
            } catch (e) {
                // ignore
            }
            return trimQuotes(text);
        }
        return value;
    }
    Core.humanizeValue = humanizeValue;

    function trimQuotes(text) {
        if (text) {
            while (text.endsWith('"') || text.endsWith("'")) {
                text = text.substring(0, text.length - 1);
            }
            while (text.startsWith('"') || text.startsWith("'")) {
                text = text.substring(1, text.length);
            }
        }
        return text;
    }
    Core.trimQuotes = trimQuotes;
})(Core || (Core = {}));

// Lots of code refers to these functions in the global namespace
var notification = Core.notification;
var clearNotifications = Core.clearNotifications;
var humanizeValue = Core.humanizeValue;
var trimQuotes = Core.trimQuotes;
var JBoss;
(function (JBoss) {
    function cleanWebAppName(name) {
        // JBoss may include .war as the application name, so remove that
        if (name && name.lastIndexOf(".war") > -1) {
            return name.replace(".war", "");
        } else {
            return name;
        }
    }
    JBoss.cleanWebAppName = cleanWebAppName;

    function cleanContextPath(contextPath) {
        if (contextPath) {
            return "/" + cleanWebAppName(contextPath);
        } else {
            return "";
        }
    }
    JBoss.cleanContextPath = cleanContextPath;

    function iconClass(state) {
        if (state) {
            switch (state.toString().toLowerCase()) {
                case 'started':
                    return "green icon-play-circle";
                case 'ok':
                    return "green icon-play-circle";
                case 'true':
                    return "green icon-play-circle";
            }
        }
        return "orange icon-off";
    }
    JBoss.iconClass = iconClass;
})(JBoss || (JBoss = {}));
var JBoss;
(function (JBoss) {
    function DmrController($scope, $location, workspace) {
        var search = $location.search();
        var connectUrl = url("/proxy/localhost/9990/management");
        var user = search["_user"] || "";
        var pwd = search["_pwd"] || "";
        if (user) {
            connectUrl += "?_user=" + user;
            if (pwd) {
                connectUrl += "&_pwd=" + pwd;
            }
        }

        var isDmr = "dmr" === search["_format"];
        var data = null;
        var format = "application/dmr-encoded";
        if (isDmr) {
            // create an operation
            /*
            var op = new dmr.ModelNode();
            //op.get("operation").set("read-resource");
            op.get("operation").set("read-attribute");
            op.get("address").setEmptyList();
            op.get("name").set("release-version");
            */
            var op = new dmr.ModelNode();
            op.get("operation").set("read-attribute");
            op.get("address").setEmptyList();
            op.get("name").set("release-version");

            data = op.toBase64String();
        } else {
            format = "application/json";
            var request = {
                "operation": "read-resource"
            };
            data = JSON.stringify(request);
        }

        console.log("Using dmr: " + isDmr + " with content type: " + format + " and data " + data);

        $.ajax({
            url: connectUrl,
            data: data,
            processData: false,
            type: "POST",
            dataType: "text",
            contentType: format,
            accepts: format,
            headers: {
                "Content-type": format,
                "Accept": format
            }
        }).done(onData);

        function onData(data) {
            if (data) {
                var json = null;
                if (isDmr) {
                    var response = dmr.ModelNode.fromBase64(data);
                    var jsonText = response.toJSONString();
                    json = JSON.parse(jsonText);
                } else {
                    json = JSON.parse(data);
                    json = json.result;
                }

                $scope.row = json;
                Core.$apply($scope);
                console.log("Response: " + JSON.stringify(json, null, "  "));
            }
        }
    }
    JBoss.DmrController = DmrController;
})(JBoss || (JBoss = {}));
/**
* @module JBoss
* @main JBoss
*/
var JBoss;
(function (JBoss) {
    var pluginName = 'jboss';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ui.bootstrap.dialog', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/jboss/server', { templateUrl: 'app/jboss/html/server.html' }).when('/jboss/applications', { templateUrl: 'app/jboss/html/applications.html' }).when('/jboss/dmr', { templateUrl: 'app/jboss/html/dmr.html' }).when('/jboss/connectors', { templateUrl: 'app/jboss/html/connectors.html' });
    }).filter('jbossIconClass', function () {
        return JBoss.iconClass;
    }).run(function ($location, workspace, viewRegistry, helpRegistry) {
        viewRegistry['jboss'] = "app/jboss/html/layoutJBossTabs.html";
        helpRegistry.addUserDoc(pluginName, 'app/' + pluginName + '/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("jboss.as") || workspace.treeContainsDomainAndProperties("jboss.jta") || workspace.treeContainsDomainAndProperties("jboss.modules");
        });

        workspace.topLevelTabs.push({
            id: "jboss",
            content: "JBoss",
            title: "Manage your JBoss container",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("jboss.as") || workspace.treeContainsDomainAndProperties("jboss.jta") || workspace.treeContainsDomainAndProperties("jboss.modules");
            },
            href: function () {
                return "#/jboss/applications";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("jboss");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(JBoss || (JBoss = {}));
var JBoss;
(function (JBoss) {
    function JBossController($scope, $location, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | jbossIconClass}}"></i></div>';
        var urlTemplate = '<div class="ngCellText" title="{{row.getProperty(col.field)}}">' + '<a ng-href="{{row.getProperty(col.field)}}" target="_blank">{{row.getProperty(col.field)}}</a>' + '</div>';

        $scope.uninstallDialog = new UI.Dialog();

        $scope.httpPort;
        $scope.httpScheme = "http";

        $scope.webapps = [];
        $scope.selected = [];

        var columnDefs = [
            {
                field: 'status',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'name',
                displayName: 'Name',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'contextPath',
                displayName: 'Context-Path',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'url',
                displayName: 'Url',
                cellTemplate: urlTemplate,
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'webapps',
            displayFooter: true,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        function render(response) {
            $scope.webapps = [];
            $scope.mbeanIndex = {};
            $scope.selected.length = 0;

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    var mbean = obj.mbean;

                    if (mbean) {
                        obj.name = JBoss.cleanWebAppName(obj.name);
                        obj.contextPath = JBoss.cleanContextPath(obj.name);

                        // compute the url for the webapp, and we want to use http as scheme
                        var hostname = Core.extractTargetUrl($location, $scope.httpScheme, $scope.httpPort);
                        obj.url = hostname + obj['contextPath'];

                        var idx = $scope.mbeanIndex[mbean];
                        if (angular.isDefined(idx)) {
                            $scope.webapps[mbean] = obj;
                        } else {
                            $scope.mbeanIndex[mbean] = $scope.webapps.length;
                            $scope.webapps.push(obj);
                        }
                        Core.$apply($scope);
                    }
                }
            }

            angular.forEach(response, function (value, key) {
                var mbean = value;
                jolokia.request({ type: "read", mbean: mbean, attribute: ["name", "status"] }, onSuccess(onAttributes));
            });
            Core.$apply($scope);
        }
        ;

        // function to control the web applications
        $scope.controlWebApps = function (op) {
            // grab id of mbean names to control
            var mbeanNames = $scope.selected.map(function (b) {
                return b.mbean;
            });
            if (!angular.isArray(mbeanNames)) {
                mbeanNames = [mbeanNames];
            }

            // execute operation on each mbean
            var lastIndex = (mbeanNames.length || 1) - 1;
            angular.forEach(mbeanNames, function (mbean, idx) {
                var onResponse = (idx >= lastIndex) ? $scope.onLastResponse : $scope.onResponse;
                jolokia.request({
                    type: 'exec',
                    mbean: mbean,
                    operation: op,
                    arguments: null
                }, onSuccess(onResponse, { error: onResponse }));
            });
        };

        $scope.start = function () {
            $scope.controlWebApps('deploy');
        };

        $scope.stop = function () {
            $scope.controlWebApps('undeploy');
        };

        $scope.reload = function () {
            $scope.controlWebApps('redeploy');
        };

        $scope.uninstall = function () {
            $scope.controlWebApps('remove');
            $scope.uninstallDialog.close();
        };

        // function to trigger reloading page
        $scope.onLastResponse = function (response) {
            $scope.onResponse(response);

            // we only want to force updating the data on the last response
            loadData();
        };

        $scope.onResponse = function (response) {
            //console.log("got response: " + response);
        };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading JBoss webapp data...");

            // must load connectors first, before showing applications, so we do this call synchronously
            var connectors = jolokia.search("jboss.as:socket-binding-group=standard-sockets,*");
            if (connectors) {
                var found = false;
                angular.forEach(connectors, function (key, value) {
                    var mbean = key;
                    if (!found) {
                        var data = jolokia.request({ type: "read", mbean: mbean, attribute: ["port", "name"] });
                        if (data && data.value && data.value.name && data.value.name.toString().toLowerCase() === 'http') {
                            found = true;
                            $scope.httpPort = data.value.port;
                            $scope.httpScheme = "http";
                        }
                    }
                });
            }
            jolokia.search("jboss.as:deployment=*", onSuccess(render));
        }

        // grab server information once
        $scope.jbossServerVersion = "";
        $scope.jbossServerName = "";
        $scope.jbossServerLaunchType = "";

        // lookup jboss 6 and 7
        var servers = jolokia.search("jboss.as:management-root=*");
        if (servers && servers.length === 1) {
            $scope.jbossServerVersion = jolokia.getAttribute(servers[0], "releaseVersion");
            $scope.jbossServerName = jolokia.getAttribute(servers[0], "name");
            $scope.jbossServerLaunchType = jolokia.getAttribute(servers[0], "launchType");
        } else {
            // wildfly is changed
            var wildflyMBean = 'jboss.as:management-root=server';
            var response = jolokia.request({ type: "read", mbean: wildflyMBean, attribute: ["releaseVersion", "name", "launchType"] });
            if (response) {
                var obj = response.value;
                if (obj) {
                    $scope.jbossServerVersion = obj.releaseVersion;
                    $scope.jbossServerName = obj.name;
                    $scope.jbossServerLaunchType = obj.launchType;
                }
            } else {
                console.log("Cannot find JBoss/Wildfly server or there was more than one server");
            }
        }
    }
    JBoss.JBossController = JBossController;
})(JBoss || (JBoss = {}));
var JBoss;
(function (JBoss) {
    function ConnectorsController($scope, $location, workspace, jolokia) {
        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field) | jbossIconClass}}"></i></div>';

        $scope.connectors = [];

        var columnDefs = [
            {
                field: 'bound',
                displayName: 'State',
                cellTemplate: stateTemplate,
                width: 56,
                minWidth: 56,
                maxWidth: 56,
                resizable: false
            },
            {
                field: 'name',
                displayName: 'Name',
                cellFilter: null,
                width: "*",
                resizable: true
            },
            {
                field: 'port',
                displayName: 'Port',
                cellFilter: null,
                width: "*",
                resizable: true
            }
        ];

        $scope.gridOptions = {
            data: 'connectors',
            displayFooter: false,
            displaySelectionCheckbox: false,
            canSelectRows: false,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        function render(response) {
            $scope.connectors = [];

            function onAttributes(response) {
                var obj = response.value;
                if (obj) {
                    obj.mbean = response.request.mbean;
                    if (!obj.port) {
                        obj.port = obj.boundPort;
                    }
                    if (!obj.name) {
                        // special hack for mail-smtp, it only has port
                        obj.name = "mail-smtp";
                    }
                    $scope.connectors.push(obj);
                    Core.$apply($scope);
                }
            }

            // create structure for each response
            angular.forEach(response, function (value, key) {
                var mbean = value;
                if (mbean.toString() !== "jboss.as:socket-binding-group=standard-sockets") {
                    if (mbean.toString().lastIndexOf("management") > 0) {
                        // management mbean do not have port
                        jolokia.request({ type: "read", mbean: mbean, attribute: ["boundPort", "name", "bound"] }, onSuccess(onAttributes));
                    } else if (mbean.toString().lastIndexOf("mail-smtp") > 0) {
                        // special hack for mail-smtp, it only has port
                        jolokia.request({ type: "read", mbean: mbean, attribute: ["port"] }, onSuccess(onAttributes));
                    } else {
                        jolokia.request({ type: "read", mbean: mbean, attribute: ["port", "name", "bound"] }, onSuccess(onAttributes));
                    }
                }
            });
            Core.$apply($scope);
        }
        ;

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading JBoss connector data...");
            jolokia.search("jboss.as:socket-binding-group=standard-sockets,*", onSuccess(render));
        }
    }
    JBoss.ConnectorsController = ConnectorsController;
})(JBoss || (JBoss = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function ServiceDependencyController($scope, $location, $routeParams, workspace, osgiDataService) {
        $scope.init = function () {
            if ($routeParams["bundleFilter"]) {
                $scope.bundleFilter = $routeParams["bundleFilter"];
            } else {
                $scope.bundleFilter = "";
            }

            if ($routeParams["pkgFilter"]) {
                $scope.packageFilter = $routeParams["pkgFilter"];
            } else {
                $scope.packageFilter = "";
            }

            if ($routeParams["view"] == "packages") {
                $scope.selectView = "packages";
            } else {
                $scope.selectView = "services";
            }

            if ($routeParams['hideUnused']) {
                $scope.hideUnused = $routeParams['hideUnused'] == "true";
            } else {
                $scope.hideUnused = true;
            }
        };

        $scope.updateLink = function () {
            var search = $location.search;

            if ($scope.bundleFilter && $scope.bundleFilter != "") {
                search["bundleFilter"] = $scope.bundleFilter;
            } else {
                delete search["bundleFilter"];
            }

            if ($scope.packageFilter && $scope.packageFilter != "") {
                search["pkgFilter"] = $scope.packageFilter;
            } else {
                delete search["pkgFilter"];
            }

            search["view"] = $scope.selectView;

            if ($scope.hideUnused) {
                search["hideUnused"] = "true";
            } else {
                search["hideUnused"] = "false";
            }

            $location.search(search);
        };

        $scope.addToDashboardLink = function () {
            var routeParams = angular.toJson($routeParams);

            var href = "#/osgi/dependencies";
            var title = "OSGi dependencies";

            var size = angular.toJson({
                size_x: 2,
                size_y: 2
            });

            var addLink = "#/dashboard/add?tab=dashboard" + "&href=" + encodeURIComponent(href) + "&routeParams=" + encodeURIComponent(routeParams) + "&size=" + encodeURIComponent(size) + "&title=" + encodeURIComponent(title);

            return addLink;
        };

        $scope.$on('$routeUpdate', function () {
            var search = $location.search;

            if (search["bundleFilter"]) {
                $scope.bundleFilter = $routeParams["bundleFilter"];
            } else {
                $scope.bundleFilter = "";
            }

            if (search["pkgFilter"]) {
                $scope.packageFilter = $routeParams["pkgFilter"];
            } else {
                $scope.packageFilter = "";
            }

            if (search["view"] == "packages") {
                $scope.selectView = "packages";
            } else {
                $scope.selectView = "services";
            }

            if (search['hideUnused']) {
                $scope.hideUnused = $routeParams['hideUnused'] == "true";
            } else {
                $scope.hideUnused = true;
            }

            $scope.updateLink();
            $scope.updateGraph();
        });

        $scope.updateGraph = function () {
            $scope.updateLink();
            $scope.updatePkgFilter();

            var graphBuilder = new Osgi.OsgiGraphBuilder(osgiDataService, $scope.bundleFilter, $scope.packageFilter, $scope.selectView == "services", $scope.selectView == "packages", $scope.hideUnused);

            $scope.graph = graphBuilder.buildGraph();
            Core.$apply($scope);
        };

        $scope.updatePkgFilter = function () {
            if ($scope.packageFilter == null || $scope.packageFilter == "") {
                $scope.selectView = "services";
                $scope.disablePkg = true;
            } else {
                $scope.disablePkg = false;
            }
        };

        $scope.init();
        $scope.updateGraph();
    }
    Osgi.ServiceDependencyController = ServiceDependencyController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function PackageController($scope, $filter, workspace, $routeParams) {
        $scope.package = $routeParams.package;
        $scope.version = $routeParams.version;

        updateTableContents();

        function populateTable(response) {
            var packages = Osgi.defaultPackageValues(workspace, $scope, response.value);
            $scope.row = packages.filter({ "Name": $scope.package, "Version": $scope.version })[0];
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var mbean = Osgi.getSelectionPackageMBean(workspace);
            if (mbean) {
                var jolokia = workspace.jolokia;
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listPackages' }, onSuccess(populateTable));
            }
        }
    }
    Osgi.PackageController = PackageController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    var OsgiDataService = (function () {
        function OsgiDataService(workspace, jolokia) {
            this.jolokia = jolokia;
            this.workspace = workspace;
        }
        OsgiDataService.prototype.getBundles = function () {
            var bundles = {};

            var response = this.jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionBundleMBean(this.workspace),
                operation: 'listBundles()'
            }, onSuccess(null));

            angular.forEach(response.value, function (value, key) {
                var obj = {
                    Identifier: value.Identifier,
                    Name: "",
                    SymbolicName: value.SymbolicName,
                    Fragment: value.Fragment,
                    State: value.State,
                    Version: value.Version,
                    LastModified: new Date(Number(value.LastModified)),
                    Location: value.Location,
                    StartLevel: undefined,
                    RegisteredServices: value.RegisteredServices,
                    ServicesInUse: value.ServicesInUse
                };
                if (value.Headers['Bundle-Name']) {
                    obj.Name = value.Headers['Bundle-Name']['Value'];
                }

                bundles[value.Identifier] = obj;
            });

            return bundles;
        };

        OsgiDataService.prototype.getServices = function () {
            var services = {};

            var response = this.jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionServiceMBean(this.workspace),
                operation: 'listServices()'
            }, onSuccess(null));

            var answer = response.value;

            angular.forEach(answer, function (value, key) {
                services[value.Identifier] = value;
            });

            return services;
        };

        OsgiDataService.prototype.getPackages = function () {
            var packages = {};

            var response = this.jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionPackageMBean(this.workspace),
                operation: 'listPackages()'
            }, onSuccess(null));

            var answer = response.value.values;

            answer.forEach(function (value) {
                packages[value.Name + "-" + value.Version] = value;
            });

            return packages;
        };
        return OsgiDataService;
    })();
    Osgi.OsgiDataService = OsgiDataService;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
* @main Osgi
*/
var Osgi;
(function (Osgi) {
    var pluginName = 'osgi';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/osgi/bundle-list', { templateUrl: 'app/osgi/html/bundle-list.html' }).when('/osgi/bundles', { templateUrl: 'app/osgi/html/bundles.html' }).when('/osgi/bundle/:bundleId', { templateUrl: 'app/osgi/html/bundle.html' }).when('/osgi/services', { templateUrl: 'app/osgi/html/services.html' }).when('/osgi/packages', { templateUrl: 'app/osgi/html/packages.html' }).when('/osgi/package/:package/:version', { templateUrl: 'app/osgi/html/package.html' }).when('/osgi/configurations', { templateUrl: 'app/osgi/html/configurations.html' }).when('/osgi/pid/:pid/:factoryPid', { templateUrl: 'app/osgi/html/pid.html' }).when('/osgi/pid/:pid', { templateUrl: 'app/osgi/html/pid.html' }).when('/osgi/fwk', { templateUrl: 'app/osgi/html/framework.html' }).when('/osgi/dependencies', { templateUrl: 'app/osgi/html/svc-dependencies.html', reloadOnSearch: false });
    }).run(function (workspace, viewRegistry, helpRegistry) {
        viewRegistry['osgi'] = "app/osgi/html/layoutOsgi.html";
        helpRegistry.addUserDoc('osgi', 'app/osgi/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("osgi.core");
        });

        workspace.topLevelTabs.push({
            id: "osgi",
            content: "OSGi",
            title: "Visualise and manage the bundles and services in this OSGi container",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("osgi.core");
            },
            href: function () {
                return "#/osgi/bundle-list";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("osgi");
            }
        });
    }).factory('osgiDataService', function (workspace, jolokia) {
        return new Osgi.OsgiDataService(workspace, jolokia);
    });

    hawtioPluginLoader.addModule(pluginName);
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function FrameworkController($scope, $dialog, workspace) {
        $scope.editDialog = new UI.Dialog();

        updateContents();

        $scope.edit = function (attr, displayName) {
            $scope.editAttr = attr;
            $scope.editDisplayName = displayName;
            $scope.editDialog.open();
        };

        $scope.edited = function (name, displayName, res) {
            $scope.editDialog.close();

            if (angular.isNumber(res)) {
                var mbean = Osgi.getSelectionFrameworkMBean(workspace);
                if (mbean) {
                    var jolokia = workspace.jolokia;
                    jolokia.request({
                        type: 'write', mbean: mbean, attribute: name, value: res
                    }, {
                        error: function (response) {
                            editWritten("error", response.error);
                        },
                        success: function (response) {
                            editWritten("success", displayName + " changed to " + res);
                        }
                    });
                }
            }
        };

        function editWritten(status, message) {
            notification(status, message);
            updateContents();
        }

        function populatePage(response) {
            $scope.startLevel = response.value.FrameworkStartLevel;
            $scope.initialBundleStartLevel = response.value.InitialBundleStartLevel;
            Core.$apply($scope);
        }

        function updateContents() {
            var mbean = Osgi.getSelectionFrameworkMBean(workspace);
            if (mbean) {
                var jolokia = workspace.jolokia;
                jolokia.request({ type: 'read', mbean: mbean }, onSuccess(populatePage));
            }
        }
    }
    Osgi.FrameworkController = FrameworkController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function PackagesController($scope, $filter, workspace, $templateCache, $compile) {
        var dateFilter = $filter('date');

        $scope.widget = new DataTable.TableWidget($scope, $templateCache, $compile, [
            {
                "mDataProp": null,
                "sClass": "control center",
                "sDefaultContent": '<i class="icon-plus"></i>'
            },
            { "mDataProp": "Name" },
            { "mDataProp": "VersionLink" },
            { "mDataProp": "RemovalPending" }
        ], {
            rowDetailTemplateId: 'packageBundlesTemplate',
            disableAddColumns: true
        });

        $scope.$watch('workspace.selection', function () {
            updateTableContents();
        });

        function populateTable(response) {
            var packages = Osgi.defaultPackageValues(workspace, $scope, response.value);
            $scope.widget.populateTable(packages);
            Core.$apply($scope);
        }

        function updateTableContents() {
            var mbean = Osgi.getSelectionPackageMBean(workspace);
            if (mbean) {
                var jolokia = workspace.jolokia;
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listPackages' }, onSuccess(populateTable));
            }
        }
    }
    Osgi.PackagesController = PackagesController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function ConfigurationsController($scope, $routeParams, $location, workspace, jolokia) {
        $scope.selectedItems = [];

        $scope.grid = {
            data: 'configurations',
            showFilter: false,
            showColumnMenu: false,
            multiSelect: false,
            filterOptions: {
                filterText: "",
                useExternalFilter: false
            },
            selectedItems: $scope.selectedItems,
            showSelectionCheckbox: false,
            displaySelectionCheckbox: false,
            columnDefs: [
                {
                    field: 'Pid',
                    displayName: 'Configuration',
                    cellTemplate: '<div class="ngCellText"><a ng-href="{{row.entity.pidLink}}" title="{{row.entity.description}}">{{row.entity.name}}</a></div>'
                }
            ]
        };

        /** the kinds of config */
        var configKinds = {
            factory: {
                class: "badge badge-info",
                title: "Configuration factory used to create separate instances of the configuration"
            },
            pid: {
                class: "badge badge-success",
                title: "Configuration which has a set of properties associated with it"
            },
            pidNoValue: {
                class: "badge badge-warning",
                title: "Configuration which does not yet have any bound values"
            }
        };

        $scope.addPidDialog = new UI.Dialog();

        Osgi.initProfileScope($scope, $routeParams, $location, localStorage, jolokia, workspace, function () {
            $scope.$watch('workspace.selection', function () {
                updateTableContents();
            });

            updateTableContents();
        });

        $scope.addPid = function (newPid) {
            $scope.addPidDialog.close();
            var mbean = Osgi.getHawtioConfigAdminMBean($scope.workspace);
            if (mbean && newPid) {
                var json = JSON.stringify({});
                $scope.jolokia.execute(mbean, "configAdminUpdate", newPid, json, onSuccess(function (response) {
                    notification("success", "Successfully created pid: " + newPid);
                    updateTableContents();
                }));
            }
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateTableContents, 50);
        });

        function onConfigPids(response) {
            var pids = {};
            angular.forEach(response, function (row) {
                var pid = row[0];
                var bundle = row[1];
                var config = createPidConfig(pid, bundle);
                config["hasValue"] = true;
                config["kind"] = configKinds.pid;
                pids[pid] = config;
            });
            $scope.pids = pids;

            // lets load the factory pids
            var mbean = Osgi.getSelectionConfigAdminMBean($scope.workspace);
            if (mbean) {
                $scope.jolokia.execute(mbean, 'getConfigurations', '(service.factoryPid=*)', onSuccess(onConfigFactoryPids, errorHandler("Failed to load factory PID configurations: ")));
            }
            loadMetaType();
        }

        /**
        * For each factory PID lets find the underlying PID to use to edit it, then lets make a link between them
        */
        function onConfigFactoryPids(response) {
            var mbean = Osgi.getSelectionConfigAdminMBean($scope.workspace);
            var pids = $scope.pids;
            if (pids && mbean) {
                angular.forEach(response, function (row) {
                    var pid = row[0];
                    var bundle = row[1];
                    if (pid) {
                        var config = pids[pid];
                        if (config) {
                            config["isFactoryInstance"] = true;
                            $scope.jolokia.execute(mbean, 'getFactoryPid', pid, onSuccess(function (factoryPid) {
                                config["factoryPid"] = factoryPid;
                                config["name"] = Osgi.removeFactoryPidPrefix(pid, factoryPid);
                                if (factoryPid) {
                                    var factoryConfig = getOrCreatePidConfig(factoryPid, bundle);
                                    if (factoryConfig) {
                                        setFactoryPid(factoryConfig);
                                        var children = factoryConfig.children;
                                        if (!children) {
                                            children = {};
                                            factoryConfig["children"] = children;
                                        }
                                        children[pid] = config;
                                        if ($scope.inFabricProfile) {
                                            Osgi.getConfigurationProperties($scope.workspace, $scope.jolokia, pid, function (configValues) {
                                                var zkPid = Core.pathGet(configValues, ["fabric.zookeeper.pid", "Value"]);
                                                if (zkPid) {
                                                    config["name"] = Osgi.removeFactoryPidPrefix(zkPid, factoryPid);
                                                    config["zooKeeperPid"] = zkPid;
                                                    Core.$apply($scope);
                                                }
                                            });
                                        }
                                        Core.$apply($scope);
                                    }
                                }
                            }));
                        }
                    }
                });
            }
            updateMetaType();
        }

        function onMetaType(response) {
            $scope.metaType = response;
            updateMetaType();
        }

        function updateConfigurations() {
            var pids = $scope.pids;
            var configurations = [];
            angular.forEach(pids, function (config, pid) {
                if (!config["isFactoryInstance"]) {
                    configurations.push(config);
                }
            });
            $scope.configurations = configurations.sortBy("name");
            Core.$apply($scope);
        }

        function updateMetaType() {
            var metaType = $scope.metaType;
            if (metaType) {
                angular.forEach(metaType.pids, function (value, pid) {
                    var bundle = null;
                    var config = getOrCreatePidConfig(pid, bundle);
                    if (config) {
                        var factoryPidBundleIds = value.factoryPidBundleIds;
                        if (factoryPidBundleIds && factoryPidBundleIds.length) {
                            setFactoryPid(config);
                        }
                        config["name"] = value.name || pid;
                        var description = value.description;
                        if (description) {
                            config["description"] = description + "\n" + pidBundleDescription(pid, config.bundle);
                        }
                    }
                });
            }
            updateConfigurations();
        }

        function loadMetaType() {
            var metaTypeMBean = Osgi.getMetaTypeMBean($scope.workspace);
            if (metaTypeMBean && $scope.pids) {
                $scope.jolokia.execute(metaTypeMBean, "metaTypeSummary", onSuccess(onMetaType));
            }
        }

        function updateTableContents() {
            $scope.configurations = [];
            if ($scope.jolokia) {
                var mbean = Osgi.getSelectionConfigAdminMBean($scope.workspace);
                if (mbean) {
                    $scope.jolokia.execute(mbean, 'getConfigurations', '(service.pid=*)', onSuccess(onConfigPids, errorHandler("Failed to load PID configurations: ")));
                }
            }
        }

        function pidBundleDescription(pid, bundle) {
            return "pid: " + pid + "\nbundle: " + bundle;
        }

        function createPidConfig(pid, bundle) {
            var config = {
                pid: pid,
                name: pid,
                class: 'pid',
                description: pidBundleDescription(pid, bundle),
                bundle: bundle,
                kind: configKinds.pidNoValue,
                pidLink: createPidLink(pid)
            };
            return config;
        }

        function getOrCreatePidConfig(pid, bundle) {
            var pids = $scope.pids;
            var factoryConfig = pids[pid];
            if (!factoryConfig) {
                factoryConfig = createPidConfig(pid, bundle);
                pids[pid] = factoryConfig;
                updateConfigurations();
            }
            return factoryConfig;
        }

        function setFactoryPid(factoryConfig) {
            factoryConfig["isFactory"] = true;
            factoryConfig["class"] = "factoryPid";
            factoryConfig["kind"] = configKinds.factory;
            var factoryPid = factoryConfig["factoryPid"] || "";
            var pid = factoryConfig["pid"] || "";
            if (!factoryPid) {
                factoryPid = pid;
                pid = null;
            }
            factoryConfig["pidLink"] = createPidLink(pid, factoryPid);
        }

        function createPidLink(pid, factoryPid) {
            if (typeof factoryPid === "undefined") { factoryPid = null; }
            return Osgi.createConfigPidLink($scope, workspace, pid, factoryPid);
        }

        function errorHandler(message) {
            return {
                error: function (response) {
                    notification("error", message + response['error'] || response);
                    Core.defaultJolokiaErrorHandler(response);
                }
            };
        }
    }
    Osgi.ConfigurationsController = ConfigurationsController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function BundleListController($scope, workspace, jolokia, localStorage) {
        $scope.result = {};
        $scope.bundles = [];
        $scope.bundleUrl = "";
        $scope.display = {
            bundleField: "Name",
            sortField: "Identifier",
            bundleFilter: "",
            startLevelFilter: 0,
            showPlatformBundles: false,
            showCxfBundles: false,
            showCamelBundles: true
        };

        if ('bundleList' in localStorage) {
            $scope.display = angular.fromJson(localStorage['bundleList']);
        }

        $scope.$watch('display', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                localStorage['bundleList'] = angular.toJson(newValue);
            }
        }, true);

        $scope.installDisabled = function () {
            return $scope.bundleUrl === "";
        };

        $scope.install = function () {
            jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionFrameworkMBean(workspace),
                operation: "installBundle(java.lang.String)",
                arguments: [$scope.bundleUrl]
            }, {
                success: function (response) {
                    var bundleID = response.value;
                    jolokia.request({
                        type: 'exec',
                        mbean: Osgi.getSelectionBundleMBean(workspace),
                        operation: "isFragment(long)",
                        arguments: [bundleID]
                    }, {
                        success: function (response) {
                            var isFragment = response.value;
                            if (isFragment) {
                                notification("success", "Fragment installed succesfully.");
                                $scope.bundleUrl = "";
                                Core.$apply($scope);
                            } else {
                                jolokia.request({
                                    type: 'exec',
                                    mbean: Osgi.getSelectionFrameworkMBean(workspace),
                                    operation: "startBundle(long)",
                                    arguments: [bundleID]
                                }, {
                                    success: function (response) {
                                        notification("success", "Bundle installed and started successfully.");
                                        $scope.bundleUrl = "";
                                        Core.$apply($scope);
                                    },
                                    error: function (response) {
                                        notification("error", response.error);
                                    }
                                });
                            }
                        },
                        error: function (response) {
                            notification("error", response.error);
                        }
                    });
                },
                error: function (response) {
                    notification("error", response.error);
                }
            });
        };

        $scope.$watch('display.sortField', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.bundles = $scope.bundles.sortBy(newValue);
            }
        });

        $scope.getStateStyle = function (state) {
            return Osgi.getStateStyle("badge", state);
        };

        $scope.getLabel = function (bundleObject) {
            var labelText;
            if ($scope.display.bundleField === "Name") {
                labelText = bundleObject.Name;
                if (labelText === "") {
                    labelText = bundleObject.SymbolicName;
                }
            } else {
                labelText = bundleObject.SymbolicName;
            }
            return labelText;
        };

        $scope.filterBundle = function (bundle) {
            if ($scope.display.startLevelFilter > 0 && bundle.StartLevel < $scope.display.startLevelFilter) {
                return false;
            }
            var labelText = $scope.getLabel(bundle);
            if ($scope.display.bundleFilter && !labelText.toLowerCase().has($scope.display.bundleFilter.toLowerCase())) {
                return false;
            }
            if (Core.isBlank($scope.display.bundleFilter)) {
                var answer = true;
                if (!$scope.display.showPlatformBundles) {
                    answer = !Karaf.isPlatformBundle(bundle['SymbolicName']);
                }
                if (answer && !$scope.display.showCxfBundles) {
                    answer = !Karaf.isCxfBundle(bundle['SymbolicName']);
                }
                if (answer && !$scope.display.showCamelBundles) {
                    answer = !Karaf.isCamelBundle(bundle['SymbolicName']);
                }
                return answer;
            }

            return true;
        };

        function processResponse(response) {
            var value = response['value'];

            var responseJson = angular.toJson(value);

            if ($scope.responseJson !== responseJson) {
                $scope.responseJson = responseJson;
                $scope.bundles = [];
                angular.forEach(value, function (value, key) {
                    var obj = {
                        Identifier: value.Identifier,
                        Name: "",
                        SymbolicName: value.SymbolicName,
                        Fragment: value.Fragment,
                        State: value.State,
                        Version: value.Version,
                        LastModified: new Date(Number(value.LastModified)),
                        Location: value.Location,
                        StartLevel: undefined
                    };
                    if (value.Headers['Bundle-Name']) {
                        obj.Name = value.Headers['Bundle-Name']['Value'];
                    }
                    $scope.bundles.push(obj);
                });

                $scope.bundles = $scope.bundles.sortBy($scope.display.sortField);

                Core.$apply($scope);

                // Obtain start level information for all the bundles, let's do this async though
                setTimeout(function () {
                    var requests = [];

                    for (var i = 0; i < $scope.bundles.length; i++) {
                        var b = $scope.bundles[i];
                        requests.push({
                            type: 'exec', mbean: Osgi.getSelectionBundleMBean(workspace),
                            operation: 'getStartLevel(long)',
                            arguments: [b.Identifier]
                        });
                    }

                    var outstanding = requests.length;

                    jolokia.request(requests, onSuccess(function (response) {
                        var id = response['request']['arguments'].first();
                        if (angular.isDefined(id)) {
                            var bundle = $scope.bundles[id];
                            if (bundle) {
                                Osgi.log.debug("Setting bundle: ", bundle['Identifier'], " start level to: ", response['value']);
                                bundle['StartLevel'] = response['value'];
                            }
                        }
                        outstanding = outstanding - 1;
                        Osgi.log.debug("oustanding responses: ", outstanding);
                        if (outstanding === 0) {
                            Osgi.log.debug("Updating page...");
                            Core.$apply($scope);
                        }
                    }));
                }, 500);
            }
        }

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Osgi.getSelectionBundleMBean(workspace),
            operation: 'listBundles()'
        }, onSuccess(processResponse));
    }
    Osgi.BundleListController = BundleListController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function ServiceController($scope, $filter, workspace, $templateCache, $compile) {
        var dateFilter = $filter('date');

        $scope.widget = new DataTable.TableWidget($scope, $templateCache, $compile, [
            {
                "mDataProp": null,
                "sClass": "control center",
                "sDefaultContent": '<i class="icon-plus"></i>'
            },
            { "mDataProp": "Identifier" },
            { "mDataProp": "BundleIdentifier" },
            { "mDataProp": "objectClass" }
        ], {
            rowDetailTemplateId: 'osgiServiceTemplate',
            disableAddColumns: true
        });

        $scope.$watch('workspace.selection', function () {
            var mbean = Osgi.getSelectionServiceMBean(workspace);
            if (mbean) {
                var jolokia = workspace.jolokia;
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listServices()' }, onSuccess(populateTable));
            }
        });

        var populateTable = function (response) {
            Osgi.defaultServiceValues(workspace, $scope, response.value);
            $scope.widget.populateTable(response.value);
            Core.$apply($scope);
        };
    }
    Osgi.ServiceController = ServiceController;
})(Osgi || (Osgi = {}));
;
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function BundlesController($scope, workspace, jolokia) {
        $scope.result = {};
        $scope.bundles = [];
        $scope.selected = [];
        $scope.loading = true;
        $scope.bundleUrl = "";

        $scope.installDisabled = function () {
            return $scope.bundleUrl === "";
        };

        var columnDefs = [
            {
                field: 'Identifier',
                displayName: 'Identifier',
                width: "48",
                headerCellTemplate: '<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}} pagination-centered" title="Identifier"><i class="icon-tag"></i></div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div>'
            },
            {
                field: 'State',
                displayName: 'Bundle State',
                width: "24",
                headerCellTemplate: '<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}} pagination-centered" title="State"><i class="icon-tasks"></i></div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div>',
                cellTemplate: '<div class="ngCellText" title="{{row.getProperty(col.field)}}"><i class="{{row.getProperty(col.field)}}"></i></div>'
            },
            {
                field: 'Name',
                displayName: 'Name',
                width: "***",
                cellTemplate: '<div class="ngCellText"><a href="#/osgi/bundle/{{row.entity.Identifier}}?p=container">{{row.getProperty(col.field)}}</a></div>'
            },
            {
                field: 'SymbolicName',
                displayName: 'Symbolic Name',
                width: "***",
                cellTemplate: '<div class="ngCellText"><a href="#/osgi/bundle/{{row.entity.Identifier}}?p=container">{{row.getProperty(col.field)}}</a></div>'
            },
            {
                field: 'Version',
                displayName: 'Version',
                width: "**"
            },
            {
                field: 'Location',
                displayName: 'Update Location',
                width: "***"
            }
        ];

        $scope.gridOptions = {
            data: 'bundles',
            showFilter: false,
            selectedItems: $scope.selected,
            selectWithCheckboxOnly: true,
            columnDefs: columnDefs,
            filterOptions: {
                filterText: ''
            }
        };

        $scope.onResponse = function () {
            jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionBundleMBean(workspace),
                operation: 'listBundles()'
            }, {
                success: render,
                error: render
            });
        };

        $scope.controlBundles = function (op) {
            var startBundle = function (response) {
            };
            var ids = $scope.selected.map(function (b) {
                return b.Identifier;
            });
            if (!angular.isArray(ids)) {
                ids = [ids];
            }
            jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionFrameworkMBean(workspace),
                operation: op,
                arguments: [ids]
            }, {
                success: $scope.onResponse,
                error: $scope.onResponse
            });
        };

        $scope.stop = function () {
            $scope.controlBundles('stopBundles([J)');
        };

        $scope.start = function () {
            $scope.controlBundles('startBundles([J)');
        };

        $scope.update = function () {
            $scope.controlBundles('updateBundles([J)');
        };

        $scope.refresh = function () {
            $scope.controlBundles('refreshBundles([J)');
        };

        $scope.uninstall = function () {
            $scope.controlBundles('uninstallBundles([J)');
        };

        $scope.install = function () {
            jolokia.request({
                type: 'exec',
                mbean: Osgi.getSelectionFrameworkMBean(workspace),
                operation: "installBundle(java.lang.String)",
                arguments: [$scope.bundleUrl]
            }, {
                success: function (response) {
                    console.log("Got: ", response);
                    $scope.bundleUrl = "";
                    jolokia.request({
                        type: 'exec',
                        mbean: Osgi.getSelectionFrameworkMBean(workspace),
                        operation: "startBundle(long)",
                        arguments: [response.value]
                    }, {
                        success: $scope.onResponse,
                        error: $scope.onResponse
                    });
                },
                error: function (response) {
                    $scope.bundleUrl = "";
                    $scope.onResponse();
                }
            });
        };

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.selected.length = 0;
                $scope.result = response.value;
                $scope.bundles = [];
                angular.forEach($scope.result, function (value, key) {
                    var obj = {
                        Identifier: value.Identifier,
                        Name: "",
                        SymbolicName: value.SymbolicName,
                        State: value.State,
                        Version: value.Version,
                        LastModified: value.LastModified,
                        Location: value.Location
                    };
                    if (value.Headers['Bundle-Name']) {
                        obj.Name = value.Headers['Bundle-Name']['Value'];
                    }
                    $scope.bundles.push(obj);
                });
                $scope.loading = false;
                Core.$apply($scope);
            }
        }

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Osgi.getSelectionBundleMBean(workspace),
            operation: 'listBundles()'
        }, onSuccess(render));
    }
    Osgi.BundlesController = BundlesController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function PidController($scope, $timeout, $routeParams, $location, workspace, jolokia) {
        $scope.deletePropDialog = new UI.Dialog();
        $scope.deletePidDialog = new UI.Dialog();
        $scope.addPropertyDialog = new UI.Dialog();
        $scope.factoryPid = $routeParams.factoryPid;
        $scope.pid = $routeParams.pid || $scope.factoryPid;

        $scope.selectValues = {};

        $scope.modelLoaded = false;
        $scope.canSave = false;

        $scope.setEditMode = function (flag) {
            $scope.editMode = flag;
            $scope.formMode = flag ? "edit" : "view";
            if (!flag || !$scope.entity) {
                $scope.entity = {};
                updateTableContents();
            }
        };
        var startInEditMode = $scope.factoryPid && !$routeParams.pid;
        $scope.setEditMode(startInEditMode);

        $scope.$on("hawtio.form.modelChange", function () {
            if ($scope.modelLoaded) {
                // TODO lets check if we've really changed the values!
                enableCanSave();
                Core.$apply($scope);
            }
        });

        Osgi.initProfileScope($scope, $routeParams, $location, localStorage, jolokia, workspace, function () {
            updateTableContents();
        });

        function updatePid(mbean, pid, data) {
            var completeFn = function (response) {
                notification("success", "Successfully updated pid: " + pid);

                if (pid && $scope.factoryPid && !$routeParams.pid && !$scope.zkPid) {
                    // we've just created a new pid so lets move to the full pid URL
                    var newPath = Osgi.createConfigPidPath($scope, pid, $scope.factoryPid);
                    $location.path(newPath);
                } else {
                    $scope.setEditMode(false);
                    $scope.canSave = false;
                    $scope.saved = true;
                }
            };
            var callback = onSuccess(completeFn, errorHandler("Failed to update: " + pid));
            if ($scope.inFabricProfile) {
                jolokia.execute(Fabric.managerMBean, "setProfileProperties", $scope.versionId, $scope.profileId, pid, data, callback);
            } else {
                var json = JSON.stringify(data);
                $scope.jolokia.execute(mbean, "configAdminUpdate", pid, json, callback);
            }
        }

        $scope.pidSave = function () {
            var data = {};

            angular.forEach($scope.entity, function (value, key) {
                var text = undefined;
                if (angular.isString(value)) {
                    text = value;
                } else if (angular.isDefined(value)) {
                    text = value.toString();
                }
                if (angular.isDefined(text)) {
                    data[decodeKey(key)] = text;
                }
            });

            //log.info("about to update value " + angular.toJson(data));
            var mbean = Osgi.getHawtioConfigAdminMBean(workspace);
            if (mbean) {
                var pidMBean = Osgi.getSelectionConfigAdminMBean($scope.workspace);
                var pid = $scope.pid;
                var zkPid = $scope.zkPid;
                var factoryPid = $scope.factoryPid;
                if (factoryPid && pidMBean && !zkPid) {
                    // lets generate a new pid
                    $scope.jolokia.execute(pidMBean, "createFactoryConfiguration", factoryPid, onSuccess(function (response) {
                        pid = response;
                        if (pid) {
                            updatePid(mbean, pid, data);
                        }
                    }, errorHandler("Failed to create new PID: ")));
                } else {
                    if (zkPid) {
                        pid = zkPid;
                    }
                    updatePid(mbean, pid, data);
                }
            }
        };

        function errorHandler(message) {
            return {
                error: function (response) {
                    notification("error", message + "\n" + response['error'] || response);
                    Core.defaultJolokiaErrorHandler(response);
                }
            };
        }

        function enableCanSave() {
            if ($scope.editMode) {
                $scope.canSave = true;
            }
        }

        $scope.addPropertyConfirmed = function (key, value) {
            $scope.addPropertyDialog.close();
            $scope.configValues[key] = {
                Key: key,
                Value: value,
                Type: "String"
            };
            enableCanSave();
            updateSchema();
        };

        $scope.deletePidProp = function (e) {
            $scope.deleteKey = e.Key;
            $scope.deletePropDialog.open();
        };

        $scope.deletePidPropConfirmed = function () {
            $scope.deletePropDialog.close();
            var cell = document.getElementById("pid." + $scope.deleteKey);
            cell.parentElement.remove();
            enableCanSave();
        };

        $scope.deletePidConfirmed = function () {
            $scope.deletePidDialog.close();

            var mbean = Osgi.getSelectionConfigAdminMBean($scope.workspace);
            if (mbean) {
                $scope.jolokia.request({
                    type: "exec",
                    mbean: mbean,
                    operation: 'delete',
                    arguments: [$scope.pid]
                }, {
                    error: function (response) {
                        notification("error", response.error);
                    },
                    success: function (response) {
                        notification("success", "Successfully deleted pid: " + $scope.pid);
                        $location.path($scope.configurationsLink);
                    }
                });
            }
        };

        function populateTable(response) {
            $scope.modelLoaded = true;
            var configValues = response || {};
            $scope.configValues = configValues;
            $scope.zkPid = Core.pathGet(configValues, ["fabric.zookeeper.pid", "Value"]);

            if ($scope.zkPid && $scope.saved) {
                // lets load the current properties direct from git
                // in case we have just saved them into git and config admin hasn't yet
                // quite caught up yet (to avoid freaking the user out that things look like
                // changes got reverted ;)
                function onProfileProperties(gitProperties) {
                    angular.forEach(gitProperties, function (value, key) {
                        var configProperty = configValues[key];
                        if (configProperty) {
                            configProperty.Value = value;
                        }
                    });
                    updateSchemaAndLoadMetaType();
                    Core.$apply($scope);
                }
                jolokia.execute(Fabric.managerMBean, "getProfileProperties", $scope.versionId, $scope.profileId, $scope.zkPid, onSuccess(onProfileProperties));
            } else {
                updateSchemaAndLoadMetaType();
            }
        }

        function updateSchemaAndLoadMetaType() {
            updateSchema();
            var metaTypeMBean = Osgi.getMetaTypeMBean($scope.workspace);
            var configValues = $scope.configValues;
            if (metaTypeMBean && configValues) {
                var locale = null;
                var pid = null;
                var factoryId = configValues["service.factoryPid"];
                if (factoryId) {
                    pid = factoryId["Value"];
                }
                pid = pid || $scope.pid;
                $scope.jolokia.execute(metaTypeMBean, "getPidMetaTypeObject", pid, locale, onSuccess(onMetaType));
            }
            Core.$apply($scope);
        }

        function onMetaType(response) {
            $scope.metaType = response;
            updateSchema();
            Core.$apply($scope);
        }

        /**
        * Updates the JSON schema model
        */
        function updateSchema() {
            var properties = {};
            var required = [];
            $scope.defaultValues = {};
            var schema = {
                type: "object",
                required: required,
                properties: properties
            };
            $scope.schema = schema;
            var inputClass = "span12";
            var labelClass = "control-label";

            var inputClassArray = "span11";
            var labelClassArray = labelClass;

            var metaType = $scope.metaType;
            if (metaType) {
                schema["id"] = metaType.id;
                schema["name"] = metaType.name;
                schema["description"] = metaType.description;

                angular.forEach(metaType.attributes, function (attribute) {
                    var id = attribute.id;
                    if (isValidProperty(id)) {
                        var key = encodeKey(id);
                        var typeName = asJsonSchemaType(attribute.typeName, attribute.id);
                        var attributeProperties = {
                            title: attribute.name,
                            tooltip: attribute.description,
                            'input-attributes': {
                                class: inputClass
                            },
                            'label-attributes': {
                                class: labelClass
                            },
                            type: typeName
                        };
                        if (attribute.typeName === "char") {
                            attributeProperties["maxLength"] = 1;
                            attributeProperties["minLength"] = 1;
                        }
                        var cardinality = attribute.cardinality;
                        if (cardinality) {
                            attributeProperties.type = "array";
                            attributeProperties["items"] = {
                                'input-attributes': {
                                    class: inputClassArray
                                },
                                'label-attributes': {
                                    class: labelClassArray
                                },
                                "type": typeName
                            };
                        }
                        if (attribute.required) {
                            required.push(id);
                        }
                        var defaultValue = attribute.defaultValue;
                        if (defaultValue) {
                            if (angular.isArray(defaultValue) && defaultValue.length === 1) {
                                defaultValue = defaultValue[0];
                            }

                            //attributeProperties["default"] = defaultValue;
                            // TODO convert to boolean / number?
                            $scope.defaultValues[key] = defaultValue;
                        }
                        var optionLabels = attribute.optionLabels;
                        var optionValues = attribute.optionValues;
                        if (optionLabels && optionLabels.length && optionValues && optionValues.length) {
                            var enumObject = {};
                            for (var i = 0; i < optionLabels.length; i++) {
                                var label = optionLabels[i];
                                var value = optionValues[i];
                                enumObject[value] = label;
                            }
                            $scope.selectValues[key] = enumObject;
                            Core.pathSet(attributeProperties, ['input-element'], "select");
                            Core.pathSet(attributeProperties, ['input-attributes', "ng-options"], "key as value for (key, value) in selectValues." + key);
                        }
                        properties[key] = attributeProperties;
                    }
                });
            }

            // now add all the missing properties...
            var entity = {};
            angular.forEach($scope.configValues, function (value, rawKey) {
                if (isValidProperty(rawKey)) {
                    var key = encodeKey(rawKey);
                    var attrValue = value;
                    var attrType = "string";
                    if (angular.isObject(value)) {
                        attrValue = value.Value;
                        attrType = asJsonSchemaType(value.Type, rawKey);
                    }
                    var property = properties[key];
                    if (!property) {
                        properties[key] = {
                            'input-attributes': {
                                class: inputClass
                            },
                            'label-attributes': {
                                class: labelClass
                            },
                            type: attrType
                        };
                    } else {
                        var propertyType = property["type"];
                        if ("array" === propertyType) {
                            if (!angular.isArray(attrValue)) {
                                attrValue = attrValue ? attrValue.split(",") : [];
                            }
                        }
                    }

                    //comply with Forms.safeIdentifier in 'forms/js/formHelpers.ts'
                    key = key.replace(/-/g, "_");
                    entity[key] = attrValue;
                }
            });

            // add default values for missing values
            angular.forEach($scope.defaultValues, function (value, key) {
                var current = entity[key];
                if (!angular.isDefined(current)) {
                    //log.info("updating entity " + key + " with default: " + value + " as was: " + current);
                    entity[key] = value;
                }
            });

            //log.info("default values: " + angular.toJson($scope.defaultValues));
            $scope.entity = entity;
        }

        var ignorePropertyIds = ["service.pid", "service.factoryPid", "fabric.zookeeper.pid"];

        function isValidProperty(id) {
            return id && ignorePropertyIds.indexOf(id) < 0;
        }

        function encodeKey(key) {
            return key.replace(/\./g, "__");
        }

        function decodeKey(key) {
            return key.replace(/__/g, ".");
        }

        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 === "string") {
                    // TODO hack to try force password type on dodgy metadata such as pax web
                    if (id && id.endsWith("password")) {
                        return "password";
                    }
                    return "string";
                }
                return typeName;
            } else {
                return "string";
            }
        }

        function updateTableContents() {
            $scope.modelLoaded = false;
            Osgi.getConfigurationProperties($scope.workspace, $scope.jolokia, $scope.pid, populateTable);
        }
    }
    Osgi.PidController = PidController;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    var OsgiGraphBuilder = (function () {
        function OsgiGraphBuilder(osgiDataService, bundleFilter, packageFilter, showServices, showPackages, hideUnused) {
            this.filteredBundles = {};
            this.bundles = null;
            this.services = null;
            this.packages = null;
            this.PREFIX_BUNDLE = "Bundle-";
            this.PREFIX_SVC = "Service-";
            this.PREFIX_PKG = "Package-";
            this.osgiDataService = osgiDataService;
            this.bundleFilter = bundleFilter;
            this.packageFilter = packageFilter;
            this.showServices = showServices;
            this.showPackages = showPackages;
            this.hideUnused = hideUnused;

            this.graphBuilder = new ForceGraph.GraphBuilder();
        }
        OsgiGraphBuilder.prototype.getBundles = function () {
            if (this.bundles == null) {
                this.bundles = this.osgiDataService.getBundles();
            }
            return this.bundles;
        };

        OsgiGraphBuilder.prototype.getServices = function () {
            if (this.services == null) {
                this.services = this.osgiDataService.getServices();
            }
            return this.services;
        };

        OsgiGraphBuilder.prototype.getPackages = function () {
            if (this.packages == null) {
                this.packages = this.osgiDataService.getPackages();
            }
            return this.packages;
        };

        OsgiGraphBuilder.prototype.bundleNodeId = function (bundle) {
            return this.PREFIX_BUNDLE + bundle.Identifier;
        };

        OsgiGraphBuilder.prototype.serviceNodeId = function (service) {
            return this.PREFIX_SVC + service.Identifier;
        };

        OsgiGraphBuilder.prototype.pkgNodeId = function (pkg) {
            return this.PREFIX_PKG + pkg.Name + "-" + pkg.Version;
        };

        // Create a service node from a given service
        OsgiGraphBuilder.prototype.buildSvcNode = function (service) {
            return {
                id: this.serviceNodeId(service),
                name: "" + service.Identifier,
                type: "service",
                used: false,
                //                image: {
                //                    url: "/hawtio/img/icons/osgi/service.png",
                //                    width: 32,
                //                    height:32
                //                },
                popup: {
                    title: "Service [" + service.Identifier + "]",
                    content: (function () {
                        var result = "";

                        if (service != null) {
                            service.objectClass.forEach(function (clazz) {
                                if (result.length > 0) {
                                    result = result + "<br/>";
                                }
                                result = result + clazz;
                            });
                        }

                        return result;
                    })
                }
            };
        };

        // Create a bundle node for a given bundle
        OsgiGraphBuilder.prototype.buildBundleNode = function (bundle) {
            return {
                id: this.bundleNodeId(bundle),
                name: bundle.SymbolicName,
                type: "bundle",
                used: false,
                navUrl: "#/osgi/bundle/" + bundle.Identifier,
                //                image: {
                //                    url: "/hawtio/img/icons/osgi/bundle.png",
                //                    width: 32,
                //                    height:32
                //                },
                popup: {
                    title: "Bundle [" + bundle.Identifier + "]",
                    content: "<p>" + bundle.SymbolicName + "<br/>Version " + bundle.Version + "</p>"
                }
            };
        };

        OsgiGraphBuilder.prototype.buildPackageNode = function (pkg) {
            return {
                id: this.pkgNodeId(pkg),
                name: pkg.Name,
                type: "package",
                used: false,
                popup: {
                    title: "Package [" + pkg.Name + "]",
                    content: "<p>" + pkg.Version + "</p>"
                }
            };
        };

        OsgiGraphBuilder.prototype.exportingBundle = function (pkg) {
            var _this = this;
            var result = null;

            pkg.ExportingBundles.forEach(function (bundleId) {
                if (_this.filteredBundles[_this.PREFIX_BUNDLE + bundleId] != null) {
                    result = bundleId;
                }
            });
            return result;
        };

        OsgiGraphBuilder.prototype.addFilteredBundles = function () {
            var _this = this;
            d3.values(this.getBundles()).forEach(function (bundle) {
                if (_this.bundleFilter == null || _this.bundleFilter == "" || bundle.SymbolicName.startsWith(_this.bundleFilter)) {
                    var bundleNode = _this.buildBundleNode(bundle);

                    _this.filteredBundles[bundleNode.id] = bundle;

                    bundleNode.used = true;

                    _this.graphBuilder.addNode(bundleNode);

                    if (_this.showServices) {
                        var services = _this.getServices();

                        bundle.RegisteredServices.forEach(function (sid) {
                            var svcNode = _this.buildSvcNode(services[sid]);
                            _this.graphBuilder.addNode(svcNode);
                            _this.graphBuilder.addLink(bundleNode.id, svcNode.id, "registered");
                        });
                    }
                }
            });
        };

        OsgiGraphBuilder.prototype.addFilteredServices = function () {
            var _this = this;
            if (this.showServices) {
                d3.values(this.getBundles()).forEach(function (bundle) {
                    bundle.ServicesInUse.forEach(function (sid) {
                        var svcNodeId = _this.PREFIX_SVC + sid;

                        if (_this.graphBuilder.getNode(svcNodeId) != null) {
                            _this.graphBuilder.getNode(svcNodeId).used = true;

                            var bundleNode = _this.graphBuilder.getNode(_this.bundleNodeId(bundle)) || _this.buildBundleNode(bundle);
                            bundleNode.used = true;

                            _this.graphBuilder.addNode(bundleNode);
                            _this.graphBuilder.addLink(svcNodeId, bundleNode.id, "inuse");
                        }
                    });
                });
            }
        };

        OsgiGraphBuilder.prototype.addFilteredPackages = function () {
            var _this = this;
            if (this.showPackages) {
                d3.values(this.getPackages()).forEach(function (pkg) {
                    if (_this.packageFilter == null || _this.packageFilter == "" || pkg.Name.startsWith(_this.packageFilter)) {
                        var exportingId = _this.exportingBundle(pkg);

                        if (exportingId != null) {
                            var bundleNode = _this.graphBuilder.getNode(_this.PREFIX_BUNDLE + exportingId);
                            bundleNode.used = true;

                            var pkgNode = _this.buildPackageNode(pkg);

                            _this.graphBuilder.addNode(pkgNode);
                            _this.graphBuilder.addLink(bundleNode.id, pkgNode.id, "registered");

                            pkg.ImportingBundles.forEach(function (bundleId) {
                                var bundleNode = _this.graphBuilder.getNode(_this.PREFIX_BUNDLE + bundleId) || _this.buildBundleNode(_this.getBundles()[bundleId]);
                                bundleNode.used = true;
                                pkgNode.used = true;

                                _this.graphBuilder.addNode(bundleNode);
                                _this.graphBuilder.addLink(bundleNode.id, pkgNode.id, "inuse");
                            });
                        }
                    }
                });
            }
        };

        OsgiGraphBuilder.prototype.buildGraph = function () {
            var _this = this;
            this.addFilteredBundles();
            this.addFilteredServices();
            this.addFilteredPackages();

            if (this.hideUnused) {
                // this will filter out all nodes that are not marked as used in our data model
                this.graphBuilder.filterNodes(function (node) {
                    return node.used;
                });

                // this will remove all nodes that do not have connections after filtering the unused nodes
                this.graphBuilder.filterNodes(function (node) {
                    return _this.graphBuilder.hasLinks(node.id);
                });
            }

            return this.graphBuilder.buildGraph();
        };
        return OsgiGraphBuilder;
    })();
    Osgi.OsgiGraphBuilder = OsgiGraphBuilder;
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    function BundleController($scope, $location, workspace, $routeParams, jolokia) {
        $scope.bundleId = $routeParams.bundleId;

        updateTableContents();

        $scope.showValue = function (key) {
            switch (key) {
                case "Bundle-Name":
                case "Bundle-SymbolicName":
                case "Bundle-Version":
                case "Export-Package":
                case "Import-Package":
                    return false;
                default:
                    return true;
            }
        };

        $scope.executeLoadClass = function (clazz) {
            var mbean = Osgi.getHawtioOSGiToolsMBean(workspace);
            if (mbean) {
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'getLoadClassOrigin', arguments: [$scope.bundleId, clazz] }, {
                    success: function (response) {
                        var divEl = document.getElementById("loadClassResult");
                        var resultBundle = response.value;
                        var style;
                        var resultTxt;
                        if (resultBundle === -1) {
                            style = "";
                            resultTxt = "Class can not be loaded from this bundle.";
                        } else {
                            style = "alert-success";
                            resultTxt = "Class is served from Bundle " + Osgi.bundleLinks(workspace, resultBundle);
                        }
                        divEl.innerHTML += "<div class='alert " + style + "'>" + "<button type='button' class='close' data-dismiss='alert'>&times;</button>" + "Loading class <strong>" + clazz + "</strong> in Bundle " + $scope.bundleId + ". " + resultTxt + "</div>";
                    },
                    error: function (response) {
                        inspectReportError(response);
                    }
                });
            } else {
                inspectReportNoMBeanFound();
            }
        };

        $scope.executeFindResource = function (resource) {
            var mbean = Osgi.getHawtioOSGiToolsMBean(workspace);
            if (mbean) {
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'getResourceURL', arguments: [$scope.bundleId, resource] }, {
                    success: function (response) {
                        var divEl = document.getElementById("loadClassResult");
                        var resultURL = response.value;
                        var style;
                        var resultTxt;
                        if (resultURL === null) {
                            style = "";
                            resultTxt = "Resource can not be found from this bundle.";
                        } else {
                            style = "alert-success";
                            resultTxt = "Resource is available from: " + resultURL;
                        }
                        divEl.innerHTML += "<div class='alert " + style + "'>" + "<button type='button' class='close' data-dismiss='alert'>&times;</button>" + "Finding resource <strong>" + resource + "</strong> in Bundle " + $scope.bundleId + ". " + resultTxt + "</div>";
                    },
                    error: function (response) {
                        inspectReportError(response);
                    }
                });
            } else {
                inspectReportNoMBeanFound();
            }
        };

        $scope.mavenLink = function (row) {
            if (angular.isObject(row)) {
                return Maven.mavenLink(row.Location);
            }

            // TODO try using the LogQuery mbean to find the mvn coords for a bundle id?
            return "";
        };

        $scope.startBundle = function (bundleId) {
            jolokia.request([
                { type: 'exec', mbean: Osgi.getSelectionFrameworkMBean(workspace), operation: 'startBundle', arguments: [bundleId] }
            ], onSuccess(updateTableContents));
        };

        $scope.stopBundle = function (bundleId) {
            jolokia.request([
                { type: 'exec', mbean: Osgi.getSelectionFrameworkMBean(workspace), operation: 'stopBundle', arguments: [bundleId] }
            ], onSuccess(updateTableContents));
        };

        $scope.updatehBundle = function (bundleId) {
            jolokia.request([
                { type: 'exec', mbean: Osgi.getSelectionFrameworkMBean(workspace), operation: 'updateBundle', arguments: [bundleId] }
            ], onSuccess(updateTableContents));
        };

        $scope.refreshBundle = function (bundleId) {
            jolokia.request([
                { type: 'exec', mbean: Osgi.getSelectionFrameworkMBean(workspace), operation: 'refreshBundle', arguments: [bundleId] }
            ], onSuccess(updateTableContents));
        };

        $scope.uninstallBundle = function (bundleId) {
            jolokia.request([{
                    type: 'exec',
                    mbean: Osgi.getSelectionFrameworkMBean(workspace),
                    operation: 'uninstallBundle',
                    arguments: [bundleId]
                }], onSuccess(function () {
                $location.path("/osgi/bundle-list");
                Core.$apply($scope);
            }));
        };

        function inspectReportNoMBeanFound() {
            var divEl = document.getElementById("loadClassResult");
            divEl.innerHTML += "<div class='alert alert-error'>" + "<button type='button' class='close' data-dismiss='alert'>&times;</button>" + "The hawtio.OSGiTools MBean is not available. Please contact technical support." + "</div>";
        }

        function inspectReportError(response) {
            var divEl = document.getElementById("loadClassResult");
            divEl.innerHTML += "<div class='alert alert-error'>" + "<button type='button' class='close' data-dismiss='alert'>&times;</button>" + "Problem invoking hawtio.OSGiTools MBean. " + response + "</div>";
        }

        function populateTable(response) {
            var values = response.value;
            $scope.bundles = values;

            // now find the row based on the selection ui
            Osgi.defaultBundleValues(workspace, $scope, values);
            $scope.row = Osgi.findBundle($scope.bundleId, values);
            Core.$apply($scope);

            // This trick is to ensure that the popover is properly visible if it is
            // smaller than the accordion
            $('.accordion-body.collapse').hover(function () {
                $(this).css('overflow', 'visible');
            }, function () {
                $(this).css('overflow', 'hidden');
            });

            // setup tooltips
            $("#bsn").tooltip({
                title: readBSNHeaderData($scope.row.Headers["Bundle-SymbolicName"].Value),
                placement: "right" });

            createImportPackageSection();
            createExportPackageSection();
            populateServicesSection();
        }

        function createImportPackageSection() {
            // setup popovers
            var importPackageHeaders = Osgi.parseManifestHeader($scope.row.Headers, "Import-Package");
            for (var pkg in $scope.row.ImportData) {
                var data = importPackageHeaders[pkg];
                var po = "<small><table>" + "<tr><td><strong>Imported Version=</strong>" + $scope.row.ImportData[pkg].ReportedVersion + "</td></tr>";
                if (data !== undefined) {
                    // This happens in case the package was imported due to a DynamicImport-Package
                    po += formatAttributesAndDirectivesForPopover(data, false);
                    if (importPackageHeaders[pkg]["Dresolution"] !== "optional") {
                        $(document.getElementById("import." + pkg)).addClass("badge-info");
                    }
                } else {
                    // This is a dynamic import
                    $(document.getElementById("import." + pkg)).addClass("badge-important");
                    var reason = $scope.row.Headers["DynamicImport-Package"];
                    if (reason !== undefined) {
                        reason = reason.Value;
                        po += "<tr><td>Dynamic Import. Imported due to:</td></tr>";
                        po += "<tr><td><strong>DynamicImport-Package=</strong>" + reason + "</td></tr>";
                    }
                }
                po += "</table></small>";
                $(document.getElementById("import." + pkg)).popover({ title: "attributes and directives", content: po, trigger: "hover", html: true });

                // Unset the value so that we can see whether there are any unbound optional imports left...
                importPackageHeaders[pkg] = undefined;
            }

            var unsatisfied = "";
            for (var pkg in importPackageHeaders) {
                if (importPackageHeaders[pkg] === undefined) {
                    continue;
                }
                if ($scope.row.ExportData[pkg] !== undefined) {
                    continue;
                }
                unsatisfied += "<tr><td><div class='less-big badge badge-warning' id='unsatisfied." + pkg + "'>" + pkg + "</div></td></tr>";
            }

            if (unsatisfied !== "") {
                unsatisfied = "<p/><p class='text-warning'>The following optional imports were not satisfied:<table>" + unsatisfied + "</table></p>";
                document.getElementById("unsatisfiedOptionalImports").innerHTML = unsatisfied;
            }

            for (var pkg in importPackageHeaders) {
                if (importPackageHeaders[pkg] === undefined) {
                    continue;
                }
                var po = "<small><table>";
                po += formatAttributesAndDirectivesForPopover(importPackageHeaders[pkg], false);
                po += "</table></small>";
                $(document.getElementById("unsatisfied." + pkg)).popover({ title: "attributes and directives", content: po, trigger: "hover", html: true });
            }
        }

        function createExportPackageSection() {
            // setup popovers
            var exportPackageHeaders = Osgi.parseManifestHeader($scope.row.Headers, "Export-Package");
            for (var pkg in $scope.row.ExportData) {
                var po = "<small><table>" + "<tr><td><strong>Exported Version=</strong>" + $scope.row.ExportData[pkg].ReportedVersion + "</td></tr>";
                po += formatAttributesAndDirectivesForPopover(exportPackageHeaders[pkg], true);
                po += "</table></small>";
                $(document.getElementById("export." + pkg)).popover({ title: "attributes and directives", content: po, trigger: "hover", html: true });
            }
        }

        function populateServicesSection() {
            if (($scope.row.RegisteredServices === undefined || $scope.row.RegisteredServices.length === 0) && ($scope.row.ServicesInUse === undefined || $scope.row.ServicesInUse === 0)) {
                // no services for this bundle
                return;
            }

            var mbean = Osgi.getSelectionServiceMBean(workspace);
            if (mbean) {
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listServices()' }, onSuccess(updateServices));
            }
        }

        function updateServices(result) {
            var data = result.value;
            for (var id in data) {
                var reg = document.getElementById("registers.service." + id);
                var uses = document.getElementById("uses.service." + id);

                if ((reg === undefined || reg === null) && (uses === undefined || uses === null)) {
                    continue;
                }

                jolokia.request({
                    type: 'exec', mbean: Osgi.getSelectionServiceMBean(workspace),
                    operation: 'getProperties', arguments: [id] }, onSuccess(function (svcId, regEl, usesEl) {
                    return function (resp) {
                        var props = resp.value;
                        var sortedKeys = Object.keys(props).sort();
                        var po = "<small><table>";
                        for (var i = 0; i < sortedKeys.length; i++) {
                            var value = props[sortedKeys[i]];
                            if (value !== undefined) {
                                var fval = value.Value;
                                if (fval.length > 15) {
                                    fval = fval.replace(/[,]/g, ",<br/>&nbsp;&nbsp;");
                                }

                                po += "<tr><td valign='top'>" + sortedKeys[i] + "</td><td>" + fval + "</td></tr>";
                            }
                        }

                        var regBID = data[svcId].BundleIdentifier;
                        po += "<tr><td>Registered&nbsp;by</td><td>Bundle " + regBID + " <div class='less-big label'>" + $scope.bundles[regBID].SymbolicName + "</div></td></tr>";
                        po += "</table></small>";

                        if (regEl !== undefined && regEl !== null) {
                            regEl.innerText = " " + formatServiceName(data[svcId].objectClass);
                            $(regEl).popover({ title: "service properties", content: po, trigger: "hover", html: true });
                        }
                        if (usesEl !== undefined && usesEl !== null) {
                            usesEl.innerText = " " + formatServiceName(data[svcId].objectClass);
                            $(usesEl).popover({ title: "service properties", content: po, trigger: "hover", html: true });
                        }
                    };
                }(id, reg, uses)));
            }
        }

        function updateTableContents() {
            //console.log("Loading the bundles");
            var mbean = Osgi.getSelectionBundleMBean(workspace);
            if (mbean) {
                jolokia.request({ type: 'exec', mbean: mbean, operation: 'listBundles()' }, onSuccess(populateTable));
            }
        }
    }
    Osgi.BundleController = BundleController;

    // These functions are exported independently to facilitate unit testing
    function readBSNHeaderData(header) {
        var idx = header.indexOf(";");
        if (idx <= 0) {
            return "";
        }
        return header.substring(idx + 1).trim();
    }
    Osgi.readBSNHeaderData = readBSNHeaderData;

    function formatAttributesAndDirectivesForPopover(data, skipVersion) {
        var str = "";
        var sortedKeys = Object.keys(data).sort();
        for (var i = 0; i < sortedKeys.length; i++) {
            var da = sortedKeys[i];
            var type = da.charAt(0);

            var separator = "";
            var txtClass;
            if (type === "A") {
                separator = "=";
                txtClass = "text-info";
            }
            if (type === "D") {
                separator = ":=";
                txtClass = "muted";
            }

            if (separator !== "") {
                if (skipVersion) {
                    if (da === "Aversion") {
                        continue;
                    }
                }

                var value = data[da];
                if (value.length > 15) {
                    value = value.replace(/[,]/g, ",<br/>&nbsp;&nbsp;");
                }
                str += "<tr><td><strong class='" + txtClass + "'>" + da.substring(1) + "</strong>" + separator + value + "</td></tr>";
            }
        }
        return str;
    }
    Osgi.formatAttributesAndDirectivesForPopover = formatAttributesAndDirectivesForPopover;

    function formatServiceName(objClass) {
        if (Object.isArray(objClass)) {
            return formatServiceNameArray(objClass);
        }
        var name = objClass.toString();
        var idx = name.lastIndexOf('.');
        return name.substring(idx + 1);
    }
    Osgi.formatServiceName = formatServiceName;

    function formatServiceNameArray(objClass) {
        var rv = [];
        for (var i = 0; i < objClass.length; i++) {
            rv.add(formatServiceName(objClass[i]));
        }
        rv = rv.filter(function (elem, pos, self) {
            return self.indexOf(elem) === pos;
        });

        rv.sort();
        return rv.toString();
    }
})(Osgi || (Osgi = {}));
/**
* @module Osgi
*/
var Osgi;
(function (Osgi) {
    Osgi.log = Logger.get("OSGi");

    function defaultBundleValues(workspace, $scope, values) {
        var allValues = values;
        angular.forEach(values, function (row) {
            row["ImportData"] = parseActualPackages(row["ImportedPackages"]);
            row["ExportData"] = parseActualPackages(row["ExportedPackages"]);
            row["IdentifierLink"] = bundleLinks(workspace, row["Identifier"]);
            row["Hosts"] = labelBundleLinks(workspace, row["Hosts"], allValues);
            row["Fragments"] = labelBundleLinks(workspace, row["Fragments"], allValues);
            row["ImportedPackages"] = row["ImportedPackages"].union([]);
            row["StateStyle"] = getStateStyle("label", row["State"]);
            row["RequiringBundles"] = labelBundleLinks(workspace, row["RequiringBundles"], allValues);
        });
        return values;
    }
    Osgi.defaultBundleValues = defaultBundleValues;

    function getStateStyle(prefix, state) {
        switch (state) {
            case "INSTALLED":
                return prefix + "-important";
            case "RESOLVED":
                return prefix + "-inverse";
            case "STARTING":
                return prefix + "-warning";
            case "ACTIVE":
                return prefix + "-success";
            case "STOPPING":
                return prefix + "-info";
            case "UNINSTALLED":
                return "";
            default:
                return prefix + "-important";
        }
    }
    Osgi.getStateStyle = getStateStyle;

    function defaultServiceValues(workspace, $scope, values) {
        angular.forEach(values, function (row) {
            row["BundleIdentifier"] = bundleLinks(workspace, row["BundleIdentifier"]);
        });
        return values;
    }
    Osgi.defaultServiceValues = defaultServiceValues;

    function defaultPackageValues(workspace, $scope, values) {
        var packages = [];

        function onPackageEntry(packageEntry, row) {
            if (!row)
                row = packageEntry;
            var name = packageEntry["Name"];
            var version = packageEntry["Version"];
            if (name && !name.startsWith("#")) {
                packageEntry["VersionLink"] = "<a href='" + url("#/osgi/package/" + name + "/" + version + workspace.hash()) + "'>" + version + "</a>";
                var importingBundles = row["ImportingBundles"] || packageEntry["ImportingBundles"];
                var exportingBundles = row["ExportingBundles"] || packageEntry["ExportingBundles"];
                packageEntry["ImportingBundleLinks"] = bundleLinks(workspace, importingBundles);
                packageEntry["ImportingBundleLinks"] = bundleLinks(workspace, importingBundles);
                packageEntry["ExportingBundleLinks"] = bundleLinks(workspace, exportingBundles);
                packages.push(packageEntry);
            }
        }

        // the values could contain a child 'values' array of objects so use those directly
        var childValues = values.values;
        if (childValues) {
            angular.forEach(childValues, onPackageEntry);
        }
        angular.forEach(values, function (row) {
            angular.forEach(row, function (version) {
                angular.forEach(version, function (packageEntry) {
                    onPackageEntry(packageEntry, row);
                });
            });
        });
        return packages;
    }
    Osgi.defaultPackageValues = defaultPackageValues;

    function defaultConfigurationValues(workspace, $scope, values) {
        var array = [];
        angular.forEach(values, function (row) {
            var map = {};
            map["Pid"] = row[0];
            map["PidLink"] = "<a href='" + url("#/osgi/pid/" + row[0] + workspace.hash()) + "'>" + row[0] + "</a>";
            map["Bundle"] = row[1];
            array.push(map);
        });
        return array;
    }
    Osgi.defaultConfigurationValues = defaultConfigurationValues;

    function parseActualPackages(packages) {
        var result = {};
        for (var i = 0; i < packages.length; i++) {
            var pkg = packages[i];
            var idx = pkg.indexOf(";");
            if (idx > 0) {
                var name = pkg.substring(0, idx);
                var ver = pkg.substring(idx + 1);
                var data = result[name];
                if (data === undefined) {
                    data = {};
                    result[name] = data;
                }
                data["ReportedVersion"] = ver;
            }
        }
        return result;
    }
    Osgi.parseActualPackages = parseActualPackages;

    function parseManifestHeader(headers, name) {
        var result = {};
        var data = {};

        var hdr = headers[name];
        if (hdr === undefined) {
            return result;
        }
        var ephdr = hdr.Value;
        var inPkg = true;
        var inQuotes = false;
        var pkgName = "";
        var daDecl = "";
        for (var i = 0; i < ephdr.length; i++) {
            var c = ephdr[i];
            if (c === '"') {
                inQuotes = !inQuotes;
                continue;
            }
            if (inQuotes) {
                daDecl += c;
                continue;
            }

            // from here on we are never inside quotes
            if (c === ';') {
                if (inPkg) {
                    inPkg = false;
                } else {
                    handleDADecl(data, daDecl);

                    // reset directive and attribute variable
                    daDecl = "";
                }
                continue;
            }

            if (c === ',') {
                handleDADecl(data, daDecl);
                result[pkgName] = data;

                // reset data
                data = {};
                pkgName = "";
                daDecl = "";
                inPkg = true;
                continue;
            }

            if (inPkg) {
                pkgName += c;
            } else {
                daDecl += c;
            }
        }
        handleDADecl(data, daDecl);
        result[pkgName] = data;

        return result;
    }
    Osgi.parseManifestHeader = parseManifestHeader;

    function handleDADecl(data, daDecl) {
        var didx = daDecl.indexOf(":=");
        if (didx > 0) {
            data["D" + daDecl.substring(0, didx)] = daDecl.substring(didx + 2);
            return;
        }

        var aidx = daDecl.indexOf("=");
        if (aidx > 0) {
            data["A" + daDecl.substring(0, aidx)] = daDecl.substring(aidx + 1);
            return;
        }
    }

    function toCollection(values) {
        var collection = values;
        if (!angular.isArray(values)) {
            collection = [values];
        }
        return collection;
    }
    Osgi.toCollection = toCollection;

    function labelBundleLinks(workspace, values, allValues) {
        var answer = "";
        var sorted = toCollection(values).sort(function (a, b) {
            return a - b;
        });
        angular.forEach(sorted, function (value, key) {
            var prefix = "";
            if (answer.length > 0) {
                prefix = " ";
            }
            var info = allValues[value] || {};
            var labelText = info.SymbolicName;
            answer += prefix + "<a class='label' href='" + url("#/osgi/bundle/" + value + workspace.hash()) + "'>" + labelText + "</a>";
        });
        return answer;
    }
    Osgi.labelBundleLinks = labelBundleLinks;

    function bundleLinks(workspace, values) {
        var answer = "";
        var sorted = toCollection(values).sort(function (a, b) {
            return a - b;
        });
        angular.forEach(sorted, function (value, key) {
            var prefix = "";
            if (answer.length > 0) {
                prefix = " ";
            }
            answer += prefix + "<a class='label' href='" + url("#/osgi/bundle/" + value + workspace.hash()) + "'>" + value + "</a>";
        });
        return answer;
    }
    Osgi.bundleLinks = bundleLinks;

    function pidLinks(workspace, values) {
        var answer = "";
        angular.forEach(toCollection(values), function (value, key) {
            var prefix = "";
            if (answer.length > 0) {
                prefix = " ";
            }
            answer += prefix + "<a href='" + url("#/osgi/bundle/" + value + workspace.hash()) + "'>" + value + "</a>";
        });
        return answer;
    }
    Osgi.pidLinks = pidLinks;

    /**
    * Finds a bundle by id
    *
    * @method findBundle
    * @for Osgi
    * @param {String} bundleId
    * @param {Array} values
    * @return {any}
    *
    */
    function findBundle(bundleId, values) {
        var answer = "";
        angular.forEach(values, function (row) {
            var id = row["Identifier"];
            if (bundleId === id.toString()) {
                answer = row;
                return answer;
            }
        });
        return answer;
    }
    Osgi.findBundle = findBundle;

    function getSelectionBundleMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("osgi.core", "bundleState");
            return Osgi.findFirstObjectName(folder);
        }
        return null;
    }
    Osgi.getSelectionBundleMBean = getSelectionBundleMBean;

    /**
    * Walks the tree looking in the first child all the way down until we find an objectName
    * @method findFirstObjectName
    * @for Osgi
    * @param {Folder} node
    * @return {String}
    *
    */
    function findFirstObjectName(node) {
        if (node) {
            var answer = node.objectName;
            if (answer) {
                return answer;
            } else {
                var children = node.children;
                if (children && children.length) {
                    return findFirstObjectName(children[0]);
                }
            }
        }
        return null;
    }
    Osgi.findFirstObjectName = findFirstObjectName;

    function getSelectionFrameworkMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("osgi.core", "framework");
            return Osgi.findFirstObjectName(folder);
        }
        return null;
    }
    Osgi.getSelectionFrameworkMBean = getSelectionFrameworkMBean;
    function getSelectionServiceMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("osgi.core", "serviceState");
            return Osgi.findFirstObjectName(folder);
        }
        return null;
    }
    Osgi.getSelectionServiceMBean = getSelectionServiceMBean;

    function getSelectionPackageMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("osgi.core", "packageState");
            return Osgi.findFirstObjectName(folder);
        }
        return null;
    }
    Osgi.getSelectionPackageMBean = getSelectionPackageMBean;

    function getSelectionConfigAdminMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("osgi.compendium", "cm");
            return Osgi.findFirstObjectName(folder);
        }
        return null;
    }
    Osgi.getSelectionConfigAdminMBean = getSelectionConfigAdminMBean;

    function getMetaTypeMBean(workspace) {
        if (workspace) {
            var mbeanTypesToDomain = workspace.mbeanTypesToDomain;
            var typeFolder = mbeanTypesToDomain["MetaTypeFacade"] || {};
            var mbeanFolder = typeFolder["io.fabric8"] || {};
            return mbeanFolder["objectName"];
        }
        return null;
    }
    Osgi.getMetaTypeMBean = getMetaTypeMBean;

    function getHawtioOSGiToolsMBean(workspace) {
        if (workspace) {
            var mbeanTypesToDomain = workspace.mbeanTypesToDomain;
            var toolsFacades = mbeanTypesToDomain["OSGiTools"] || {};
            var hawtioFolder = toolsFacades["hawtio"] || {};
            return hawtioFolder["objectName"];
        }
        return null;
    }
    Osgi.getHawtioOSGiToolsMBean = getHawtioOSGiToolsMBean;
    function getHawtioConfigAdminMBean(workspace) {
        if (workspace) {
            var mbeanTypesToDomain = workspace.mbeanTypesToDomain;
            var typeFolder = mbeanTypesToDomain["ConfigAdmin"];
            var mbeanFolder = typeFolder["hawtio"];
            return mbeanFolder["objectName"];
        }
        return null;
    }
    Osgi.getHawtioConfigAdminMBean = getHawtioConfigAdminMBean;

    /**
    * Creates a link to the given configuration pid and/or factoryPid
    */
    function createConfigPidLink($scope, workspace, pid, factoryPid) {
        if (typeof factoryPid === "undefined") { factoryPid = null; }
        return url("#" + createConfigPidPath($scope, pid, factoryPid) + workspace.hash());
    }
    Osgi.createConfigPidLink = createConfigPidLink;

    /**
    * Creates a path to the given configuration pid and/or factoryPid
    */
    function createConfigPidPath($scope, pid, factoryPid) {
        if (typeof factoryPid === "undefined") { factoryPid = null; }
        var link;
        pid = pid || "";
        if (factoryPid) {
            link = pid + "/" + factoryPid;
        } else {
            link = pid;
        }
        var versionId = $scope.versionId;
        var profileId = $scope.profileId;
        if (versionId && versionId) {
            return "/wiki/branch/" + versionId + "/configuration/" + link + "/" + $scope.pageId;
        } else {
            return "/osgi/pid/" + link;
        }
    }
    Osgi.createConfigPidPath = createConfigPidPath;

    /**
    * A helper method which initialises a scope's jolokia to refer to a profile's jolokia if used in a Fabric
    * or use a local jolokia
    */
    function initProfileScope($scope, $routeParams, $location, localStorage, jolokia, workspace, initFn) {
        if (typeof initFn === "undefined") { initFn = null; }
        Wiki.initScope($scope, $routeParams, $location);
        $scope.versionId = $routeParams.versionId || $scope.branch;
        $scope.profileId = $routeParams.profileId || Fabric.pagePathToProfileId($scope.pageId);
        if (!$scope.pageId) {
            $scope.pageId = Fabric.fabricTopLevel + Fabric.profilePath($scope.profileId);
        }

        if (!initFn) {
            initFn = function () {
                return null;
            };
        }
        var versionId = $scope.versionId;
        var profileId = $scope.profileId;
        if (versionId && versionId) {
            $scope.inFabricProfile = true;
            $scope.configurationsLink = "/wiki/branch/" + versionId + "/configurations/" + $scope.pageId;

            Fabric.profileJolokia(jolokia, profileId, versionId, function (profileJolokia) {
                $scope.jolokia = profileJolokia;
                if (profileJolokia) {
                    $scope.workspace = Core.createRemoteWorkspace(profileJolokia, $location, localStorage);
                }
                initFn();
            });
        } else {
            $scope.configurationsLink = "/osgi/configurations";
            $scope.jolokia = jolokia;
            $scope.workspace = workspace;
            initFn();
        }
    }
    Osgi.initProfileScope = initProfileScope;

    function getConfigurationProperties(workspace, jolokia, pid, onDataFn) {
        var mbean = getSelectionConfigAdminMBean(workspace);
        var answer = null;
        if (jolokia && mbean) {
            answer = jolokia.execute(mbean, 'getProperties', pid, onSuccess(onDataFn));
        }
        return answer;
    }
    Osgi.getConfigurationProperties = getConfigurationProperties;

    /**
    * For a pid of the form "foo.generatedId" for a pid "foo" or "foo.bar" remove the "foo." prefix
    */
    function removeFactoryPidPrefix(pid, factoryPid) {
        if (pid && factoryPid) {
            if (pid.startsWith(factoryPid)) {
                return pid.substring(factoryPid.length + 1);
            }
            var idx = factoryPid.lastIndexOf(".");
            if (idx > 0) {
                var prefix = factoryPid.substring(0, idx + 1);
                return Core.trimLeading(pid, prefix);
            }
        }
        return pid;
    }
    Osgi.removeFactoryPidPrefix = removeFactoryPidPrefix;
})(Osgi || (Osgi = {}));
var ForceGraph;
(function (ForceGraph) {
    /**
    * GraphBuilder
    *
    * @class GraphBuilder
    */
    var GraphBuilder = (function () {
        function GraphBuilder() {
            this.nodes = {};
            this.links = [];
            this.linkTypes = {};
        }
        /**
        * Adds a node to this graph
        * @method addNode
        * @param {Object} node
        */
        GraphBuilder.prototype.addNode = function (node) {
            if (!this.nodes[node.id]) {
                this.nodes[node.id] = node;
            }
        };

        GraphBuilder.prototype.getNode = function (id) {
            return this.nodes[id];
        };

        GraphBuilder.prototype.hasLinks = function (id) {
            var _this = this;
            var result = false;

            this.links.forEach(function (link) {
                if (link.source.id == id || link.target.id == id) {
                    result = result || (_this.nodes[link.source.id] != null && _this.nodes[link.target.id] != null);
                }
            });
            return result;
        };

        GraphBuilder.prototype.addLink = function (srcId, targetId, linkType) {
            if ((this.nodes[srcId] != null) && (this.nodes[targetId] != null)) {
                this.links.push({
                    source: this.nodes[srcId],
                    target: this.nodes[targetId],
                    type: linkType
                });

                if (!this.linkTypes[linkType]) {
                    this.linkTypes[linkType] = {
                        used: true
                    };
                }
                ;
            }
        };

        GraphBuilder.prototype.nodeIndex = function (id, nodes) {
            var result = -1;
            var index = 0;

            for (index = 0; index < nodes.length; index++) {
                var node = nodes[index];
                if (node.id == id) {
                    result = index;
                    break;
                }
            }

            return result;
        };

        GraphBuilder.prototype.filterNodes = function (filter) {
            var filteredNodes = {};
            var newLinks = [];

            d3.values(this.nodes).forEach(function (node) {
                if (filter(node)) {
                    filteredNodes[node.id] = node;
                }
            });

            this.links.forEach(function (link) {
                if (filteredNodes[link.source.id] && filteredNodes[link.target.id]) {
                    newLinks.push(link);
                }
            });

            this.nodes = filteredNodes;
            this.links = newLinks;
        };

        GraphBuilder.prototype.buildGraph = function () {
            var _this = this;
            var graphNodes = [];
            var linktypes = d3.keys(this.linkTypes);
            var graphLinks = [];

            d3.values(this.nodes).forEach(function (node) {
                if (node.includeInGraph == null || node.includeInGraph) {
                    node.includeInGraph = true;
                    graphNodes.push(node);
                }
            });

            this.links.forEach(function (link) {
                if (_this.nodes[link.source.id] != null && _this.nodes[link.target.id] != null && _this.nodes[link.source.id].includeInGraph && _this.nodes[link.target.id].includeInGraph) {
                    graphLinks.push({
                        source: _this.nodeIndex(link.source.id, graphNodes),
                        target: _this.nodeIndex(link.target.id, graphNodes),
                        type: link.type
                    });
                }
            });

            return {
                nodes: graphNodes,
                links: graphLinks,
                linktypes: linktypes
            };
        };
        return GraphBuilder;
    })();
    ForceGraph.GraphBuilder = GraphBuilder;
})(ForceGraph || (ForceGraph = {}));
/**
* Force Graph plugin & directive
*
* @module ForceGraph
*/
var ForceGraph;
(function (ForceGraph) {
    var pluginName = 'forceGraph';

    angular.module(pluginName, ['bootstrap', 'ngResource']).directive('hawtioForceGraph', function () {
        return new ForceGraph.ForceGraphDirective();
    });

    hawtioPluginLoader.addModule(pluginName);
})(ForceGraph || (ForceGraph = {}));
var ForceGraph;
(function (ForceGraph) {
    var log = Logger.get("ForceGraph");

    var ForceGraphDirective = (function () {
        function ForceGraphDirective() {
            this.restrict = 'A';
            this.replace = true;
            this.transclude = false;
            this.scope = {
                graph: '=graph',
                nodesize: '@',
                selectedModel: '@',
                linkDistance: '@',
                markerKind: '@',
                charge: '@'
            };
            this.link = function ($scope, $element, $attrs) {
                $scope.trans = [0, 0];
                $scope.scale = 1;

                $scope.$watch('graph', function (oldVal, newVal) {
                    updateGraph();
                });

                $scope.redraw = function () {
                    $scope.trans = d3.event.translate;
                    $scope.scale = d3.event.scale;

                    $scope.viewport.attr("transform", "translate(" + $scope.trans + ")" + " scale(" + $scope.scale + ")");
                };

                // This is a callback for the animation
                $scope.tick = function () {
                    // provide curvy lines as curves are kind of hawt
                    $scope.graphEdges.attr("d", function (d) {
                        var dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy);
                        return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
                    });

                    // apply the translates coming from the layouter
                    $scope.graphNodes.attr("transform", function (d) {
                        return "translate(" + d.x + "," + d.y + ")";
                    });

                    $scope.graphLabels.attr("transform", function (d) {
                        return "translate(" + d.x + "," + d.y + ")";
                    });
                };

                $scope.mover = function (d) {
                    if (d.popup != null) {
                        $("#pop-up").fadeOut(100, function () {
                            // Popup content
                            if (d.popup.title != null) {
                                $("#pop-up-title").html(d.popup.title);
                            } else {
                                $("#pop-up-title").html("");
                            }

                            if (d.popup.content != null) {
                                $("#pop-up-content").html(d.popup.content);
                            } else {
                                $("#pop-up-content").html("");
                            }

                            // Popup position
                            var popLeft = (d.x * $scope.scale) + $scope.trans[0] + 20;
                            var popTop = (d.y * $scope.scale) + $scope.trans[1] + 20;

                            $("#pop-up").css({ "left": popLeft, "top": popTop });
                            $("#pop-up").fadeIn(100);
                        });
                    }
                };

                $scope.mout = function (d) {
                    $("#pop-up").fadeOut(50);
                    //d3.select(this).attr("fill","url(#ten1)");
                };

                var updateGraph = function () {
                    var canvas = $($element);

                    // TODO: determine the canvas size dynamically
                    var h = $($element).parent().height();
                    var w = $($element).parent().width();
                    var i = 0;

                    canvas.children("svg").remove();

                    // First we create the top level SVG object
                    // TODO maybe pass in the width/height
                    $scope.svg = d3.select(canvas[0]).append("svg").attr("width", w).attr("height", h);

                    // The we add the markers for the arrow tips
                    var linkTypes = null;
                    if ($scope.graph) {
                        linkTypes = $scope.graph.linktypes;
                    }
                    if (!linkTypes) {
                        return;
                    }
                    $scope.svg.append("svg:defs").selectAll("marker").data(linkTypes).enter().append("svg:marker").attr("id", String).attr("viewBox", "0 -5 10 10").attr("refX", 15).attr("refY", -1.5).attr("markerWidth", 6).attr("markerHeight", 6).attr("orient", "auto").append("svg:path").attr("d", "M0,-5L10,0L0,5");

                    // The bounding box can't be zoomed or scaled at all
                    $scope.svg.append("svg:g").append("svg:rect").attr("class", "graphbox.frame").attr('width', w).attr('height', h);

                    $scope.viewport = $scope.svg.append("svg:g").call(d3.behavior.zoom().on("zoom", $scope.redraw)).append("svg:g");

                    $scope.viewport.append("svg:rect").attr("width", 1000000).attr("height", 1000000).attr("class", "graphbox").attr("transform", "translate(-50000, -500000)");

                    // Only do this if we have a graph object
                    if ($scope.graph) {
                        var ownerScope = $scope.$parent || $scope;
                        var selectedModel = $scope.selectedModel || "selectedNode";

                        // kick off the d3 forced graph layout
                        $scope.force = d3.layout.force().nodes($scope.graph.nodes).links($scope.graph.links).size([w, h]).on("tick", $scope.tick);

                        if (angular.isDefined($scope.linkDistance)) {
                            $scope.force.linkDistance($scope.linkDistance);
                        }
                        if (angular.isDefined($scope.charge)) {
                            $scope.force.charge($scope.charge);
                        }
                        var markerTypeName = $scope.markerKind || "marker-end";

                        // Add all edges to the viewport
                        $scope.graphEdges = $scope.viewport.append("svg:g").selectAll("path").data($scope.force.links()).enter().append("svg:path").attr("class", function (d) {
                            return "link " + d.type;
                        }).attr(markerTypeName, function (d) {
                            return "url(#" + d.type + ")";
                        });

                        // add all nodes to the viewport
                        $scope.graphNodes = $scope.viewport.append("svg:g").selectAll("circle").data($scope.force.nodes()).enter().append("a").attr("xlink:href", function (d) {
                            return d.navUrl;
                        }).on("mouseover.onLink", function (d, i) {
                            var sel = d3.select(d3.event.target);
                            sel.classed('selected', true);
                            ownerScope[selectedModel] = d;
                            Core.pathSet(ownerScope, selectedModel, d);
                            Core.$apply(ownerScope);
                        }).on("mouseout.onLink", function (d, i) {
                            var sel = d3.select(d3.event.target);
                            sel.classed('selected', false);
                        });

                        function hasImage(d) {
                            return d.image && d.image.url;
                        }

                        // Add the images if they are set
                        $scope.graphNodes.filter(function (d) {
                            return d.image != null;
                        }).append("image").attr("xlink:href", function (d) {
                            return d.image.url;
                        }).attr("x", function (d) {
                            return -(d.image.width / 2);
                        }).attr("y", function (d) {
                            return -(d.image.height / 2);
                        }).attr("width", function (d) {
                            return d.image.width;
                        }).attr("height", function (d) {
                            return d.image.height;
                        });

                        // if we don't have an image add a circle
                        $scope.graphNodes.filter(function (d) {
                            return !hasImage(d);
                        }).append("circle").attr("class", function (d) {
                            return d.type;
                        }).attr("r", function (d) {
                            return d.size || $scope.nodesize;
                        });

                        // Add the labels to the viewport
                        $scope.graphLabels = $scope.viewport.append("svg:g").selectAll("g").data($scope.force.nodes()).enter().append("svg:g");

                        // A copy of the text with a thick white stroke for legibility.
                        $scope.graphLabels.append("svg:text").attr("x", 8).attr("y", ".31em").attr("class", "shadow").text(function (d) {
                            return d.name;
                        });

                        $scope.graphLabels.append("svg:text").attr("x", 8).attr("y", ".31em").text(function (d) {
                            return d.name;
                        });

                        // animate, then stop
                        $scope.force.start();

                        $scope.graphNodes.call($scope.force.drag).on("mouseover", $scope.mover).on("mouseout", $scope.mout);
                    }
                };
            };
        }
        return ForceGraphDirective;
    })();
    ForceGraph.ForceGraphDirective = ForceGraphDirective;
    ;
})(ForceGraph || (ForceGraph = {}));
/**
* @module Health
*/
var Health;
(function (_Health) {
    function HealthController($scope, jolokia, workspace, $templateCache) {
        $scope.levelSorting = {
            'ERROR': 0,
            'WARNING': 1,
            'INFO': 2
        };

        $scope.colorMaps = {
            'ERROR': {
                'Health': '#ff0a47',
                'Remaining': '#e92614'
            },
            'WARNING': {
                'Health': '#33cc00',
                'Remaining': '#f7ee09'
            },
            'INFO': {
                'Health': '#33cc00',
                'Remaining': '#00cc33'
            }
        };

        $scope.results = [];
        $scope.responses = {};
        $scope.mbeans = [];
        $scope.mbeanStatus = {};
        $scope.displays = [];
        $scope.page = '';

        $scope.pageFilter = '';

        $scope.$watch('mbeans', function (newValue, oldValue) {
            Core.unregister(jolokia, $scope);
            if (!newValue) {
                return;
            }
            $scope.mbeanStatus = {};
            newValue.forEach(function (mbean) {
                var unregFunc = Core.register(jolokia, $scope, {
                    type: 'exec', mbean: mbean,
                    operation: "healthList()"
                }, {
                    success: $scope.render,
                    error: function (response) {
                        _Health.log.info("Failed to invoke healthList() on mbean: " + mbean + " due to: ", response.error);
                        _Health.log.debug("Stack trace: ", response.stacktrace.split("\n"));
                        unregFunc();
                    }
                });

                var error = function (response) {
                    if (!response.error.has("AttributeNotFoundException")) {
                        _Health.log.info("Failed to read CurrentStatus on mbean: " + mbean + " due to: ", response.error);
                        _Health.log.debug("Stack trace: ", response.stacktrace.split("\n"));
                    }
                };

                //see if the mbean has a CurrentStatus attribute and keep an eye on it if so
                jolokia.request({
                    type: 'read', mbean: mbean, attribute: 'CurrentStatus'
                }, {
                    success: function (response) {
                        $scope.mbeanStatus[response.request['mbean']] = response.value;
                        Core.register(jolokia, $scope, {
                            type: 'read', mbean: mbean, attribute: 'CurrentStatus'
                        }, {
                            success: function (response) {
                                /*
                                log.debug("response for CurrentStatus",
                                response.request['mbean'],
                                ": ",
                                response.value);
                                */
                                if (response.value === $scope.mbeanStatus[response.request['mbean']]) {
                                    return;
                                }
                                $scope.mbeanStatus[response.request['mbean']] = response.value;
                                Core.$apply($scope);
                            },
                            error: error
                        });
                    },
                    error: error
                });
            });
        }, true);

        $scope.getTitleClass = function (display) {
            if (!display) {
                return "warning";
            }
            if (!display.values || display.values.length === 0) {
                return "ok";
            }
            var answer = "ok";
            display.values.forEach(function (value) {
                if (answer !== "warning" && value.level && value.level.toLowerCase() !== 'info') {
                    answer = "warning";
                }
            });

            return answer;
        };

        $scope.getHumanName = function (name) {
            if (name.startsWith("org.apache.activemq")) {
                var answer = name;
                var nameParts = name.split(',');
                nameParts.forEach(function (part) {
                    if (part.startsWith('brokerName')) {
                        var parts = part.split('=');
                        if (parts[1]) {
                            answer = "Broker: " + parts[1];
                        }
                    }
                });
                return answer;
            }
            if (name.startsWith("io.fabric8:service")) {
                return "Fabric8";
            }

            return name;
        };

        $scope.getMBeans = function () {
            var healthMap = _Health.getHealthMBeans(workspace);
            _Health.log.debug("HealthMap: ", healthMap);
            if (healthMap) {
                if (!angular.isArray(healthMap)) {
                    return [healthMap.objectName];
                }
                var answer = healthMap.map(function (obj) {
                    return obj.objectName;
                });
                _Health.log.debug("Health mbeans: ", answer);
                return answer;
            } else {
                _Health.log.debug("No health mbeans found...");
                return [];
            }
        };

        $scope.$on('jmxTreeUpdated', function () {
            $scope.mbeans = $scope.getMBeans();
        });

        $scope.$on('$routeChangeSuccess', function () {
            $scope.mbeans = $scope.getMBeans();
        });

        $scope.mbeans = $scope.getMBeans();

        $scope.render = function (response) {
            /*
            log.debug("response for ",
            response.request['mbean'],
            ".",
            response.request['operation'],
            ": ",
            response.value);
            */
            var mbean = response.request['mbean'];
            var values = response.value;

            var responseJson = angular.toJson(values);

            if (mbean in $scope.responses) {
                if ($scope.responses[mbean] === responseJson) {
                    return;
                }
            }

            $scope.responses[mbean] = responseJson;

            var display = $scope.displays.find(function (m) {
                return m.mbean === mbean;
            });

            values = defaultValues(values);

            values = values.sortBy(function (value) {
                if (!value.level) {
                    return 99;
                }
                return $scope.levelSorting[value.level];
            });

            values.forEach(function (value) {
                var healthPercentCurrent = 0;
                var healthPercentRemaining = 1;

                if ('healthPercent' in value) {
                    var healthPercent = value['healthPercent'];
                    healthPercentCurrent = healthPercent.round(3);
                    healthPercentRemaining = 1 - healthPercentCurrent;
                    healthPercentRemaining = healthPercentRemaining.round(3);
                }

                value.data = {
                    total: 1,
                    terms: [
                        {
                            term: 'Health',
                            count: healthPercentCurrent
                        }, {
                            term: 'Remaining',
                            count: healthPercentRemaining
                        }]
                };
                value.colorMap = $scope.colorMaps[value.level];
            });

            if (!display) {
                $scope.displays.push({
                    mbean: mbean,
                    values: values
                });
            } else {
                display.values = values;
            }

            //log.debug("Display: ", $scope.displays);
            if ($scope.page === '') {
                $scope.page = $templateCache.get('pageTemplate');
            }

            Core.$apply($scope);
        };

        $scope.filterValues = function (value) {
            var json = angular.toJson(value);
            return json.has($scope.pageFilter);
        };

        $scope.sanitize = function (value) {
            var answer = {};
            Object.extended(value).keys().forEach(function (key) {
                if ($scope.showKey(key) && value[key]) {
                    answer[key] = value[key];
                }
            });
            return answer;
        };

        $scope.showKey = function (key) {
            if (key === "colorMap" || key === "data") {
                return false;
            }
            return true;
        };

        $scope.getTitle = function (value) {
            if (value['healthId'].endsWith('profileHealth')) {
                return 'Profile: <strong>' + value['profile'] + '</strong>';
            }
            return 'HealthID: <strong>' + value['healthId'] + '</strong>';
        };

        $scope.isPercentage = function (key) {
            if (key !== undefined && key.toUpperCase().indexOf("PERCENT") > 0) {
                return true;
            }
            return false;
        };

        /*
        * Default the values that are missing in the returned JSON
        */
        function defaultValues(values) {
            angular.forEach(values, function (aData) {
                var domain = aData["domain"];
                if (!domain) {
                    var id = aData["healthId"];
                    if (id) {
                        var idx = id.lastIndexOf('.');
                        if (idx > 0) {
                            domain = id.substring(0, idx);
                            var alias = Health.healthDomains[domain];
                            if (alias) {
                                domain = alias;
                            }
                            var kind = aData["kind"];
                            if (!kind) {
                                kind = humanizeValue(id.substring(idx + 1));
                                aData["kind"] = kind;
                            }
                        }
                    }
                    aData["domain"] = domain;
                }
            });
            return values;
        }

        function createOKStatus(object) {
            return {
                healthId: object.domain + ".status",
                level: "INFO",
                message: object.title + " is OK"
            };
        }
    }
    _Health.HealthController = HealthController;
})(Health || (Health = {}));
/**
* @module Health
*/
var Health;
(function (Health) {
    Health.log = Logger.get("Health");

    Health.healthDomains = {
        "org.apache.activemq": "ActiveMQ",
        "org.apache.camel": "Camel",
        "io.fabric8": "Fabric8"
    };

    function hasHealthMBeans(workspace) {
        var beans = getHealthMBeans(workspace);
        if (beans) {
            if (angular.isArray(beans))
                return beans.length >= 1;
            return true;
        }
        return false;
    }
    Health.hasHealthMBeans = hasHealthMBeans;

    /**
    * Returns the health MBeans
    * @method getHealthMBeans
    * @for Health
    * @param {Workspace} workspace
    * @return {String}
    */
    function getHealthMBeans(workspace) {
        if (workspace) {
            var healthMap = workspace.mbeanServicesToDomain["Health"] || {};
            var selection = workspace.selection;
            if (selection) {
                var domain = selection.domain;
                if (domain) {
                    var mbean = healthMap[domain];
                    if (mbean) {
                        return mbean;
                    }
                }
            }
            if (healthMap) {
                // lets append all the mbeans together from all the domains
                var answer = [];
                angular.forEach(healthMap, function (value) {
                    if (angular.isArray(value)) {
                        answer = answer.concat(value);
                    } else {
                        answer.push(value);
                    }
                });
                return answer;
            } else
                return null;
        }
    }
    Health.getHealthMBeans = getHealthMBeans;
})(Health || (Health = {}));
/**
* @module Health
* @main Health
*/
var Health;
(function (Health) {
    var pluginName = 'health';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/health', { templateUrl: 'app/health/html/health.html' });
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry['health'] = layoutFull;

        helpRegistry.addUserDoc('health', 'app/health/doc/help.md', function () {
            return Health.hasHealthMBeans(workspace);
        });

        workspace.topLevelTabs.push({
            id: "health",
            content: "Health",
            title: "View the health of the various sub systems",
            isValid: function (workspace) {
                return Health.hasHealthMBeans(workspace);
            },
            href: function () {
                return "#/health";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("health");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Health || (Health = {}));
/**
* @module Quartz
*/
var Quartz;
(function (Quartz) {
    Quartz.log = Logger.get("Quartz");

    function iconClass(state) {
        if (state) {
            switch (state.toString().toLowerCase()) {
                case 'true':
                    return "green icon-play-circle";
                case 'normal':
                    return "green icon-play-circle";
                case 'paused':
                    return "orange icon-off";
            }
        }
        return "orange icon-off";
    }
    Quartz.iconClass = iconClass;

    function misfireText(val) {
        if (val) {
            switch (val) {
                case -1:
                    return "ignore";
                case 0:
                    return "smart";
                case 1:
                    return "fire once now";
                case 2:
                    return "do nothing";
            }
        }
        return "unknown";
    }
    Quartz.misfireText = misfireText;

    function jobDataClassText(text) {
        // add info sign icon to the text so the end user notice there is some more details, by clicking the cell.
        return "<i class='icon-info-sign'> " + text + "</i>";
    }
    Quartz.jobDataClassText = jobDataClassText;

    /**
    * Returns true if the state of the item begins with the given state - or one of the given states
    * @method
    * @param item the item which has a State
    * @param state a value or an array of states
    */
    function isState(item, state) {
        var value = item.Started;
        if (angular.isArray(state)) {
            return state.any(function (stateText) {
                return value.startsWith(stateText);
            });
        } else {
            return value.startsWith(state);
        }
    }
    Quartz.isState = isState;

    /**
    * Returns true if the Quartz plugin is enabled
    */
    function isQuartzPluginEnabled(workspace) {
        return getQuartzMBean(workspace);
    }
    Quartz.isQuartzPluginEnabled = isQuartzPluginEnabled;

    function getQuartzMBean(workspace) {
        return Core.getMBeanTypeObjectName(workspace, "quartz", "QuartzScheduler");
    }
    Quartz.getQuartzMBean = getQuartzMBean;

    function isScheduler(workspace) {
        return workspace.hasDomainAndProperties('quartz', { type: 'QuartzScheduler' });
    }
    Quartz.isScheduler = isScheduler;

    function getSelectedSchedulerName(workspace) {
        var selection = workspace.selection;
        if (selection && selection.domain === Quartz.jmxDomain) {
            // lets get the cache name
            return selection.entries["name"];
        }
        return null;
    }
    Quartz.getSelectedSchedulerName = getSelectedSchedulerName;
})(Quartz || (Quartz = {}));
/**
* @module Quartz
*/
var Quartz;
(function (Quartz) {
    function QuartzController($scope, $location, workspace, jolokia) {
        var log = Logger.get("Quartz");

        var stateTemplate = '<div class="ngCellText pagination-centered" title="{{row.entity.state}}"><i class="{{row.entity.state | quartzIconClass}}"></i></div>';
        var misfireTemplate = '<div class="ngCellText" title="{{row.entity.misfireInstruction}}">{{row.entity.misfireInstruction | quartzMisfire}}</div>';

        $scope.valueDetails = new UI.Dialog();

        $scope.selectedScheduler = null;
        $scope.selectedSchedulerMBean = null;
        $scope.triggers = [];
        $scope.jobs = [];

        $scope.misfireInstructions = [
            { id: '-1', title: 'Ignore' },
            { id: '0', title: 'Smart' },
            { id: '1', title: 'Fire once now' },
            { id: '2', title: 'Do nothing' }
        ];
        $scope.updatedTrigger = {};
        $scope.triggerSchema = {
            properties: {
                'cron': {
                    description: 'Cron expression',
                    label: 'Cron expression',
                    tooltip: 'Specify a cron expression for the trigger',
                    type: 'string',
                    hidden: false
                },
                'repeatCount': {
                    description: 'Repeat count',
                    tooltip: 'Number of times to repeat. Use -1 for forever.',
                    type: 'integer',
                    hidden: false
                },
                'repeatInterval': {
                    description: 'Repeat interval',
                    tooltip: 'Elapsed time in millis between triggering',
                    type: 'integer',
                    hidden: false
                },
                'misfireInstruction': {
                    description: 'Misfire instruction',
                    tooltip: 'What to do when misfiring happens',
                    type: 'string',
                    hidden: false,
                    'input-element': 'select',
                    'input-attributes': {
                        'ng-options': "mi.id as mi.title for mi in misfireInstructions"
                    }
                }
            }
        };

        $scope.gridOptions = {
            selectedItems: [],
            data: 'triggers',
            showFilter: true,
            filterOptions: {
                filterText: ''
            },
            showSelectionCheckbox: false,
            enableRowClickSelection: true,
            multiSelect: false,
            primaryKeyFn: function (entity, idx) {
                return entity.group + "/" + entity.name;
            },
            columnDefs: [
                {
                    field: 'state',
                    displayName: 'State',
                    cellTemplate: stateTemplate,
                    width: 56,
                    minWidth: 56,
                    maxWidth: 56,
                    resizable: false
                },
                {
                    field: 'group',
                    displayName: 'Group',
                    resizable: true,
                    width: 150
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    resizable: true,
                    width: 150
                },
                {
                    field: 'type',
                    displayName: 'Type',
                    resizable: false,
                    width: 70
                },
                {
                    field: 'expression',
                    displayName: 'Expression',
                    resizable: true,
                    width: 180
                },
                {
                    field: 'misfireInstruction',
                    displayName: 'Misfire Instruction',
                    cellTemplate: misfireTemplate,
                    width: 150
                },
                {
                    field: 'previousFireTime',
                    displayName: 'Previous Fire'
                },
                {
                    field: 'nextFireTime',
                    displayName: 'Next Fire'
                },
                {
                    field: 'finalFireTime',
                    displayName: 'Final Fire'
                }
            ]
        };

        $scope.jobsGridOptions = {
            selectedItems: [],
            data: 'jobs',
            showFilter: true,
            filterOptions: {
                filterText: ''
            },
            showSelectionCheckbox: false,
            enableRowClickSelection: true,
            multiSelect: false,
            primaryKeyFn: function (entity, idx) {
                return entity.group + "/" + entity.name;
            },
            columnDefs: [
                {
                    field: 'group',
                    displayName: 'Group',
                    resizable: true,
                    width: 150
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    resizable: true,
                    width: 150
                },
                {
                    field: 'durability',
                    displayName: 'Durable',
                    width: 70,
                    resizable: false
                },
                {
                    field: 'shouldRecover',
                    displayName: 'Recover',
                    width: 70,
                    resizable: false
                },
                {
                    field: 'jobClass',
                    displayName: 'Job ClassName',
                    width: 350
                },
                {
                    field: 'description',
                    displayName: 'Description',
                    resizable: true
                }
            ]
        };

        $scope.openJobDetailView = function () {
            if ($scope.jobsGridOptions.selectedItems.length === 1) {
                $scope.valueDetails.open();
            }
        };

        $scope.renderIcon = function (state) {
            return Quartz.iconClass(state);
        };

        $scope.renderQuartz = function (response) {
            $scope.selectedSchedulerDetails = [];

            log.debug("Selected scheduler mbean " + $scope.selectedScheduler);
            var obj = response.value;
            if (obj) {
                // redraw table
                $scope.selectedScheduler = obj;
                $scope.triggers = [];
                $scope.job = [];

                // grab state for all triggers which requires to call a JMX operation per trigger
                obj.AllTriggers.forEach(function (t) {
                    var state = jolokia.request({
                        type: "exec", mbean: $scope.selectedSchedulerMBean,
                        operation: "getTriggerState", arguments: [t.name, t.group] });
                    if (state) {
                        t.state = state.value;
                    } else {
                        t.state = "unknown";
                    }

                    // unique id of trigger
                    t.id = t.name + "/" + t.group;

                    // grab information about the trigger from the job map, as quartz does not have the information itself
                    // so we had to enrich the job map in camel-quartz to include this information
                    var job = obj.AllJobDetails[t.jobName];
                    if (job) {
                        job = job[t.group];
                        if (job) {
                            var repeatCounter;
                            var repeatInterval;

                            t.type = job.jobDataMap["CamelQuartzTriggerType"];
                            if (t.type && t.type == "cron") {
                                t.expression = job.jobDataMap["CamelQuartzTriggerCronExpression"];
                            } else if (t.type && t.type == "simple") {
                                t.expression = "every " + job.jobDataMap["CamelQuartzTriggerSimpleRepeatInterval"] + " ms.";
                                repeatCounter = job.jobDataMap["CamelQuartzTriggerSimpleRepeatCounter"];
                                repeatInterval = job.jobDataMap["CamelQuartzTriggerSimpleRepeatInterval"];
                                if (repeatCounter > 0) {
                                    t.expression += " (" + repeatCounter + " times)";
                                } else {
                                    t.expression += " (forever)";
                                }
                                t.repeatCounter = repeatCounter;
                                t.repeatInterval = repeatInterval;
                            } else {
                                // fallback and grab from Camel endpoint if that is possible (supporting older Camel releases)
                                var uri = job.jobDataMap["CamelQuartzEndpoint"];
                                if (uri) {
                                    var cron = Core.getQueryParameterValue(uri, "cron");
                                    if (cron) {
                                        t.type = "cron";

                                        // replace + with space as Camel uses + as space in the cron when specifying in the uri
                                        cron = cron.replace(/\++/g, ' ');
                                        t.expression = cron;
                                    }
                                    repeatCounter = Core.getQueryParameterValue(uri, "trigger.repeatCount");
                                    repeatInterval = Core.getQueryParameterValue(uri, "trigger.repeatInterval");
                                    if (repeatCounter || repeatInterval) {
                                        t.type = "simple";
                                        t.expression = "every " + repeatInterval + " ms.";
                                        if (repeatCounter && repeatCounter > 0) {
                                            t.expression += " (" + repeatCounter + " times)";
                                        } else {
                                            t.expression += " (forever)";
                                        }
                                        t.repeatCounter = repeatCounter;
                                        t.repeatInterval = repeatInterval;
                                    }
                                }
                            }
                        }
                    }

                    $scope.triggers.push(t);
                });

                // grab state for all triggers which requires to call a JMX operation per trigger
                $scope.jobs = [];
                $scope.triggers.forEach(function (t) {
                    var job = obj.AllJobDetails[t.jobName];
                    if (job) {
                        job = job[t.group];
                        if (job) {
                            generateJobDataMapDetails(job);

                            // unique id of jobs
                            job.id = job.jobName + "/" + job.group;

                            $scope.jobs.push(job);
                        }
                    }
                });
            }

            Core.$apply($scope);
        };

        function generateJobDataMapDetails(data) {
            var value = data.jobDataMap;
            if (!angular.isArray(value) && angular.isObject(value)) {
                var detailHtml = "<table class='table table-striped'>";
                detailHtml += "<thead><th>Key</th><th>Value</th></thead>";
                var object = value;
                var keys = Object.keys(value).sort();
                angular.forEach(keys, function (key) {
                    var value = object[key];
                    detailHtml += "<tr><td>" + safeNull(key) + "</td><td>" + safeNull(value) + "</td></tr>";
                });
                detailHtml += "</table>";
                data.detailHtml = detailHtml;
            }
        }

        $scope.pauseScheduler = function () {
            if ($scope.selectedSchedulerMBean) {
                jolokia.request({
                    type: "exec", mbean: $scope.selectedSchedulerMBean,
                    operation: "standby" }, onSuccess(function (response) {
                    notification("success", "Paused scheduler " + $scope.selectedScheduler.SchedulerName);
                }));
            }
        };

        $scope.startScheduler = function () {
            if ($scope.selectedSchedulerMBean) {
                jolokia.request({
                    type: "exec", mbean: $scope.selectedSchedulerMBean,
                    operation: "start" }, onSuccess(function (response) {
                    notification("success", "Started scheduler " + $scope.selectedScheduler.SchedulerName);
                }));
            }
        };

        $scope.enableSampleStatistics = function () {
            if ($scope.selectedSchedulerMBean) {
                jolokia.setAttribute($scope.selectedSchedulerMBean, "SampledStatisticsEnabled", true);
            }
        };

        $scope.disableSampleStatistics = function () {
            if ($scope.selectedSchedulerMBean) {
                jolokia.setAttribute($scope.selectedSchedulerMBean, "SampledStatisticsEnabled", false);
            }
        };

        $scope.pauseTrigger = function () {
            if ($scope.gridOptions.selectedItems.length === 1) {
                var groupName = $scope.gridOptions.selectedItems[0].group;
                var triggerName = $scope.gridOptions.selectedItems[0].name;

                jolokia.request({
                    type: "exec", mbean: $scope.selectedSchedulerMBean,
                    operation: "pauseTrigger", arguments: [triggerName, groupName] }, onSuccess(function (response) {
                    notification("success", "Paused trigger " + groupName + "/" + triggerName);
                }));
            }
        };

        $scope.resumeTrigger = function () {
            if ($scope.gridOptions.selectedItems.length === 1) {
                var groupName = $scope.gridOptions.selectedItems[0].group;
                var triggerName = $scope.gridOptions.selectedItems[0].name;

                jolokia.request({
                    type: "exec", mbean: $scope.selectedSchedulerMBean,
                    operation: "resumeTrigger", arguments: [triggerName, groupName] }, onSuccess(function (response) {
                    notification("success", "Resumed trigger " + groupName + "/" + triggerName);
                }));
            }
        };

        $scope.onBeforeUpdateTrigger = function () {
            var row = $scope.gridOptions.selectedItems[0];
            if (row && row.type === 'cron') {
                $scope.updatedTrigger["type"] = 'cron';
                $scope.updatedTrigger["cron"] = row.expression;
                $scope.updatedTrigger["repeatCount"] = null;
                $scope.updatedTrigger["repeatInterval"] = null;

                // must be a string type for the select-box to select it
                $scope.updatedTrigger["misfireInstruction"] = '' + row.misfireInstruction;
                $scope.triggerSchema.properties["cron"].hidden = false;
                $scope.triggerSchema.properties["repeatCount"].hidden = true;
                $scope.triggerSchema.properties["repeatInterval"].hidden = true;
                $scope.showTriggerDialog = true;
            } else if (row && row.type === 'simple') {
                $scope.updatedTrigger["type"] = 'simple';
                $scope.updatedTrigger["cron"] = null;
                $scope.updatedTrigger["repeatCount"] = row.repeatCounter;
                $scope.updatedTrigger["repeatInterval"] = row.repeatInterval;

                // must be a string type for the select-box to select it
                $scope.updatedTrigger["misfireInstruction"] = '' + row.misfireInstruction;
                $scope.triggerSchema.properties["cron"].hidden = true;
                $scope.triggerSchema.properties["repeatCount"].hidden = false;
                $scope.triggerSchema.properties["repeatInterval"].hidden = false;
                $scope.showTriggerDialog = true;
            } else {
                $scope.updatedTrigger = {};
                $scope.showTriggerDialog = false;
            }
        };

        $scope.onUpdateTrigger = function () {
            var cron = $scope.updatedTrigger["cron"];
            var repeatCounter = $scope.updatedTrigger["repeatCount"];
            var repeatInterval = $scope.updatedTrigger["repeatInterval"];
            var misfireInstruction = parseInt($scope.updatedTrigger["misfireInstruction"]);
            $scope.updatedTrigger = {};

            var groupName = $scope.gridOptions.selectedItems[0].group;
            var triggerName = $scope.gridOptions.selectedItems[0].name;

            if (cron) {
                log.info("Updating trigger " + groupName + "/" + triggerName + " with cron " + cron);

                jolokia.request({
                    type: "exec", mbean: "hawtio:type=QuartzFacade",
                    operation: "updateCronTrigger", arguments: [
                        $scope.selectedSchedulerMBean,
                        triggerName,
                        groupName,
                        misfireInstruction,
                        cron,
                        null] }, onSuccess(function (response) {
                    notification("success", "Updated trigger " + groupName + "/" + triggerName);
                }));
            } else if (repeatCounter || repeatInterval) {
                if (repeatCounter == null) {
                    repeatCounter = -1;
                }
                if (repeatInterval == null) {
                    repeatInterval = 1000;
                }

                log.info("Updating trigger " + groupName + "/" + triggerName + " with interval " + repeatInterval + " ms. for " + repeatCounter + " times");

                jolokia.request({
                    type: "exec", mbean: "hawtio:type=QuartzFacade",
                    operation: "updateSimpleTrigger", arguments: [
                        $scope.selectedSchedulerMBean,
                        triggerName,
                        groupName,
                        misfireInstruction,
                        repeatCounter,
                        repeatInterval] }, onSuccess(function (response) {
                    notification("success", "Updated trigger " + groupName + "/" + triggerName);
                }));
            }
        };

        function reloadTree() {
            log.debug("Reloading Quartz Tree");
            var mbean = Quartz.getQuartzMBean(workspace);
            var domain = "quartz";
            var rootFolder = new Folder("Quartz Schedulers");
            rootFolder.addClass = "quartz-folder";
            rootFolder.typeName = "quartzSchedulers";
            rootFolder.domain = domain;
            rootFolder.key = "";
            var children = [rootFolder];

            if (mbean) {
                function render(results) {
                    angular.forEach(results, function (value, key) {
                        var name = jolokia.request({
                            type: "read", mbean: value,
                            attribute: ["SchedulerName"] });

                        var txt = name.value["SchedulerName"];
                        var scheduler = new Folder(txt);
                        scheduler.addClass = "quartz-scheduler";
                        scheduler.typeName = "quartzScheduler";
                        scheduler.domain = domain;
                        scheduler.objectName = value;

                        // use scheduler name as key as that is unique for us
                        scheduler.key = txt;
                        rootFolder.children.push(scheduler);
                    });

                    log.debug("Setitng up Quartz tree with nid " + $location.search()["nid"]);
                    var nid = $location.search()["nid"];
                    if (nid) {
                        var data = rootFolder.children.filter(function (folder) {
                            return folder.key === nid;
                        });
                        log.debug("Found nid in tree " + data);
                        if (data && data.length === 1) {
                            selectionChanged(data[0]);
                        }
                    }

                    Core.$apply($scope);

                    var treeElement = $("#quartztree");
                    Jmx.enableTree($scope, $location, workspace, treeElement, children, true, function (selectedNode) {
                        var data = selectedNode.data;
                        selectionChanged(data);
                        Core.$apply($scope);
                    });

                    // lets do this asynchronously to avoid Error: $digest already in progress
                    setTimeout(updateSelectionFromURL, 50);
                }

                jolokia.search("quartz:type=QuartzScheduler,*", onSuccess(render));
            }
        }

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURLAndAutoSelect($location, $("#quartztree"), function (first) {
                // use function to auto select first scheduler if there is only one scheduler
                var schedulers = first.getChildren();
                if (schedulers && schedulers.length === 1) {
                    first = schedulers[0];
                    return first;
                } else {
                    return first;
                }
            }, true);
        }

        function selectionChanged(data) {
            var selectionKey = data ? data.objectName : null;
            log.debug("Selection is now: " + selectionKey);

            if (selectionKey) {
                // if we selected a scheduler then register a callback to get its trigger data updated in-real-time
                // as the trigger has prev/next fire times that changes
                $scope.selectedSchedulerMBean = selectionKey;

                // TODO: is there a better way to add our nid to the uri parameter?
                $location.search({ nid: data.key });

                var request = [
                    { type: "read", mbean: $scope.selectedSchedulerMBean }
                ];

                // unregister before registering new
                Core.unregister(jolokia, $scope);
                Core.register(jolokia, $scope, request, onSuccess($scope.renderQuartz));
            } else {
                Core.unregister(jolokia, $scope);
                $scope.selectedSchedulerMBean = null;
                $scope.selectedScheduler = null;
                $scope.triggers = [];
                $scope.jobs = [];
                $scope.updatedTrigger = {};
            }
        }

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        $scope.$on('jmxTreeUpdated', function () {
            reloadTree();
        });

        // reload tree on startup
        reloadTree();
    }
    Quartz.QuartzController = QuartzController;
})(Quartz || (Quartz = {}));
/**
* @module Quartz
* @main Quartz
*/
var Quartz;
(function (Quartz) {
    var pluginName = 'quartz';
    Quartz.jmxDomain = 'quartz';

    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/quartz/scheduler', { templateUrl: 'app/quartz/html/scheduler.html' }).when('/quartz/triggers', { templateUrl: 'app/quartz/html/triggers.html' }).when('/quartz/jobs', { templateUrl: 'app/quartz/html/jobs.html' });
    }).filter('quartzIconClass', function () {
        return Quartz.iconClass;
    }).filter('quartzMisfire', function () {
        return Quartz.misfireText;
    }).filter('quartzJobDataClassText', function () {
        return Quartz.jobDataClassText;
    }).run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry['quartz'] = 'app/quartz/html/layoutQuartzTree.html';
        helpRegistry.addUserDoc('quartz', 'app/quartz/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties(Quartz.jmxDomain);
        });

        workspace.topLevelTabs.push({
            id: "quartz",
            content: "Quartz",
            title: "Quartz Scheduler",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties(Quartz.jmxDomain);
            },
            href: function () {
                return "#/quartz/scheduler";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("quartz");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Quartz || (Quartz = {}));
/**
*  @module Themes
*/
var Themes;
(function (Themes) {
    function PreferencesController($scope, localStorage, branding) {
        $scope.availableThemes = Themes.getAvailableThemes();
        $scope.availableBrandings = Themes.getAvailableBrandings();

        Core.initPreferenceScope($scope, localStorage, {
            'theme': {
                'value': Themes.currentTheme,
                'override': function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        Themes.setTheme(newValue, branding);
                    }
                }
            },
            'branding': {
                'value': Themes.currentBranding,
                'override': function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        Themes.setBranding(newValue, branding);
                    }
                }
            }
        });
    }
    Themes.PreferencesController = PreferencesController;
})(Themes || (Themes = {}));
/**
* @module Themes
* @main Themes
*/
var Themes;
(function (Themes) {
    Themes.defaultLoginBg = 'app/themes/img/default/hawtio-nologo.jpg';

    Themes.definitions = {
        'Default': {
            label: 'Default',
            file: 'app/themes/css/default.css',
            loginBg: Themes.defaultLoginBg
        },
        'Dark': {
            label: 'Dark',
            file: 'app/themes/css/dark.css',
            loginBg: Themes.defaultLoginBg
        },
        '3270': {
            label: 'Dark',
            file: 'app/themes/css/3270.css',
            loginBg: Themes.defaultLoginBg
        }
    };

    Themes.brandings = {
        'hawtio': {
            label: 'hawtio',
            setFunc: function (branding) {
                branding.appName = 'hawtio';
                branding.appLogo = 'img/hawtio_logo.svg';
                branding.css = 'css/site-branding.css';
                branding.logoOnly = true;
                branding.fullscreenLogin = false;
                branding.favicon = 'favicon.ico';
                return branding;
            }
        },
        'Example': {
            label: 'Example',
            setFunc: function (branding) {
                branding.appName = 'Example';
                branding.logoOnly = false;
                return branding;
            }
        }
    };

    Themes.currentTheme = 'Default';
    Themes.currentBranding = 'hawtio';

    function getAvailableThemes() {
        return Object.extended(Themes.definitions).keys();
    }
    Themes.getAvailableThemes = getAvailableThemes;

    function getAvailableBrandings() {
        return Object.extended(Themes.brandings).keys();
    }
    Themes.getAvailableBrandings = getAvailableBrandings;

    function getBranding(name) {
        var b = Themes.brandings[name];
        if (!b || !b['setFunc']) {
            b = Themes.brandings['hawtio'];
        }
        return b;
    }

    function setCSS(el, file) {
        var cssEL = $(el);
        cssEL.prop("disabled", true);
        cssEL.attr({ href: file });
        cssEL.prop("disabled", false);
    }

    function setFavicon(file) {
        $('#favicon').remove();
        $('head').append('<link id="favicon" rel="icon" type="image/ico" href="' + file + '">"');
    }

    function applyTheme(theme, branding) {
        if (!theme || !theme['file'] || !theme['label']) {
            Themes.log.info("invalid theme, setting theme to Default");
            setCSS("#theme", Themes.definitions['Default']['file']);
            branding.loginBg = Themes.definitions['Default']['loginBg'];
        } else {
            Themes.log.debug("Setting theme to ", theme['label']);
            setCSS("#theme", theme['file']);
            if (theme['loginBg']) {
                branding.loginBg = theme['loginBg'];
            }
        }
    }

    function setBranding(name, branding) {
        var b = getBranding(name);
        branding = b.setFunc(branding);
        Themes.log.debug("Set branding to: ", branding);
        if (branding.favicon) {
            setFavicon(branding.favicon);
        }
        if (branding.css) {
            setCSS("#branding", branding.css);
        }
        Themes.currentBranding = b['label'];
        localStorage['branding'] = Themes.currentBranding;
    }
    Themes.setBranding = setBranding;

    function setTheme(name, branding) {
        if (!(name in Themes.definitions)) {
            name = 'Default';
            Themes.log.info("unknown theme name, using default theme");
        }
        var theme = Core.pathGet(Themes.definitions, [name]);
        applyTheme(theme, branding);
        Themes.currentTheme = name;
        localStorage['theme'] = Themes.currentTheme;
    }
    Themes.setTheme = setTheme;

    Themes.pluginName = "themes";
    Themes.log = Logger.get("Themes");
    Themes._module = angular.module(Themes.pluginName, ["hawtioCore"]);

    Themes._module.run(function (localStorage, branding, preferencesRegistry) {
        var themeName = localStorage['theme'];
        Themes.setTheme(themeName, branding);

        var brandingName = localStorage['branding'];
        Themes.setBranding(brandingName, branding);

        preferencesRegistry.addTab("Theme", "app/themes/html/preferences.html");
        Themes.log.debug("Loaded");
    });

    hawtioPluginLoader.addModule(Themes.pluginName);
})(Themes || (Themes = {}));
/**
* @module DataTable
*/
var DataTable;
(function (DataTable) {
    var SimpleDataTable = (function () {
        function SimpleDataTable($compile) {
            var _this = this;
            this.$compile = $compile;
            this.restrict = 'A';
            this.scope = {
                config: '=hawtioSimpleTable',
                target: '@',
                showFiles: '@'
            };
            // necessary to ensure 'this' is this object <sigh>
            this.link = function ($scope, $element, $attrs) {
                return _this.doLink($scope, $element, $attrs);
            };
        }
        SimpleDataTable.prototype.doLink = function ($scope, $element, $attrs) {
            var defaultPrimaryKeyFn = function (entity, idx) {
                // default function to use id/_id/name as primary key, and fallback to use index
                return entity["id"] || entity["_id"] || entity["name"] || idx;
            };

            var config = $scope.config;

            var dataName = config.data || "data";

            // need to remember which rows has been selected as the config.data / config.selectedItems
            // so we can re-select them when data is changed/updated, and entity may be new instances
            // so we need a primary key function to generate a 'primary key' of the entity
            var primaryKeyFn = config.primaryKeyFn || defaultPrimaryKeyFn;
            $scope.rows = [];

            var scope = $scope.$parent || $scope;

            var listener = function (otherValue) {
                var value = Core.pathGet(scope, dataName);
                if (value && !angular.isArray(value)) {
                    value = [value];
                    Core.pathSet(scope, dataName, value);
                }

                if (!('sortInfo' in config)) {
                    // an optional defaultSort can be used to indicate a column
                    // should not automatic be the default sort
                    var ds = config.columnDefs.first()['defaultSort'];
                    var sortField;
                    if (angular.isUndefined(ds) || ds === true) {
                        sortField = config.columnDefs.first()['field'];
                    } else {
                        sortField = config.columnDefs.slice(1).first()['field'];
                    }
                    config['sortInfo'] = {
                        sortBy: sortField,
                        ascending: true
                    };
                }

                var sortInfo = $scope.config.sortInfo;

                // enrich the rows with information about their index
                var idx = -1;
                $scope.rows = (value || []).sortBy(sortInfo.sortBy, !sortInfo.ascending).map(function (entity) {
                    idx++;
                    return {
                        entity: entity,
                        index: idx,
                        getProperty: function (name) {
                            return entity[name];
                        }
                    };
                });

                Core.pathSet(scope, ['hawtioSimpleTable', dataName, 'rows'], $scope.rows);

                // okay the data was changed/updated so we need to re-select previously selected items
                // and for that we need to evaluate the primary key function so we can match new data with old data.
                var reSelectedItems = [];
                $scope.rows.forEach(function (row, idx) {
                    var rpk = primaryKeyFn(row.entity, row.index);
                    var selected = config.selectedItems.some(function (s) {
                        var spk = primaryKeyFn(s, s.index);
                        return angular.equals(rpk, spk);
                    });
                    if (selected) {
                        // need to enrich entity with index, as we push row.entity to the re-selected items
                        row.entity.index = row.index;
                        reSelectedItems.push(row.entity);
                        DataTable.log.debug("Data changed so keep selecting row at index " + row.index);
                    }
                });
                config.selectedItems = reSelectedItems;
            };

            scope.$watch(dataName, listener);

            // lets add a separate event so we can force updates
            // if we find cases where the delta logic doesn't work
            // (such as for nested hawtioinput-input-table)
            scope.$on("hawtio.datatable." + dataName, listener);

            function getSelectionArray() {
                var selectionArray = config.selectedItems;
                if (!selectionArray) {
                    selectionArray = [];
                    config.selectedItems = selectionArray;
                }
                if (angular.isString(selectionArray)) {
                    var name = selectionArray;
                    selectionArray = Core.pathGet(scope, name);
                    if (!selectionArray) {
                        selectionArray = [];
                        scope[name] = selectionArray;
                    }
                }
                return selectionArray;
            }

            function isMultiSelect() {
                var multiSelect = $scope.config.multiSelect;
                if (angular.isUndefined(multiSelect)) {
                    multiSelect = true;
                }
                return multiSelect;
            }

            $scope.toggleAllSelections = function () {
                var allRowsSelected = $scope.config.allRowsSelected;
                var newFlag = allRowsSelected;
                var selectionArray = getSelectionArray();
                selectionArray.splice(0, selectionArray.length);
                angular.forEach($scope.rows, function (row) {
                    row.selected = newFlag;
                    if (allRowsSelected) {
                        selectionArray.push(row.entity);
                    }
                });
            };

            $scope.toggleRowSelection = function (row) {
                if (row) {
                    var selectionArray = getSelectionArray();
                    if (!isMultiSelect()) {
                        // lets clear all other selections
                        selectionArray.splice(0, selectionArray.length);
                        angular.forEach($scope.rows, function (r) {
                            if (r !== row) {
                                r.selected = false;
                            }
                        });
                    }
                    var entity = row.entity;
                    if (entity) {
                        var idx = selectionArray.indexOf(entity);
                        if (row.selected) {
                            if (idx < 0) {
                                selectionArray.push(entity);
                            }
                        } else {
                            // clear the all selected checkbox
                            $scope.config.allRowsSelected = false;
                            if (idx >= 0) {
                                selectionArray.splice(idx, 1);
                            }
                        }
                    }
                }
            };

            $scope.sortBy = function (field) {
                if ($scope.config.sortInfo.sortBy === field) {
                    $scope.config.sortInfo.ascending = !$scope.config.sortInfo.ascending;
                } else {
                    $scope.config.sortInfo.sortBy = field;
                    $scope.config.sortInfo.ascending = true;
                }
                $scope.$emit("hawtio.datatable." + dataName);
            };

            $scope.getClass = function (field) {
                if ('sortInfo' in $scope.config) {
                    if ($scope.config.sortInfo.sortBy === field) {
                        if ($scope.config.sortInfo.ascending) {
                            return 'asc';
                        } else {
                            return 'desc';
                        }
                    }
                }

                return '';
            };

            $scope.showRow = function (row) {
                var filter = Core.pathGet($scope, ['config', 'filterOptions', 'filterText']);
                if (Core.isBlank(filter)) {
                    return true;
                }
                var rowJson = angular.toJson(row);
                return rowJson.toLowerCase().has(filter.toLowerCase());
            };

            $scope.isSelected = function (row) {
                return config.selectedItems.some(row.entity);
            };

            $scope.onRowSelected = function (row) {
                var idx = config.selectedItems.indexOf(row.entity);
                if (idx >= 0) {
                    DataTable.log.debug("De-selecting row at index " + row.index);
                    config.selectedItems.splice(idx, 1);
                } else {
                    if (!config.multiSelect) {
                        config.selectedItems = [];
                    }
                    DataTable.log.debug("Selecting row at index " + row.index);

                    // need to enrich entity with index, as we push row.entity to the selected items
                    row.entity.index = row.index;
                    config.selectedItems.push(row.entity);
                }
            };

            // lets add the header and row cells
            var rootElement = $element;
            rootElement.empty();

            var showCheckBox = firstValueDefined(config, ["showSelectionCheckbox", "displaySelectionCheckbox"], true);
            var enableRowClickSelection = firstValueDefined(config, ["enableRowClickSelection"], false);

            var onMouseDown;
            if (enableRowClickSelection) {
                onMouseDown = "ng-mousedown='onRowSelected(row)' ";
            } else {
                onMouseDown = "";
            }
            var headHtml = "<thead><tr>";

            // use a function to check if a row is selected so the UI can be kept up to date asap
            var bodyHtml = "<tbody><tr ng-repeat='row in rows track by $index' ng-show='showRow(row)' " + onMouseDown + "ng-class=\"{'selected': isSelected(row)}\" >";
            var idx = 0;
            if (showCheckBox) {
                var toggleAllHtml = isMultiSelect() ? "<input type='checkbox' ng-show='rows.length' ng-model='config.allRowsSelected' ng-change='toggleAllSelections()'>" : "";

                headHtml += "\n<th>" + toggleAllHtml + "</th>";
                bodyHtml += "\n<td><input type='checkbox' ng-model='row.selected' ng-change='toggleRowSelection(row)'></td>";
            }
            angular.forEach(config.columnDefs, function (colDef) {
                var field = colDef.field;
                var cellTemplate = colDef.cellTemplate || '<div class="ngCellText" title="{{row.entity.' + field + '}}">{{row.entity.' + field + '}}</div>';

                headHtml += "\n<th class='clickable no-fade table-header' ng-click=\"sortBy('" + field + "')\" ng-class=\"getClass('" + field + "')\">{{config.columnDefs[" + idx + "].displayName}}<span class='indicator'></span></th>";

                bodyHtml += "\n<td>" + cellTemplate + "</td>";
                idx += 1;
            });
            var html = headHtml + "\n</tr></thead>\n" + bodyHtml + "\n</tr></tbody>";

            var newContent = this.$compile(html)($scope);
            rootElement.html(newContent);
        };
        return SimpleDataTable;
    })();
    DataTable.SimpleDataTable = SimpleDataTable;

    /**
    * Returns the first property value defined in the given object or the default value if none are defined
    *
    * @param object the object to look for properties
    * @param names the array of property names to look for
    * @param defaultValue the value if no property values are defined
    * @return {*} the first defined property value or the defaultValue if none are defined
    */
    function firstValueDefined(object, names, defaultValue) {
        var answer = defaultValue;
        var found = false;
        angular.forEach(names, function (name) {
            var value = object[name];
            if (!found && angular.isDefined(value)) {
                answer = value;
                found = true;
            }
        });
        return answer;
    }
})(DataTable || (DataTable = {}));
/**
* @module DataTable
* @main DataTable
*/
var DataTable;
(function (DataTable) {
    var pluginName = 'datatable';
    DataTable.log = Logger.get("DataTable");

    angular.module(pluginName, ['bootstrap', 'ngResource']).config(function ($routeProvider) {
        $routeProvider.when('/datatable/test', { templateUrl: 'app/datatable/html/test.html' });
    }).directive('hawtioSimpleTable', function ($compile) {
        return new DataTable.SimpleDataTable($compile);
    }).directive('hawtioDatatable', function ($templateCache, $compile, $timeout, $filter) {
        // return the directive link function. (compile function not needed)
        return function (scope, element, attrs) {
            var gridOptions = null;
            var data = null;
            var widget = null;
            var timeoutId = null;
            var initialised = false;
            var childScopes = [];
            var rowDetailTemplate = null;
            var rowDetailTemplateId = null;
            var selectedItems = null;

            // used to update the UI
            function updateGrid() {
                // console.log("updating the grid!!");
                Core.$applyNowOrLater(scope);
            }

            function convertToDataTableColumn(columnDef) {
                var data = {
                    mData: columnDef.field,
                    sDefaultContent: ""
                };
                var name = columnDef.displayName;
                if (name) {
                    data["sTitle"] = name;
                }
                var width = columnDef.width;
                if (angular.isNumber(width)) {
                    data["sWidth"] = "" + width + "px";
                } else if (angular.isString(width) && !width.startsWith("*")) {
                    data["sWidth"] = width;
                }
                var template = columnDef.cellTemplate;
                if (template) {
                    data["fnCreatedCell"] = function (nTd, sData, oData, iRow, iCol) {
                        var childScope = childScopes[iRow];
                        if (!childScope) {
                            childScope = scope.$new(false);
                            childScopes[iRow] = childScope;
                        }
                        var entity = oData;
                        childScope["row"] = {
                            entity: entity,
                            getProperty: function (name) {
                                return entity[name];
                            }
                        };

                        var elem = $(nTd);
                        elem.html(template);
                        var contents = elem.contents();
                        contents.removeClass("ngCellText");
                        $compile(contents)(childScope);
                    };
                } else {
                    var cellFilter = columnDef.cellFilter;
                    var render = columnDef.render;
                    if (cellFilter && !render) {
                        var filter = $filter(cellFilter);
                        if (filter) {
                            render = function (data, type, full) {
                                return filter(data);
                            };
                        }
                    }
                    if (render) {
                        data["mRender"] = render;
                    }
                }
                return data;
            }

            function destroyChildScopes() {
                angular.forEach(childScopes, function (childScope) {
                    childScope.$destroy();
                });
                childScopes = [];
            }

            function selectHandler(selection) {
                if (selection && selectedItems) {
                    selectedItems.splice(0, selectedItems.length);
                    selectedItems.push(selection);
                    Core.$apply(scope);
                }
            }

            function onTableDataChange(value) {
                gridOptions = value;
                if (gridOptions) {
                    selectedItems = gridOptions.selectedItems;
                    rowDetailTemplate = gridOptions.rowDetailTemplate;
                    rowDetailTemplateId = gridOptions.rowDetailTemplateId;

                    // TODO deal with updating the gridOptions on the fly?
                    if (widget === null) {
                        var widgetOptions = {
                            selectHandler: selectHandler,
                            disableAddColumns: true,
                            rowDetailTemplateId: rowDetailTemplateId,
                            ignoreColumns: gridOptions.ignoreColumns,
                            flattenColumns: gridOptions.flattenColumns
                        };

                        // lets find a child table element
                        // or lets add one if there's not one already
                        var rootElement = $(element);
                        var tableElement = rootElement.children("table");
                        if (!tableElement.length) {
                            $("<table class='table table-bordered table-condensed'></table>").appendTo(rootElement);
                            tableElement = rootElement.children("table");
                        }
                        tableElement.removeClass('table-striped');
                        tableElement.addClass('dataTable');
                        var trElement = Core.getOrCreateElements(tableElement, ["thead", "tr"]);

                        destroyChildScopes();

                        // convert the column configurations
                        var columns = [];
                        var columnCounter = 1;
                        var extraLeftColumn = rowDetailTemplate || rowDetailTemplateId;
                        if (extraLeftColumn) {
                            columns.push({
                                "mDataProp": null,
                                "sClass": "control center",
                                "sWidth": "30px",
                                "sDefaultContent": '<i class="icon-plus"></i>'
                            });

                            var th = trElement.children("th");
                            if (th.length < columnCounter++) {
                                $("<th></th>").appendTo(trElement);
                            }
                        }

                        var columnDefs = gridOptions.columnDefs;
                        if (angular.isString(columnDefs)) {
                            // TODO watch this value?
                            columnDefs = scope[columnDefs];
                        }

                        angular.forEach(columnDefs, function (columnDef) {
                            // if there's not another <tr> then lets add one
                            th = trElement.children("th");
                            if (th.length < columnCounter++) {
                                var name = columnDef.displayName || "";
                                $("<th>" + name + "</th>").appendTo(trElement);
                            }
                            columns.push(convertToDataTableColumn(columnDef));
                        });
                        widget = new DataTable.TableWidget(scope, $templateCache, $compile, columns, widgetOptions);
                        widget.tableElement = tableElement;

                        var sortInfo = gridOptions.sortInfo;
                        if (sortInfo && columnDefs) {
                            var sortColumns = [];
                            var field = sortInfo.field;
                            if (field) {
                                var idx = columnDefs.findIndex({ field: field });
                                if (idx >= 0) {
                                    if (extraLeftColumn) {
                                        idx += 1;
                                    }
                                    var asc = sortInfo.direction || "asc";
                                    asc = asc.toLowerCase();
                                    sortColumns.push([idx, asc]);
                                }
                            }
                            if (sortColumns.length) {
                                widget.sortColumns = sortColumns;
                            }
                        }

                        // if all the column definitions have an sWidth then lets turn off
                        // the auto-width calculations
                        if (columns.every(function (col) {
                            return col.sWidth;
                        })) {
                            widget.dataTableConfig.bAutoWidth = false;
                        }

                        /*
                        // lets avoid word wrap
                        widget.dataTableConfig["fnCreatedRow"] = function( nRow, aData, iDataIndex ) {
                        var cells = $(nRow).children("td");
                        cells.css("overflow", "hidden");
                        cells.css("white-space", "nowrap");
                        cells.css("text-overflow", "ellipsis");
                        };
                        */
                        var filterText = null;
                        var filterOptions = gridOptions.filterOptions;
                        if (filterOptions) {
                            filterText = filterOptions.filterText;
                        }
                        if (filterText || (angular.isDefined(gridOptions.showFilter) && !gridOptions.showFilter)) {
                            // disable the text filter box
                            widget.dataTableConfig.sDom = 'Rlrtip';
                        }
                        if (filterText) {
                            scope.$watch(filterText, function (value) {
                                var dataTable = widget.dataTable;
                                if (dataTable) {
                                    dataTable.fnFilter(value);
                                }
                            });
                        }
                        if (angular.isDefined(gridOptions.displayFooter) && !gridOptions.displayFooter && widget.dataTableConfig.sDom) {
                            // remove the footer
                            widget.dataTableConfig.sDom = widget.dataTableConfig.sDom.replace('i', '');
                        }
                        // TODO....
                        // lets make sure there is enough th headers for the columns!
                    }
                    if (!data) {
                        // TODO deal with the data name changing one day?
                        data = gridOptions.data;
                        if (data) {
                            var listener = function (value) {
                                if (initialised || (value && (!angular.isArray(value) || value.length))) {
                                    initialised = true;
                                    destroyChildScopes();
                                    widget.populateTable(value);
                                    updateLater();
                                }
                            };
                            scope.$watch(data, listener);

                            // lets add a separate event so we can force updates
                            // if we find cases where the delta logic doesn't work
                            // (such as for nested hawtioinput-input-table)
                            scope.$on("hawtio.datatable." + data, function (args) {
                                var value = Core.pathGet(scope, data);
                                listener(value);
                            });
                        }
                    }
                }
                updateGrid();
            }

            // watch the expression, and update the UI on change.
            scope.$watch(attrs.hawtioDatatable, onTableDataChange);

            // schedule update in one second
            function updateLater() {
                // save the timeoutId for canceling
                timeoutId = $timeout(function () {
                    updateGrid(); // update DOM
                }, 300);
            }

            // 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 () {
                destroyChildScopes();
                $timeout.cancel(timeoutId);
            });

            updateLater(); // kick off the UI update process.
        };
    });

    hawtioPluginLoader.addModule(pluginName);
})(DataTable || (DataTable = {}));
/**
* @module DataTable
*/
var DataTable;
(function (DataTable) {
    /**
    * @class TableWidget
    */
    // TODO would make sense to move this to UI
    var TableWidget = (function () {
        function TableWidget(scope, $templateCache, $compile, dataTableColumns, config) {
            if (typeof config === "undefined") { config = {}; }
            var _this = this;
            this.scope = scope;
            this.$templateCache = $templateCache;
            this.$compile = $compile;
            this.dataTableColumns = dataTableColumns;
            this.config = config;
            this.ignoreColumnHash = {};
            this.flattenColumnHash = {};
            this.detailTemplate = null;
            this.openMessages = [];
            this.addedExpandNodes = false;
            this.tableElement = null;
            this.sortColumns = null;
            this.dataTableConfig = {
                bPaginate: false,
                sDom: 'Rlfrtip',
                bDestroy: true,
                bAutoWidth: true
            };
            // the jQuery DataTable widget
            this.dataTable = null;
            // TODO is there an easier way of turning an array into a hash to true so it acts as a hash?
            angular.forEach(config.ignoreColumns, function (name) {
                _this.ignoreColumnHash[name] = true;
            });
            angular.forEach(config.flattenColumns, function (name) {
                _this.flattenColumnHash[name] = true;
            });

            var templateId = config.rowDetailTemplateId;
            if (templateId) {
                this.detailTemplate = this.$templateCache.get(templateId);
            }
        }
        /**
        * Adds new data to the table
        * @method addData
        * @for TableWidget
        * @param {Object} newData
        */
        TableWidget.prototype.addData = function (newData) {
            var dataTable = this.dataTable;
            dataTable.fnAddData(newData);
        };

        /**
        * Populates the table with the given data
        * @method populateTable
        * @for TableWidget
        * @param {Object} data
        */
        TableWidget.prototype.populateTable = function (data) {
            var _this = this;
            var $scope = this.scope;

            if (!data) {
                $scope.messages = [];
            } else {
                $scope.messages = data;

                var formatMessageDetails = function (dataTable, parentRow) {
                    var oData = dataTable.fnGetData(parentRow);
                    var div = $('<div>');
                    div.addClass('innerDetails');
                    _this.populateDetailDiv(oData, div);
                    return div;
                };

                var array = data;
                if (angular.isArray(data)) {
                } else if (angular.isObject(data)) {
                    array = [];
                    angular.forEach(data, function (object) {
                        return array.push(object);
                    });
                }

                var tableElement = this.tableElement;
                if (!tableElement) {
                    tableElement = $('#grid');
                }
                var tableTr = Core.getOrCreateElements(tableElement, ["thead", "tr"]);
                var tableBody = Core.getOrCreateElements(tableElement, ["tbody"]);
                var ths = $(tableTr).find("th");

                // lets add new columns based on the data...
                // TODO wont compile in TypeScript!
                //var columns = this.dataTableColumns.slice();
                var columns = [];
                angular.forEach(this.dataTableColumns, function (value) {
                    return columns.push(value);
                });

                //var columns = this.dataTableColumns.slice();
                var addColumn = function (key, title) {
                    columns.push({
                        "sDefaultContent": "",
                        "mData": null,
                        mDataProp: key
                    });

                    // lets see if we need to add another <th>
                    if (tableTr) {
                        $("<th>" + title + "</th>").appendTo(tableTr);
                    }
                };

                var checkForNewColumn = function (value, key, prefix) {
                    // lets check if we have a column data for it (if its not ignored)
                    //var keyName: string = key.toString();
                    //var config: Object = {mDataProp: key};
                    var found = _this.ignoreColumnHash[key] || columns.any(function (k, v) {
                        return "mDataProp" === k && v === key;
                    });

                    //var found = this.ignoreColumnHash[key] || columns.any(config);
                    if (!found) {
                        // lets check if its a flatten column
                        if (_this.flattenColumnHash[key]) {
                            // TODO so this only works on the first row - sucks! :)
                            if (angular.isObject(value)) {
                                var childPrefix = prefix + key + ".";
                                angular.forEach(value, function (value, key) {
                                    return checkForNewColumn(value, key, childPrefix);
                                });
                            }
                        } else {
                            addColumn(prefix + key, humanizeValue(key));
                        }
                    }
                };

                if (!this.config.disableAddColumns && angular.isArray(array) && array.length > 0) {
                    var first = array[0];
                    if (angular.isObject(first)) {
                        angular.forEach(first, function (value, key) {
                            return checkForNewColumn(value, key, "");
                        });
                    }
                }

                // lets default to column 1 sorting if there's no property on column 1 for expansion
                if (columns.length > 1) {
                    var col0 = columns[0];
                    if (!this.sortColumns && !col0["mDataProp"] && !col0["mData"]) {
                        var sortOrder = [[1, "asc"]];
                        this.sortColumns = sortOrder;
                    }
                }
                if (array.length && !angular.isArray(array[0])) {
                    //this.dataTableConfig["aoData"] = array;
                    this.dataTableConfig["aaData"] = array;
                } else {
                    this.dataTableConfig["aaData"] = array;
                }
                this.dataTableConfig["aoColumns"] = columns;
                if (this.sortColumns) {
                    this.dataTableConfig["aaSorting"] = this.sortColumns;
                }

                if (this.dataTable) {
                    this.dataTable.fnClearTable(false);
                    this.dataTable.fnAddData(array);
                    this.dataTable.fnDraw();
                    // lets try update it...
                } else {
                    this.dataTable = tableElement.dataTable(this.dataTableConfig);
                }

                var widget = this;

                if (this.dataTable) {
                    var keys = new KeyTable({
                        "table": tableElement[0],
                        "datatable": this.dataTable
                    });
                    keys.fnSetPosition(0, 0);

                    if (angular.isArray(data) && data.length) {
                        var selected = data[0];
                        var selectHandler = widget.config.selectHandler;
                        if (selected && selectHandler) {
                            selectHandler(selected);
                        }
                    }
                }

                // lets try focus on the table
                $(tableElement).focus();

                var widget = this;

                // add a handler for the expand/collapse column for all rows (and future rows)
                var expandCollapseNode = function () {
                    var dataTable = widget.dataTable;
                    var parentRow = this.parentNode;
                    var openMessages = widget.openMessages;
                    var i = $.inArray(parentRow, openMessages);

                    var element = $('i', this);
                    if (i === -1) {
                        element.removeClass('icon-plus');
                        element.addClass('icon-minus');
                        var dataDiv = formatMessageDetails(dataTable, parentRow);
                        var detailsRow = $(dataTable.fnOpen(parentRow, dataDiv, 'details'));
                        detailsRow.css("padding", "0");

                        setTimeout(function () {
                            detailsRow.find(".innerDetails").slideDown(400, function () {
                                $(parentRow).addClass('opened');
                                openMessages.push(parentRow);
                            });
                        }, 20);
                    } else {
                        $(parentRow.nextSibling).find(".innerDetails").slideUp(400, function () {
                            $(parentRow).removeClass('opened');
                            element.removeClass('icon-minus');
                            element.addClass('icon-plus');
                            dataTable.fnClose(parentRow);
                            openMessages.splice(i, 1);
                        });
                    }

                    // lets let angular render any new detail templates
                    Core.$apply($scope);
                };

                if (!this.addedExpandNodes) {
                    this.addedExpandNodes = true;

                    $(tableElement).on("click", "td.control", expandCollapseNode);

                    //$(document).on("click", "#grid td.control", expandCollapseNode);
                    keys.event.action(0, null, function (node) {
                        expandCollapseNode.call(node);
                    });
                }

                keys.event.focus(null, null, function (node) {
                    var dataTable = widget.dataTable;
                    var row = node;
                    if (node) {
                        var nodeName = node.nodeName;
                        if (nodeName) {
                            if (nodeName.toLowerCase() === "td") {
                                row = $(node).parents("tr")[0];
                            }
                            var selected = dataTable.fnGetData(row);
                            var selectHandler = widget.config.selectHandler;
                            if (selected && selectHandler) {
                                selectHandler(selected);
                            }
                        }
                    }
                });

                // $(document).on("click", "#grid td", function () {
                $(tableElement).find("td.control").on("click", function (event) {
                    var dataTable = widget.dataTable;
                    if ($(this).hasClass('selected')) {
                        $(this).removeClass('focus selected');
                    } else {
                        if (!widget.config.multiSelect) {
                            dataTable.$('td.selected').removeClass('focus selected');
                        }
                        $(this).addClass('focus selected');

                        var row = $(this).parents("tr")[0];
                        var selected = dataTable.fnGetData(row);
                        var selectHandler = widget.config.selectHandler;
                        if (selected && selectHandler) {
                            selectHandler(selected);
                        }
                    }
                });
            }
            Core.$apply($scope);
        };

        TableWidget.prototype.populateDetailDiv = function (row, div) {
            // lets remove the silly "0" property that gets shoved in there due to the expand/collapse row
            delete row["0"];
            var scope = this.scope.$new();
            scope.row = row;
            scope.templateDiv = div;
            var template = this.detailTemplate;
            if (!template) {
                var templateId = this.config.rowDetailTemplateId;
                if (templateId) {
                    this.detailTemplate = this.$templateCache.get(templateId);
                    template = this.detailTemplate;
                }
            }
            if (template) {
                div.html(template);
                this.$compile(div.contents())(scope);
            }
        };
        return TableWidget;
    })();
    DataTable.TableWidget = TableWidget;
})(DataTable || (DataTable = {}));
/**
* @module Infinispan
*/
var Infinispan;
(function (Infinispan) {
    /**
    * Returns the name of the cache from the mbean results
    * @class CLI
    */
    var CLI = (function () {
        function CLI(workspace, jolokia) {
            this.workspace = workspace;
            this.jolokia = jolokia;
            this.cacheName = null;
            this.sessionId = null;
            this.useSessionIds = true;
        }
        CLI.prototype.setCacheName = function (name) {
            if (name) {
                name = trimQuotes(name);
                var postfix = "(local)";
                if (name.endsWith(postfix)) {
                    name = name.substring(0, name.length - postfix.length);
                }
            }
            if (!this.cacheName || this.cacheName !== name) {
                if (this.sessionId) {
                    this.deleteSession(this.sessionId);
                }
                this.cacheName = name;
                this.createSession();
            }
        };

        CLI.prototype.createSession = function () {
            var _this = this;
            if (this.useSessionIds) {
                var mbean = Infinispan.getInterpreterMBean(this.workspace);
                if (mbean) {
                    var cli = this;
                    this.jolokia.execute(mbean, "createSessionId", this.cacheName, onSuccess(function (value) {
                        console.log("Has session ID: " + value);
                        _this.sessionId = value;
                    }));
                } else {
                    this.warnMissingMBean();
                }
            }
        };

        CLI.prototype.execute = function (sql, handler) {
            if (sql) {
                sql = sql.trim();
                if (!sql.endsWith(";")) {
                    sql += ";";
                }
                var sessionId = (this.useSessionIds) ? this.sessionId : null;
                if (!this.useSessionIds) {
                    sql = "cache " + this.cacheName + "; " + sql;
                }
                if (sessionId || !this.useSessionIds) {
                    var mbean = Infinispan.getInterpreterMBean(this.workspace);
                    if (mbean) {
                        this.jolokia.execute(mbean, "execute", sessionId, sql, onSuccess(handler));
                    } else {
                        this.warnMissingMBean();
                    }
                } else {
                    notification("warning", "Cannot evaluate SQL as we don't have a sessionId yet!");
                }
            }
        };

        CLI.prototype.deleteSession = function (sessionId) {
            // there is no delete API so far
        };

        CLI.prototype.warnMissingMBean = function () {
            notification("error", "No Interpreter MBean available");
        };
        return CLI;
    })();
    Infinispan.CLI = CLI;
})(Infinispan || (Infinispan = {}));
var Infinispan;
(function (Infinispan) {
    function TreeController($scope, $location, workspace) {
        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(updateSelectionFromURL, 50);
        });

        $scope.$watch('workspace.tree', function () {
            if (workspace.moveIfViewInvalid())
                return;

            var children = [];

            // lets pull out each context
            var tree = workspace.tree;
            if (tree) {
                var domainName = Infinispan.jmxDomain;
                var folder = tree.get(domainName);
                if (folder) {
                    var cachesFolder = new Folder("Caches");
                    cachesFolder.domain = domainName;
                    cachesFolder.key = "root-Infinispan-Caches";
                    cachesFolder.typeName = "Caches";
                    children.push(cachesFolder);
                    addAllCacheStatistics(folder, cachesFolder);
                }

                var treeElement = $("#infinispantree");
                Jmx.enableTree($scope, $location, workspace, treeElement, children);

                // lets do this asynchronously to avoid Error: $digest already in progress
                setTimeout(updateSelectionFromURL, 50);
            }
        });

        function addAllCacheStatistics(folder, answer) {
            if (folder) {
                var children = folder.children;
                if (children) {
                    angular.forEach(folder.children, function (value, key) {
                        if (value.objectName && value.title === "Statistics") {
                            var cacheName = value.parent.parent.title || value.title;
                            var name = humanizeValue(cacheName);
                            var cacheFolder = new Folder(name);
                            cacheFolder.addClass = "org-infinispn-cache";
                            cacheFolder.typeName = "Cache";
                            cacheFolder.key = answer.key + "-" + cacheName;
                            cacheFolder.objectName = value.objectName;
                            cacheFolder.domain = value.domain;
                            cacheFolder.entries = value.entries;
                            answer.children.push(cacheFolder);
                        } else {
                            addAllCacheStatistics(value, answer);
                        }
                    });
                }
            }
        }

        function updateSelectionFromURL() {
            Jmx.updateTreeSelectionFromURL($location, $("#infinispantree"), true);
        }
    }
    Infinispan.TreeController = TreeController;
})(Infinispan || (Infinispan = {}));
/**
* @module Infinispan
* @main Infinispan
*/
var Infinispan;
(function (Infinispan) {
    var pluginName = 'infinispan';
    Infinispan.jmxDomain = 'org.infinispan';

    var toolBar = "app/infinispan/html/attributeToolBar.html";

    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/infinispan/query', { templateUrl: 'app/infinispan/html/query.html' });
    }).filter('infinispanCacheName', function () {
        return Infinispan.infinispanCacheName;
    }).run(function (workspace, viewRegistry, helpRegistry) {
        viewRegistry['infinispan'] = 'app/infinispan/html/layoutCacheTree.html';
        helpRegistry.addUserDoc('infinispan', 'app/infinispan/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties(Infinispan.jmxDomain);
        });

        /*
        Jmx.addAttributeToolBar(pluginName, jmxDomain, (selection: NodeSelection) => {
        // TODO there should be a nicer way to do this!
        var folderNames = selection.folderNames;
        if (folderNames && selection.domain === jmxDomain) {
        return toolBar;
        }
        return null;
        });
        */
        // register default attribute views
        var nameTemplate = '<div class="ngCellText" title="Infinispan cache">{{row.entity | infinispanCacheName}}</div>';

        var attributes = workspace.attributeColumnDefs;
        attributes[Infinispan.jmxDomain + "/Caches/folder"] = [
            {
                field: '_id', displayName: 'Name',
                cellTemplate: nameTemplate, width: "**" },
            { field: 'numberOfEntries', displayName: 'Entries' },
            { field: 'hits', displayName: 'Hits' },
            { field: 'hitRatio', displayName: 'Hit Ratio' },
            { field: 'stores', displayName: 'Stores' },
            { field: 'averageReadTime', displayName: 'Avg Read Time' },
            { field: 'averageWriteTime', displayName: 'Avg Write Time' }
        ];

        workspace.topLevelTabs.push({
            id: "infinispan",
            content: "Infinispan",
            title: "View your distributed data",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties(Infinispan.jmxDomain);
            },
            href: function () {
                return "#/jmx/attributes?tab=infinispan";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("infinispan");
            }
        });

        workspace.subLevelTabs.push({
            content: '<i class="icon-pencil"></i> Query',
            title: "Perform InSQL commands on the cache",
            isValid: function (workspace) {
                return Infinispan.getSelectedCacheName(workspace) && Infinispan.getInterpreterMBean(workspace);
            },
            href: function () {
                return "#/infinispan/query";
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Infinispan || (Infinispan = {}));
/**
* @module Infinispan
*/
var Infinispan;
(function (Infinispan) {
    /**
    * Returns the name of the cache from the mbean results
    * @method infinispanCacheName
    * @for Infinispan
    * @param {any} entity
    * @return {String}
    */
    function infinispanCacheName(entity) {
        // there's no name in the MBean so lets extract it from the JMX ObjectName
        if (entity) {
            var id = entity._id;
            if (id) {
                var prefix = 'name="';
                var idx = id.indexOf(prefix);
                if (idx > 0) {
                    idx += prefix.length;
                    var lastIdx = id.indexOf('"', idx + 1);
                    if (lastIdx > 0) {
                        return id.substring(idx, lastIdx);
                    } else {
                        return id.substring(idx);
                    }
                }
            }
            return id;
        }
        return null;
    }
    Infinispan.infinispanCacheName = infinispanCacheName;

    /**
    * Returns the MBean ObjectName for the interpreter
    * @method getInterpreterMBean
    * @for Infinispan
    * @param {Workspace} workspace
    * @return {String}
    */
    function getInterpreterMBean(workspace) {
        if (workspace) {
            var folder = workspace.findMBeanWithProperties(Infinispan.jmxDomain, { component: "Interpreter", type: "CacheManager" });
            if (folder) {
                return folder.objectName;
            }
        }
        return null;
    }
    Infinispan.getInterpreterMBean = getInterpreterMBean;

    function getSelectedCacheName(workspace) {
        var selection = workspace.selection;
        if (selection && selection.domain === Infinispan.jmxDomain) {
            // lets get the cache name
            return selection.entries["name"];
        }
        return null;
    }
    Infinispan.getSelectedCacheName = getSelectedCacheName;
})(Infinispan || (Infinispan = {}));
var Infinispan;
(function (Infinispan) {
    function QueryController($scope, $location, workspace, jolokia) {
        var interpreter = new Infinispan.CLI(workspace, jolokia);
        $scope.logs = [];
        $scope.filteredLogs = [];
        $scope.selectedItems = [];
        $scope.searchText = "";
        $scope.filter = {
            // The default logging level to show, empty string => show all
            logLevelQuery: "",
            // The default value of the exact match logging filter
            logLevelExactMatch: false
        };

        var columnDefs = [
            {
                field: 'key',
                displayName: 'Key'
            },
            {
                field: 'value',
                displayName: 'Value'
            }
        ];

        $scope.gridOptions = {
            selectedItems: $scope.selectedItems,
            data: 'filteredLogs',
            displayFooter: false,
            showFilter: false,
            filterOptions: {
                filterText: "searchText"
            },
            columnDefs: columnDefs,
            rowDetailTemplateId: "logDetailTemplate"
        };

        $scope.$on("$routeChangeSuccess", function (event, current, previous) {
            // lets do this asynchronously to avoid Error: $digest already in progress
            setTimeout(refreshCacheName, 50);
        });

        $scope.$watch('workspace.selection', function () {
            refreshCacheName();
        });

        $scope.doQuery = function () {
            if ($scope.sql) {
                interpreter.execute($scope.sql, handleResults);
            }
        };

        function handleResults(results) {
            $scope.output = null;
            if (!results) {
                console.log("no output...");
            } else {
                var error = results["ERROR"] || "";
                var stackTrace = results["STACKTRACE"] || "";
                if (error || stackTrace) {
                    if (stackTrace) {
                        error += "\n" + stackTrace;
                    }
                    notification("error", error);
                } else {
                    var output = results["OUTPUT"];
                    if (!output) {
                        notification("error", "No results!");
                    } else {
                        $scope.output = output;
                        console.log("==== output: " + output);
                        Core.$apply($scope);
                    }
                }
            }
        }

        function refreshCacheName() {
            var cacheName = Infinispan.getSelectedCacheName(workspace);
            console.log("selected cacheName is: " + cacheName);
            if (cacheName) {
                interpreter.setCacheName(cacheName);
            }
        }
    }
    Infinispan.QueryController = QueryController;
})(Infinispan || (Infinispan = {}));
var SpringBatch;
(function (SpringBatch) {
    function JobOverviewExecListController($scope, $routeParams, $location, workspace, jolokia, $resource, $rootScope, $http) {
        var springBatchServerOrigin = $rootScope.springBatchServer;
        var springBatchServerPath = springBatchServerOrigin + 'jobs/:jobName';
        var proxyUrl = $rootScope.proxyUrl;
        var executionsListPath = '/:jobInstanceId/executions.json';
        var paramsListPath = 'jobs/:jobName/:jobInstanceId';
        var jobName = $routeParams.jobName;
        $scope.jobName = $routeParams.jobName;
        var jobInstances = null;
        var jobList = $resource(proxyUrl + springBatchServerPath);
        $scope.springBatchServer = encodeURIComponent(springBatchServerOrigin);

        $scope.executionPredicate = 'name';
        $scope.executionReverse = false;
        $scope.stepPredicate = 'name';
        $scope.stepReverse = false;

        $scope.fetchAllExecutions = function (jobInstance) {
            if (jobInstance != undefined) {
                var jobList = $resource(proxyUrl + springBatchServerPath + executionsListPath);
                jobList.get({ 'jobName': jobName, jobInstanceId: jobInstance.id }, function (data) {
                    var jobExecutionList = new Array();
                    for (var execution in data.jobInstance.jobExecutions) {
                        data.jobInstance.jobExecutions[execution].id = execution;
                        jobExecutionList.add(data.jobInstance.jobExecutions[execution]);
                    }
                    $scope.jobName = jobName;
                    $scope.jobExecutionList = jobExecutionList;
                });
            }
        };

        $scope.fetchParams = function (jobName, jobInstanceId, executionId) {
            var paramsResource = $resource(proxyUrl + springBatchServerPath + paramsListPath);
            paramsResource.get({ 'jobName': jobName, 'jobInstanceId': jobInstanceId + '.json' }, function (data) {
                var jobParams = new Array();
                if (executionId) {
                    for (var param in data.jobInstance.jobExecutions[executionId].jobParameters) {
                        jobParams.add({ 'name': param, 'value': data.jobInstance.jobExecutions[executionId].jobParameters[param] });
                    }
                } else {
                    for (var execution in data.jobInstance.jobExecutions) {
                        for (var param in data.jobInstance.jobExecutions[execution].jobParameters) {
                            jobParams.add({ 'name': param, 'value': data.jobInstance.jobExecutions[execution].jobParameters[param] });
                        }
                        break;
                    }
                    $scope.jobParams = jobParams;
                }
            });
        };

        $scope.removeParam = function (jobParams, index) {
            jobParams.splice(index, 1);
        };

        $scope.addParam = function (jobParams, index) {
            jobParams.add({ name: '', value: '' });
        };

        $scope.runJob = function (jobName, jobParams) {
            if (jobName && jobParams) {
                var springServerOrigin = springBatchServerOrigin.replace('\\', '');
                var postUrl = proxyUrl + springServerOrigin + 'jobs/' + jobName + '.json';
                $http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
                var params = '';
                for (var param in jobParams) {
                    params = params + jobParams[param].name + '=' + jobParams[param].value;
                    if ((param + 1) != jobParams.length) {
                        params = params + ',';
                    }
                }
                params = encodeURIComponent(params);
                $http.post(postUrl, 'jobParameters=' + params).success(function (data) {
                    if (data.jobExecution) {
                        $rootScope.alert.content = 'Job started successfully.';
                        $rootScope.alert.type = 'alert-success';
                        $rootScope.alert.show();
                    } else if (data.errors) {
                        $rootScope.alert.content = '';
                        for (var message in data.errors) {
                            $rootScope.alert.content += data.errors[message] + '\n';
                            $rootScope.alert.type = 'alert-error';
                            $rootScope.alert.show();
                        }
                    }
                }).error(function (data) {
                    $rootScope.alert.content = 'Count not start the job';
                    $rootScope.alert.type = 'alert-error';
                    $rootScope.alert.show();
                });
            }
        };

        jobList.get({ 'jobName': jobName + '.json' }, function (data) {
            for (var job in data.job.jobInstances) {
                data.job.jobInstances[job].id = job;
                jobInstances = data.job.jobInstances;
            }
            if ($routeParams.jobInstanceId == undefined) {
                for (var job in data.job.jobInstances) {
                    $scope.jobInstance = jobInstances[job];
                    break;
                }
            } else {
                if (jobInstances && jobInstances[$routeParams.jobInstanceId]) {
                    $scope.jobInstance = jobInstances[$routeParams.jobInstanceId];
                }
            }
            if ($scope.jobInstance) {
                $scope.fetchAllExecutions($scope.jobInstance);
                $scope.fetchParams(jobName, $scope.jobInstance.id);
            } else {
                $scope.jobParams = new Array();
            }
        });

        $scope.refreshJobInstance = function (jobInstance) {
            var jobList = $resource(proxyUrl + springBatchServerPath);
            jobList.get({ 'jobName': jobName + '.json' }, function (data) {
                for (var job in data.job.jobInstances) {
                    data.job.jobInstances[job].id = job;
                }
                var jobInstanceId = null;
                if (jobInstance && jobInstance.id) {
                    jobInstanceId = jobInstance.id;
                    $scope.jobInstance = data.job.jobInstances[jobInstanceId];
                    $scope.fetchAllExecutions(data.job.jobInstances[jobInstanceId]);
                    $scope.fetchParams(jobName, jobInstanceId);
                } else {
                    for (var job in data.job.jobInstances) {
                        $scope.jobInstance = data.job.jobInstances[job];
                        $scope.fetchAllExecutions(data.job.jobInstances[job]);
                        $scope.fetchParams(jobName, job);
                        break;
                    }
                }
                $scope.stepExecutionList = null;
            });
        };
        $scope.fetchNextJobInstance = function (jobInstance) {
            var tempId = null;
            var jobList = $resource(proxyUrl + springBatchServerPath);
            jobList.get({ 'jobName': jobName + '.json' }, function (data) {
                for (var job in data.job.jobInstances) {
                    data.job.jobInstances[job].id = job;
                    if (jobInstance && jobInstance.id && (parseInt(job) > parseInt(jobInstance.id))) {
                        tempId = job;
                        break;
                    } else if (jobInstance && jobInstance.id) {
                        tempId = jobInstance.id;
                    }
                }
                if (jobInstance) {
                    $scope.jobInstance = data.job.jobInstances[tempId];
                } else {
                    for (var job in data.job.jobInstances) {
                        $scope.jobInstance = data.job.jobInstances[job];
                        break;
                    }
                }
                if ($scope.jobInstance) {
                    $scope.fetchAllExecutions($scope.jobInstance);
                    $scope.fetchParams(jobName, $scope.jobInstance.id);
                }

                $scope.stepExecutionList = null;
            });
        };
        $scope.fetchPrevJobInstance = function (jobInstance) {
            var tempId = null;
            var jobList = $resource(proxyUrl + springBatchServerPath);
            jobList.get({ 'jobName': jobName + '.json' }, function (data) {
                for (var job in data.job.jobInstances) {
                    data.job.jobInstances[job].id = job;
                    if (jobInstance && jobInstance.id && (parseInt(job) < parseInt(jobInstance.id))) {
                        tempId = job;
                    }
                }
                if (jobInstance) {
                    if ((tempId == null) && jobInstance.id) {
                        tempId = jobInstance.id;
                    }
                    $scope.jobInstance = data.job.jobInstances[tempId];
                } else {
                    for (var job in data.job.jobInstances) {
                        $scope.jobInstance = data.job.jobInstances[job];
                        break;
                    }
                }
                if ($scope.jobInstance) {
                    $scope.fetchAllExecutions($scope.jobInstance);
                    $scope.fetchParams(jobName, $scope.jobInstance.id);
                }
                $scope.stepExecutionList = null;
            });
        };

        $scope.fetchStepsForExecution = function (executionId) {
            var jobList = $resource(proxyUrl + springBatchServerOrigin + 'jobs/executions/:executionId');
            jobList.get({ 'executionId': executionId + '.json' }, function (data) {
                var stepList = new Array();
                for (var execution in data.jobExecution.stepExecutions) {
                    data.jobExecution.stepExecutions[execution].name = execution;
                    stepList.add(data.jobExecution.stepExecutions[execution]);
                }
                $scope.executionId = executionId;
                $scope.stepExecutionList = stepList;
            });
        };
    }
    SpringBatch.JobOverviewExecListController = JobOverviewExecListController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function stepExecutionContextController($scope, $routeParams, $http, $rootScope) {
        var springBatchServerOrigin = $rootScope.springBatchServer;
        var proxyUrl = '/hawtio';
        var jobExecutionId = $routeParams.jobExecutionId;
        var stepExecutionId = $routeParams.stepExecutionId;
        var jobName = $routeParams.jobName;

        var jobId = $routeParams.jobId;
        $scope.springBatchServer = springBatchServerOrigin;
        var stepExecutionContext = $http.get(proxyUrl + "/contextFormatter?jobExecutionId=" + jobExecutionId + "&stepExecutionId=" + stepExecutionId + "&server=" + springBatchServerOrigin + "&contextType=stepExecution").success(function (data) {
            $scope.htmlView = data;
        });
        $scope.jobName = jobName;
        $scope.jobId = jobId;
    }
    SpringBatch.stepExecutionContextController = stepExecutionContextController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function ConnectSpringBatchController($scope, $routeParams, $location, workspace, $rootScope, $resource, $http) {
        $scope.host = 'localhost';
        $scope.port = 8080;

        $scope.connectSpringBatch = function () {
            if ($scope.selectedSpringBatchServer) {
                $rootScope.springBatchServer = $scope.selectedSpringBatchServer;
                $rootScope.alert.content = 'Connected successfully.';
                $rootScope.alert.type = 'alert-success';
                $rootScope.alert.show();
            }
        };

        $scope.addSpringBatchServerToGlobalList = function () {
            var server = SpringBatch.getServerUrl($scope.host, $scope.port, $scope.path);
            if ($rootScope.springBatchServerList.indexOf($scope.selectedSpringBatchServer) > 0) {
                $rootScope.alert.content = 'Server already in the list.';
                $rootScope.alert.type = 'alert-error';
                $rootScope.alert.show();
                return;
            }
            $http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
            $http.post('/hawtio/springBatch', 'server=' + server).success(function (data) {
                $rootScope.springBatchServerList.add(server);
                $rootScope.springBatchServer = $scope.selectedSpringBatchServer;
                $rootScope.alert.content = 'Server added.';
                $rootScope.alert.type = 'alert-success';
                $rootScope.alert.show();
            }).error(function (data) {
                $rootScope.alert.content = 'Could not add server.';
                $rootScope.alert.type = 'alert-error';
                $rootScope.alert.show();
            });
        };

        $scope.removeServer = function (index) {
            $http.delete('/hawtio/springBatch?server=' + encodeURIComponent($scope.selectedSpringBatchServer)).success(function (data) {
                $scope.springBatchServerList.splice($scope.springBatchServerList.indexOf($scope.selectedSpringBatchServer), 1);
                $rootScope.alert.content = 'Server deleted.';
                $rootScope.alert.type = 'alert-info';
                $rootScope.alert.show();
            }).error(function (data) {
                $rootScope.alert.content = 'Could not delete server.';
                $rootScope.alert.type = 'alert-error';
                $rootScope.alert.show();
            });
        };

        $scope.editServer = function () {
            $scope.host = SpringBatch.getHost($scope.selectedSpringBatchServer);
            $scope.port = parseInt(SpringBatch.getPort($scope.selectedSpringBatchServer));
            $scope.path = SpringBatch.getServerSuffix($scope.selectedSpringBatchServer);
        };

        $scope.updateServer = function () {
            var server = SpringBatch.getServerUrl($scope.host, $scope.port, $scope.path);
            var replaceServer = $scope.selectedSpringBatchServer;
            $http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
            $http.post('/hawtio/springBatch', 'server=' + server + '&replaceServer=' + replaceServer).success(function (data) {
                $rootScope.springBatchServerList[$rootScope.springBatchServerList.indexOf($scope.selectedSpringBatchServer)] = server;
                $rootScope.alert.content = 'Server updated.';
                $rootScope.alert.type = 'alert-success';
                $rootScope.alert.show();
            }).error(function (data) {
                $rootScope.alert.content = 'Could not add server.';
                $rootScope.alert.type = 'alert-error';
                $rootScope.alert.show();
            });
        };
    }
    SpringBatch.ConnectSpringBatchController = ConnectSpringBatchController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function ServerListController($scope, $location, workspace, jolokia, $resource, $rootScope, $http) {
        var serverList = [];
        var serverHref = '';
        for (var server in $rootScope.springBatchServerList) {
            serverHref += '#/springbatch/jobs/';
            serverHref += SpringBatch.getHost($rootScope.springBatchServerList[server]) + '/';
            serverHref += SpringBatch.getPort($rootScope.springBatchServerList[server]);
            if (SpringBatch.getServerSuffix($rootScope.springBatchServerList[server]).length > 0)
                serverHref += '/' + SpringBatch.getServerSuffix($rootScope.springBatchServerList[server]);
            serverList.add({
                href: serverHref,
                hostname: SpringBatch.getHost($rootScope.springBatchServerList[server]),
                port: SpringBatch.getPort($rootScope.springBatchServerList[server])
            });
            serverHref = '';
        }
        $scope.serverList = serverList;
    }
    SpringBatch.ServerListController = ServerListController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function JobListController($scope, $location, workspace, jolokia, $resource, $rootScope, $http, $routeParams) {
        var targetServerHost = $routeParams.host;
        var targetServerPort = $routeParams.port;
        var targetServerSuffix = $routeParams.serverSuffix;

        var targetServer = targetServerHost + '\\:' + targetServerPort + '/';
        if ((targetServerSuffix != undefined) && (targetServerSuffix.length > 0))
            targetServer += (targetServerSuffix + '/');
        $rootScope.springBatchServer = targetServer;

        var springBatchServerPath = $rootScope.springBatchServer + 'jobs.json';
        var proxyUrl = $rootScope.proxyUrl;

        $scope.predicate = 'name';
        $scope.reverse = false;

        $scope.getJobList = function () {
            var jobList = $resource(proxyUrl + springBatchServerPath);
            jobList.get(function (data) {
                if (data.jobs && data.jobs.registrations) {
                    var jobList = new Array();
                    for (var job in data.jobs.registrations) {
                        data.jobs.registrations[job].showLaunchForm = false;
                        data.jobs.registrations[job].launchParams = '';
                        jobList.add(data.jobs.registrations[job]);
                    }
                    $scope.jobList = jobList;
                }
            });
        };

        $scope.getJobList();

        $scope.launchJob = function (jobName) {
            var job;
            for (var idx in $scope.jobList) {
                if ($scope.jobList[idx].name == jobName)
                    job = $scope.jobList[idx];
            }
            var params = job.launchParams;

            if (jobName && params) {
                var springServerOrigin = $rootScope.springBatchServer.replace('\\', '');
                var postUrl = proxyUrl + springServerOrigin + 'jobs/' + jobName + '.json';
                $http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
                params = encodeURIComponent(params);
                $http.post(postUrl, 'jobParameters=' + params).success(function (data) {
                    if (data.jobExecution) {
                        $rootScope.alert.content = 'Job started successfully.';
                        $rootScope.alert.type = 'alert-success';
                        $rootScope.alert.show();
                        $scope.getJobList();
                    } else if (data.errors) {
                        $rootScope.alert.content = '';
                        for (var message in data.errors) {
                            $rootScope.alert.content += data.errors[message] + '\n';
                            $rootScope.alert.type = 'alert-error';
                            $rootScope.alert.show();
                        }
                    }
                }).error(function (data) {
                    $rootScope.alert.content = 'Count not start the job';
                    $rootScope.alert.type = 'alert-error';
                    $rootScope.alert.show();
                });
            }
        };
    }
    SpringBatch.JobListController = JobListController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function NavBarController($scope, $routeParams, $location, workspace) {
        var subLevelTabs = [
            { uri: 'servers', name: 'Servers List' },
            { uri: 'jobs/executions', name: 'Jobs Execution List' },
            { uri: 'connect', name: 'Connect' }
        ];

        $scope.subLevelTabs = subLevelTabs;

        $scope.isActive = function (tab) {
            return ('/springbatch/' + tab.uri === $location.path());
        };
    }
    SpringBatch.NavBarController = NavBarController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function jobExecutionContextController($scope, $routeParams, $http, $rootScope) {
        var springBatchServerOrigin = $rootScope.springBatchServer;
        var proxyUrl = '/hawtio';
        var jobExecutionId = $routeParams.jobExecutionId;
        var jobName = $routeParams.jobName;
        var jobId = $routeParams.jobId;
        $scope.springBatchServer = springBatchServerOrigin;
        var jobExecutionContext = $http.get(proxyUrl + "/contextFormatter?jobExecutionId=" + jobExecutionId + "&server=" + springBatchServerOrigin + "&contextType=jobExecution").success(function (data) {
            $scope.htmlView = data;
        });
        $scope.jobId = jobId;
        $scope.jobName = jobName;
    }
    SpringBatch.jobExecutionContextController = jobExecutionContextController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function ExecutionHistoryController($scope, $routeParams, $location, workspace, $resource, $rootScope) {
        var springBatchServerOrigin = $rootScope.springBatchServer;
        var proxyUrl = '/hawtio/proxy/';
        var executionHistoryPath = 'jobs/:jobName/executions.json';
        $scope.predicate = 'id';
        $scope.reverse = false;

        var jobExecutionRes = $resource(proxyUrl + springBatchServerOrigin + executionHistoryPath);
        jobExecutionRes.get({ 'jobName': $routeParams.jobName }, function (data) {
            var executionList = new Array();
            for (var execution in data.jobExecutions) {
                data.jobExecutions[execution].id = execution;
                executionList.add(data.jobExecutions[execution]);
            }
            $scope.executionHistory = executionList;
            $scope.jobName = $routeParams.jobName;
            $scope.jobId = $routeParams.jobId;
        });
    }
    SpringBatch.ExecutionHistoryController = ExecutionHistoryController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function SpringBatchJobExecutionListController($scope, $resource, $rootScope) {
        var springBatchServerOrigin = $rootScope.springBatchServer;
        if (springBatchServerOrigin == undefined) {
            $rootScope.alert.content = 'No Server selected. Please, use Connect or Server List screen to select one.';
            $rootScope.alert.type = 'alert-error';
            $rootScope.alert.show();
            return;
        }
        var springBatchServerPath = springBatchServerOrigin + 'jobs';
        var proxyUrl = $rootScope.proxyUrl;
        var executionsListPath = '/executions.json';

        $scope.predicate = 'name';
        $scope.reverse = false;

        var executionListRes = $resource(proxyUrl + springBatchServerPath + executionsListPath);
        executionListRes.get(function (data) {
            var executionList = new Array();
            for (var execution in data.jobExecutions) {
                data.jobExecutions[execution].id = execution;
                executionList.add(data.jobExecutions[execution]);
            }
            $scope.jobExecutions = executionList;
        });
    }
    SpringBatch.SpringBatchJobExecutionListController = SpringBatchJobExecutionListController;
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    SpringBatch.templatePath = 'app/springbatch/html/';
    SpringBatch.pluginName = 'SpringBatch';

    angular.module(SpringBatch.pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'hawtio-ui']).config(function ($routeProvider) {
        $routeProvider.when('/springbatch/servers', { templateUrl: SpringBatch.templatePath + 'serverList.html' }).when('/springbatch/jobs', { templateUrl: SpringBatch.templatePath + 'jobs.html' }).when('/springbatch/jobs/:jobName/executions', { templateUrl: SpringBatch.templatePath + 'overview.html' }).when('/springbatch/jobs/:jobName/executions/:jobInstanceId', { templateUrl: SpringBatch.templatePath + 'overview.html' }).when('/springbatch/jobs/executions', { templateUrl: SpringBatch.templatePath + 'jobsExecutionList.html' }).when('/springbatch/connect', { templateUrl: SpringBatch.templatePath + 'connectSpringBatch.html' }).when('/springbatch/jobs/:jobId/executions/:jobName/:jobExecutionId', { templateUrl: SpringBatch.templatePath + 'jobExecutionContext.html' }).when('/springbatch/jobs/:jobName/:jobId/history/executions', { templateUrl: SpringBatch.templatePath + 'executionHistory.html' }).when('/springbatch/jobs/:jobId/executions/:jobName/:jobExecutionId/steps/:stepExecutionId', { templateUrl: SpringBatch.templatePath + 'stepExecutionContext.html' }).when('/springbatch/jobs/:host/:port/:serverSuffix', { templateUrl: SpringBatch.templatePath + 'jobs.html' }).when('/springbatch/jobs/:host/:port', { templateUrl: SpringBatch.templatePath + 'jobs.html' });
    }).value('ui.config', {
        // The ui-jq directive namespace
        jq: {
            gridster: {
                widget_margins: [10, 10],
                widget_base_dimensions: [140, 140]
            }
        }
    }).run(function ($location, workspace, viewRegistry, $rootScope, $resource) {
        viewRegistry['springbatch'] = 'app/springbatch/html/layoutSpringBatch.html';

        workspace.topLevelTabs.push({
            id: "springbatch",
            content: "SpringBatch",
            title: "View Spring-Batch jobs",
            // TODO: Need a way to figure out if spring batch is in the JVM
            isValid: function (workspace) {
                return false;
            },
            href: function () {
                return "#/springbatch/servers";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("springbatch");
            }
        });

        // TODO: server list should not be hardcoded
        var serverListRes = $resource('/hawtio/springBatch');
        serverListRes.get(function (data) {
            $rootScope.springBatchServerList = data.springBatchServerList || [
                'localhost\\:8080/spring-batch-admin-sample/',
                'localhost\\:8181/'
            ];
            //                $rootScope.springBatchServer = $rootScope.springBatchServerList[0];
        });

        $rootScope.proxyUrl = '/hawtio/proxy/';

        $rootScope.alert = {
            enable: false,
            content: '',
            type: '',
            hide: function () {
                this.enable = false;
            },
            show: function () {
                this.enable = true;
            }
        };
    });

    hawtioPluginLoader.addModule(SpringBatch.pluginName);
})(SpringBatch || (SpringBatch = {}));
var SpringBatch;
(function (SpringBatch) {
    function getHost(link) {
        var endIdx;
        if (link.indexOf('\\') >= 0)
            endIdx = link.indexOf('\\');
        else
            endIdx = link.indexOf(':');
        return link.substring(0, endIdx);
    }
    SpringBatch.getHost = getHost;

    function getPort(link) {
        return link.substring(link.indexOf(':') + 1, link.indexOf('/'));
    }
    SpringBatch.getPort = getPort;

    function getServerSuffix(link) {
        if (link.indexOf('/') != link.lastIndexOf('/'))
            return link.substring(link.indexOf('/') + 1, link.lastIndexOf('/'));
        else
            return '';
    }
    SpringBatch.getServerSuffix = getServerSuffix;

    function getServerUrl(host, port, path) {
        var server = '';
        server = host + '\\:' + port;
        if (path) {
            if (path.charAt(0) != '/')
                server = server + '/' + path;
            else
                server = server + path;
        }
        if (server.charAt(server.length - 1) != '/') {
            server = server + '/';
        }
        return server;
    }
    SpringBatch.getServerUrl = getServerUrl;
})(SpringBatch || (SpringBatch = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var JSPlumb = (function () {
        function JSPlumb() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                // Whether or not each node in the graph can be dragged around
                var enableDragging = true;
                if (angular.isDefined($attrs['draggable'])) {
                    enableDragging = Core.parseBooleanValue($attrs['draggable']);
                }

                var useLayout = true;
                if (angular.isDefined($attrs['layout'])) {
                    useLayout = Core.parseBooleanValue($attrs['layout']);
                }

                var timeout = 100;
                if (angular.isDefined($attrs['timeout'])) {
                    timeout = Core.parseIntValue($attrs['timeout'], "timeout");
                }

                var endpointStyle = ["Dot", { radius: 10, cssClass: 'jsplumb-circle', hoverClass: 'jsplumb-circle-hover' }];
                var labelStyles = ["Label"];
                var arrowStyles = [
                    "Arrow", {
                        location: 1,
                        id: "arrow",
                        length: 8,
                        width: 8,
                        foldback: 0.8
                    }];

                var connectorStyle = ["Flowchart", { cornerRadius: 4, gap: 8 }];

                if (angular.isDefined($scope.connectorStyle)) {
                    connectorStyle = $scope.connectorStyle;
                }

                // Given an element, create a node data structure
                var createNode = function (nodeEl) {
                    var el = $(nodeEl);
                    var id = el.attr('id');
                    var anchors = el.attr('anchors');
                    if (anchors.has("{{") || anchors.has("}}")) {
                        // we don't want to add this yet...
                        return null;
                    }
                    if (anchors) {
                        anchors = anchors.split(',').map(function (anchor) {
                            return anchor.trim();
                        });
                    } else {
                        anchors = ["Top"];
                    }

                    var node = {
                        id: id,
                        label: 'node ' + id,
                        el: el,
                        width: el.outerWidth(),
                        height: el.outerHeight(),
                        edges: [],
                        connections: [],
                        endpoints: [],
                        anchors: anchors
                    };

                    return node;
                };

                var createEndpoint = function (jsPlumb, node) {
                    var endpoint = jsPlumb.addEndpoint(node.el, {
                        isSource: true,
                        isTarget: true,
                        anchor: node.anchors,
                        connector: connectorStyle,
                        maxConnections: -1
                    });
                    node.endpoints.push(endpoint);

                    //$scope.jsPlumbEndpoints[node.id] = endpoint
                    if (enableDragging) {
                        jsPlumb.draggable(node.el, {
                            containment: $element
                        });
                    }
                };

                var nodes = [];
                var transitions = [];
                var nodesById = {};

                var gatherElements = function () {
                    var nodeEls = $element.find('.jsplumb-node');

                    angular.forEach(nodeEls, function (nodeEl) {
                        if (!nodesById[nodeEl.id]) {
                            var node = createNode(nodeEl);
                            if (node) {
                                nodes.push(node);
                                nodesById[node.id] = node;
                            }
                        }
                    });

                    angular.forEach(nodes, function (sourceNode) {
                        var targets = sourceNode.el.attr('connect-to');
                        if (targets) {
                            targets = targets.split(',');
                            angular.forEach(targets, function (target) {
                                var targetNode = nodesById[target.trim()];
                                if (targetNode) {
                                    var edge = {
                                        source: sourceNode,
                                        target: targetNode
                                    };
                                    transitions.push(edge);
                                    sourceNode.edges.push(edge);
                                    targetNode.edges.push(edge);
                                }
                            });
                        }
                    });
                };

                /*
                $element.bind('DOMNodeInserted', (event) => {
                if ($scope.jsPlumb) {
                if (angular.isString(event.target.className)
                && !event.target.className.has("_jsPlumb_endpoint_anchor_")
                && event.target.className.has("jsplumb-node")) {
                // TODO - handle added nodes here, like from ng-repeat for example
                //console.log("DOMNodeInserted: ", event);
                gatherElements();
                var newNodes = nodes.filter((node) => { return node.endpoints.isEmpty(); });
                if (newNodes && newNodes.length) {
                angular.forEach(newNodes, (node) => {
                //console.log("Adding node: ", node.id);
                createEndpoint($scope.jsPlumb, node);
                });
                $scope.jsPlumb.repaintEverything();
                Core.$applyLater($scope);
                }
                }
                }
                });
                */
                // Kick off the initial layout of elements in the container
                setTimeout(function () {
                    $scope.jsPlumb = jsPlumb.getInstance({
                        Container: $element
                    });

                    $scope.jsPlumb.importDefaults({
                        Anchor: "AutoDefault",
                        Connector: "Flowchart",
                        ConnectorStyle: connectorStyle,
                        DragOptions: { cursor: "pointer", zIndex: 2000 },
                        Endpoint: endpointStyle,
                        PaintStyle: { strokeStyle: "#42a62c", lineWidth: 4 },
                        HoverPaintStyle: { strokeStyle: "#42a62c", lineWidth: 4 },
                        ConnectionOverlays: [
                            arrowStyles,
                            labelStyles
                        ]
                    });

                    gatherElements();

                    $scope.jsPlumbNodes = nodes;
                    $scope.jsPlumbNodesById = nodesById;
                    $scope.jsPlumbTransitions = transitions;

                    //$scope.jsPlumbEndpoints = {};
                    //$scope.jsPlumbConnections = [];
                    // First we'll lay out the graph and then later apply jsplumb to all
                    // of the nodes and connections
                    if (useLayout) {
                        $scope.layout = dagre.layout().nodeSep(50).edgeSep(10).rankSep(50).nodes(nodes).edges(transitions).debugLevel(1).run();
                    }

                    angular.forEach($scope.jsPlumbNodes, function (node) {
                        if (useLayout) {
                            node.el.css({ top: node.dagre.y, left: node.dagre.x });
                        }
                        createEndpoint($scope.jsPlumb, node);
                    });

                    angular.forEach($scope.jsPlumbTransitions, function (edge) {
                        var connection = $scope.jsPlumb.connect({
                            source: edge.source.el,
                            target: edge.target.el
                        }, {
                            connector: connectorStyle,
                            maxConnections: -1
                        });
                        edge.source.connections.push(connection);
                        edge.target.connections.push(connection);
                        //$scope.jsPlumbConnections.push(connection);
                    });

                    $scope.jsPlumb.recalculateOffsets($element);
                    $scope.jsPlumb.repaintEverything();

                    if (angular.isDefined($scope.jsPlumbCallback) && angular.isFunction($scope.jsPlumbCallback)) {
                        $scope.jsPlumbCallback($scope.jsPlumb, $scope.jsPlumbNodes, $scope.jsPlumbNodesById, $scope.jsPlumbTransitions);
                    }

                    Core.$apply($scope);
                }, timeout);
            };
        }
        return JSPlumb;
    })();
    UI.JSPlumb = JSPlumb;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var TablePager = (function () {
        function TablePager() {
            var _this = this;
            this.restrict = 'A';
            this.scope = true;
            this.templateUrl = UI.templatePath + 'tablePager.html';
            this.$scope = null;
            this.element = null;
            this.attrs = null;
            this.tableName = null;
            this.setRowIndexName = null;
            this.rowIndexName = null;
            // necessary to ensure 'this' is this object <sigh>
            this.link = function (scope, element, attrs) {
                return _this.doLink(scope, element, attrs);
            };
        }
        TablePager.prototype.doLink = function (scope, element, attrs) {
            var _this = this;
            this.$scope = scope;
            this.element = element;
            this.attrs = attrs;
            this.tableName = attrs["hawtioPager"] || attrs["array"] || "data";
            this.setRowIndexName = attrs["onIndexChange"] || "onIndexChange";
            this.rowIndexName = attrs["rowIndex"] || "rowIndex";

            scope.first = function () {
                _this.goToIndex(0);
            };

            scope.last = function () {
                _this.goToIndex(scope.tableLength() - 1);
            };

            scope.previous = function () {
                _this.goToIndex(scope.rowIndex() - 1);
            };

            scope.next = function () {
                _this.goToIndex(scope.rowIndex() + 1);
            };

            scope.isEmptyOrFirst = function () {
                var idx = scope.rowIndex();
                var length = scope.tableLength();
                return length <= 0 || idx <= 0;
            };

            scope.isEmptyOrLast = function () {
                var idx = scope.rowIndex();
                var length = scope.tableLength();
                return length < 1 || idx + 1 >= length;
            };

            scope.rowIndex = function () {
                return Core.pathGet(scope.$parent, _this.rowIndexName.split('.'));
            };

            scope.tableLength = function () {
                var data = _this.tableData();
                return data ? data.length : 0;
            };
        };

        TablePager.prototype.tableData = function () {
            return Core.pathGet(this.$scope.$parent, this.tableName.split('.')) || [];
        };

        TablePager.prototype.goToIndex = function (idx) {
            var name = this.setRowIndexName;
            var fn = this.$scope[name];
            if (angular.isFunction(fn)) {
                fn(idx);
            } else {
                console.log("No function defined in scope for " + name + " but was " + fn);
                this.$scope[this.rowIndexName] = idx;
            }
        };
        return TablePager;
    })();
    UI.TablePager = TablePager;
})(UI || (UI = {}));
var UI;
(function (UI) {
    function groupBy() {
        return function (list, group) {
            if (list.length === 0) {
                return list;
            }

            if (Core.isBlank(group)) {
                return list;
            }

            var newGroup = 'newGroup';

            var currentGroup = list.first()[group];
            list.first()[newGroup] = true;

            list.forEach(function (item) {
                if (item[group] !== currentGroup) {
                    item[newGroup] = true;
                    currentGroup = item[group];
                }
            });

            return list;
        };
    }
    UI.groupBy = groupBy;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    /**
    * TODO turn this into a normal directive function
    *
    * @property AutoDropDown
    * @type IAutoDropDown
    */
    UI.AutoDropDown = {
        restrict: 'A',
        link: function ($scope, $element, $attrs) {
            function locateElements(event) {
                var el = $element.get(0);
                if (event && event.relatedNode !== el && event.type) {
                    if (event && event.type !== 'resize') {
                        return;
                    }
                }

                var overflowEl = $($element.find('.overflow'));
                var overflowMenu = $(overflowEl.find('ul.dropdown-menu'));

                /*
                Logger.info("element inner width: ", $element.innerWidth());
                Logger.info("element position: ", $element.position());
                Logger.info("element offset: ", $element.offset());
                Logger.info("overflowEl offset: ", overflowEl.offset());
                Logger.info("overflowEl position: ", overflowEl.position());
                */
                var margin = 0;
                var availableWidth = 0;

                try  {
                    margin = overflowEl.outerWidth() - overflowEl.innerWidth();
                    availableWidth = overflowEl.position().left - $element.position().left - 50;
                } catch (e) {
                    UI.log.debug("caught " + e);
                }

                $element.children('li:not(.overflow):not(.pull-right):not(:hidden)').each(function () {
                    var self = $(this);
                    availableWidth = availableWidth - self.outerWidth(true);
                    if (availableWidth < 0) {
                        self.detach();
                        self.prependTo(overflowMenu);
                    }
                });

                if (overflowMenu.children().length > 0) {
                    overflowEl.css({ visibility: "visible" });
                }

                if (availableWidth > 130) {
                    var noSpace = false;

                    overflowMenu.children('li:not(.overflow):not(.pull-right)').filter(function () {
                        return $(this).css('display') !== 'none';
                    }).each(function () {
                        if (noSpace) {
                            return;
                        }
                        var self = $(this);

                        if (availableWidth > self.outerWidth()) {
                            availableWidth = availableWidth - self.outerWidth();
                            self.detach();
                            self.insertBefore(overflowEl);
                        } else {
                            noSpace = true;
                        }
                    });
                }

                if (overflowMenu.children().length === 0) {
                    overflowEl.css({ visibility: "hidden" });
                }
            }

            $(window).resize(locateElements);
            $element.get(0).addEventListener("DOMNodeInserted", locateElements);
            $scope.$watch(setTimeout(locateElements, 500));
        }
    };
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function Editor($parse) {
        return {
            restrict: 'A',
            replace: true,
            templateUrl: UI.templatePath + "editor.html",
            scope: {
                text: '=hawtioEditor',
                mode: '=',
                outputEditor: '@',
                name: '@'
            },
            controller: function ($scope, $element, $attrs) {
                $scope.codeMirror = null;
                $scope.doc = null;
                $scope.options = [];

                UI.observe($scope, $attrs, 'name', 'editor');

                $scope.applyOptions = function () {
                    if ($scope.codeMirror) {
                        $scope.options.each(function (option) {
                            $scope.codeMirror.setOption(option.key, option['value']);
                        });
                        $scope.options = [];
                    }
                };

                $scope.$watch('doc', function () {
                    if ($scope.doc) {
                        $scope.codeMirror.on('change', function (changeObj) {
                            $scope.text = $scope.doc.getValue();
                            $scope.dirty = !$scope.doc.isClean();
                            Core.$apply($scope);
                        });
                    }
                });

                $scope.$watch('codeMirror', function () {
                    if ($scope.codeMirror) {
                        $scope.doc = $scope.codeMirror.getDoc();
                    }
                });

                $scope.$watch('text', function (oldValue, newValue) {
                    if ($scope.codeMirror && $scope.doc) {
                        if (!$scope.codeMirror.hasFocus()) {
                            $scope.doc.setValue($scope.text || "");
                        }
                    }
                });
            },
            link: function ($scope, $element, $attrs) {
                if ('dirty' in $attrs) {
                    $scope.dirtyTarget = $attrs['dirty'];
                    $scope.$watch("$parent['" + $scope.dirtyTarget + "']", function (newValue, oldValue) {
                        if (newValue !== oldValue) {
                            $scope.dirty = newValue;
                        }
                    });
                }

                var config = Object.extended($attrs).clone();

                delete config['$$element'];
                delete config['$attr'];
                delete config['class'];
                delete config['hawtioEditor'];
                delete config['mode'];
                delete config['dirty'];
                delete config['outputEditor'];

                if ('onChange' in $attrs) {
                    var onChange = $attrs['onChange'];
                    delete config['onChange'];
                    $scope.options.push({
                        onChange: function (codeMirror) {
                            var func = $parse(onChange);
                            if (func) {
                                func($scope.$parent, { codeMirror: codeMirror });
                            }
                        }
                    });
                }

                angular.forEach(config, function (value, key) {
                    $scope.options.push({
                        key: key,
                        'value': value
                    });
                });

                $scope.$watch('mode', function () {
                    if ($scope.mode) {
                        if (!$scope.codeMirror) {
                            $scope.options.push({
                                key: 'mode',
                                'value': $scope.mode
                            });
                        } else {
                            $scope.codeMirror.setOption('mode', $scope.mode);
                        }
                    }
                });

                $scope.$watch('dirty', function (newValue, oldValue) {
                    if ($scope.dirty && !$scope.doc.isClean()) {
                        $scope.doc.markClean();
                    }
                    if (newValue !== oldValue && 'dirtyTarget' in $scope) {
                        $scope.$parent[$scope.dirtyTarget] = $scope.dirty;
                    }
                });

                $scope.$watch(function () {
                    return $element.is(':visible');
                }, function (newValue, oldValue) {
                    if (newValue !== oldValue && $scope.codeMirror) {
                        $scope.codeMirror.refresh();
                    }
                });

                $scope.$watch('text', function () {
                    if (!$scope.codeMirror) {
                        var options = {
                            value: $scope.text
                        };

                        options = CodeEditor.createEditorSettings(options);
                        $scope.codeMirror = CodeMirror.fromTextArea($element.find('textarea').get(0), options);
                        var outputEditor = $scope.outputEditor;
                        if (outputEditor) {
                            var outputScope = $scope.$parent || $scope;
                            Core.pathSet(outputScope, outputEditor, $scope.codeMirror);
                        }
                        $scope.applyOptions();
                    }
                });
            }
        };
    }
    UI.Editor = Editor;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var ViewportHeight = (function () {
        function ViewportHeight() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                var lastHeight = 0;

                var resizeFunc = function () {
                    var neighbor = angular.element($attrs['hawtioViewport']);
                    var container = angular.element($attrs['containingDiv']);

                    var start = neighbor.position().top + neighbor.height();

                    var myHeight = container.height() - start;
                    if (angular.isDefined($attrs['heightAdjust'])) {
                        var heightAdjust = $attrs['heightAdjust'].toNumber();
                    }
                    myHeight = myHeight + heightAdjust;

                    $element.css({
                        height: myHeight,
                        'min-height': myHeight
                    });

                    if (lastHeight !== myHeight) {
                        lastHeight = myHeight;
                        $element.trigger('resize');
                    }
                };

                resizeFunc();
                $scope.$watch(resizeFunc);

                $().resize(function () {
                    resizeFunc();
                    Core.$apply($scope);
                    return false;
                });
            };
        }
        return ViewportHeight;
    })();
    UI.ViewportHeight = ViewportHeight;

    var HorizontalViewport = (function () {
        function HorizontalViewport() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                var adjustParent = angular.isDefined($attrs['adjustParent']) && Core.parseBooleanValue($attrs['adjustParent']);

                $element.get(0).addEventListener("DOMNodeInserted", function () {
                    var canvas = $element.children();
                    $element.height(canvas.outerHeight(true));
                    if (adjustParent) {
                        $element.parent().height($element.outerHeight(true) + UI.getScrollbarWidth());
                    }
                });
            };
        }
        return HorizontalViewport;
    })();
    UI.HorizontalViewport = HorizontalViewport;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    // expand the element to accomodate a group of elements to prevent them from wrapping
    var DivRow = (function () {
        function DivRow() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                $element.get(0).addEventListener("DOMNodeInserted", function () {
                    var targets = $element.children();
                    var width = 0;
                    angular.forEach(targets, function (target) {
                        var el = angular.element(target);
                        switch (el.css('display')) {
                            case 'none':
                                break;
                            default:
                                width = width + el.outerWidth(true) + 5;
                        }
                    });
                    $element.width(width);
                });
            };
        }
        return DivRow;
    })();
    UI.DivRow = DivRow;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function hawtioBreadcrumbs() {
        return {
            restrict: 'E',
            replace: true,
            templateUrl: UI.templatePath + 'breadcrumbs.html',
            require: 'hawtioDropDown',
            scope: {
                config: '='
            },
            controller: function ($scope, $element, $attrs) {
                $scope.action = "itemClicked(config, $event)";

                $scope.levels = {};

                $scope.itemClicked = function (config, $event) {
                    //log.debug("Item clicked: ", config);
                    if (config.level && angular.isNumber(config.level)) {
                        $scope.levels[config.level] = config;

                        var keys = Object.extended($scope.levels).keys().sortBy("");
                        var toRemove = keys.from(config.level + 1);

                        toRemove.forEach(function (i) {
                            if (i in $scope.levels) {
                                $scope.levels[i] = {};
                                delete $scope.levels[i];
                            }
                        });

                        // reset any previously deleted action
                        angular.forEach($scope.levels, function (value, key) {
                            if (value.items && value.items.length > 0) {
                                value.items.forEach(function (i) {
                                    //log.debug("Resetting action: ", i);
                                    i['action'] = $scope.action;
                                });
                            }
                        });
                        if (config.items) {
                            config.open = true;
                            config.items.forEach(function (i) {
                                i['action'] = $scope.action;
                            });
                            delete config.action;
                        } else {
                            //ooh we picked a thing!
                            var keys = Object.extended($scope.levels).keys().sortBy("");
                            var path = [];
                            keys.forEach(function (key) {
                                path.push($scope.levels[key]['title']);
                            });
                            var pathString = '/' + path.join("/");
                            $scope.config.path = pathString;
                        }

                        // for some reason levels > 1 get two click events :-S
                        if (config.level > 1) {
                            $event.preventDefault();
                            $event.stopPropagation();
                        }
                    }
                };

                function addAction(config, level) {
                    config.level = level;
                    if (level > 0) {
                        config.breadcrumbAction = config.action;
                        config.action = $scope.action;
                    }
                    if (config.items) {
                        config.items.forEach(function (item) {
                            addAction(item, level + 1);
                        });
                    }
                }

                function setLevels(config, pathParts, level) {
                    if (pathParts.length === 0) {
                        return;
                    }
                    var part = pathParts.removeAt(0)[0];

                    //log.debug("config: ", config, " checking part: ", part, " pathParts: ", pathParts);
                    if (config && config.items) {
                        var matched = false;
                        config.items.forEach(function (item) {
                            //log.debug("checking item: ", item, " against part: ", part);
                            if (!matched && item['title'] === part) {
                                //log.debug("Found match");
                                matched = true;
                                $scope.levels[level] = item;
                                setLevels(item, pathParts, level + 1);
                            }
                        });
                    }
                }

                // watch to see if the parent scope changes the path
                $scope.$watch('config.path', function (newValue, oldValue) {
                    if (!Core.isBlank(newValue)) {
                        var pathParts = newValue.split('/').exclude(function (p) {
                            return Core.isBlank(p);
                        });

                        //log.debug("path: ", newValue);
                        //log.debug("pathParts: ", pathParts);
                        var matches = true;
                        pathParts.forEach(function (part, index) {
                            //log.debug("Checking part: ", part, " index: ", index)
                            if (!matches) {
                                return;
                            }
                            if (!$scope.levels[index] || Core.isBlank($scope.levels[index]['title']) || $scope.levels[index]['title'] !== part) {
                                matches = false;
                            }
                        });

                        //log.debug("matches: ", matches);
                        if (matches) {
                            return;
                        }

                        // adjust $scope.levels to match the path
                        $scope.levels = [];
                        $scope.levels['0'] = $scope.config;
                        setLevels($scope.config, pathParts.from(0), 1);
                    }
                });

                $scope.$watch('config', function (newValue, oldValue) {
                    addAction($scope.config, 0);
                    $scope.levels['0'] = $scope.config;
                });
            }
        };
    }
    UI.hawtioBreadcrumbs = hawtioBreadcrumbs;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    UI.log = Logger.get("UI");

    UI.scrollBarWidth = null;

    function getIfSet(attribute, $attr, def) {
        if (attribute in $attr) {
            var wantedAnswer = $attr[attribute];
            if (wantedAnswer && !wantedAnswer.isBlank()) {
                return wantedAnswer;
            }
        }
        return def;
    }
    UI.getIfSet = getIfSet;

    /*
    * Helper function to ensure a directive attribute has some default value
    */
    function observe($scope, $attrs, key, defValue, callbackFunc) {
        if (typeof callbackFunc === "undefined") { callbackFunc = null; }
        $attrs.$observe(key, function (value) {
            if (!angular.isDefined(value)) {
                $scope[key] = defValue;
            } else {
                $scope[key] = value;
            }
            if (angular.isDefined(callbackFunc) && callbackFunc) {
                callbackFunc($scope[key]);
            }
        });
    }
    UI.observe = observe;

    function getScrollbarWidth() {
        if (!angular.isDefined(UI.scrollBarWidth)) {
            var div = document.createElement('div');
            div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
            div = div.firstChild;
            document.body.appendChild(div);
            UI.scrollBarWidth = div.offsetWidth - div.clientWidth;
            document.body.removeChild(div);
        }
        return UI.scrollBarWidth;
    }
    UI.getScrollbarWidth = getScrollbarWidth;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var MessagePanel = (function () {
        function MessagePanel() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                var height = "100%";
                if ('hawtioMessagePanel' in $attrs) {
                    var wantedHeight = $attrs['hawtioMessagePanel'];
                    if (wantedHeight && !wantedHeight.isBlank()) {
                        height = wantedHeight;
                    }
                }

                var speed = "1s";
                if ('speed' in $attrs) {
                    var wantedSpeed = $attrs['speed'];
                    if (speed && !speed.isBlank()) {
                        speed = wantedSpeed;
                    }
                }

                $element.css({
                    position: 'absolute',
                    bottom: 0,
                    height: 0,
                    'min-height': 0,
                    transition: 'all ' + speed + ' ease-in-out'
                });

                $element.parent().mouseover(function () {
                    $element.css({
                        height: height,
                        'min-height': 'auto'
                    });
                });

                $element.parent().mouseout(function () {
                    $element.css({
                        height: 0,
                        'min-height': 0
                    });
                });
            };
        }
        return MessagePanel;
    })();
    UI.MessagePanel = MessagePanel;

    var InfoPanel = (function () {
        function InfoPanel() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attrs) {
                var validDirections = {
                    'left': {
                        side: 'right',
                        out: 'width'
                    },
                    'right': {
                        side: 'left',
                        out: 'width'
                    },
                    'up': {
                        side: 'bottom',
                        out: 'height'
                    },
                    'down': {
                        side: 'top',
                        out: 'height'
                    }
                };

                var direction = "right";
                if ('hawtioInfoPanel' in $attrs) {
                    var wantedDirection = $attrs['hawtioInfoPanel'];
                    if (wantedDirection && !wantedDirection.isBlank()) {
                        if (Object.extended(validDirections).keys().any(wantedDirection)) {
                            direction = wantedDirection;
                        }
                    }
                }

                var speed = "1s";
                if ('speed' in $attrs) {
                    var wantedSpeed = $attrs['speed'];
                    if (speed && !speed.isBlank()) {
                        speed = wantedSpeed;
                    }
                }

                var toggle = "open";
                if ('toggle' in $attrs) {
                    var wantedToggle = $attrs['toggle'];
                    if (toggle && !toggle.isBlank()) {
                        toggle = wantedToggle;
                    }
                }

                var initialCss = {
                    position: 'absolute',
                    transition: 'all ' + speed + ' ease-in-out'
                };

                var openCss = {};
                openCss[validDirections[direction]['out']] = '100%';
                var closedCss = {};
                closedCss[validDirections[direction]['out']] = 0;

                initialCss[validDirections[direction]['side']] = 0;
                initialCss[validDirections[direction]['out']] = 0;

                $element.css(initialCss);

                $scope.$watch(toggle, function (newValue, oldValue) {
                    if (Core.parseBooleanValue(newValue)) {
                        $element.css(openCss);
                    } else {
                        $element.css(closedCss);
                    }
                });

                $element.click(function () {
                    $scope[toggle] = false;
                    Core.$apply($scope);
                });
            };
        }
        return InfoPanel;
    })();
    UI.InfoPanel = InfoPanel;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    /**
    * Directive class that organizes child elements into columns automatically
    *
    * @class AutoColumns
    */
    var AutoColumns = (function () {
        function AutoColumns() {
            this.restrict = 'A';
            this.link = function ($scope, $element, $attr) {
                var selector = UI.getIfSet('hawtioAutoColumns', $attr, 'div');
                var minMargin = UI.getIfSet('minMargin', $attr, '3').toNumber();

                var go = function () {
                    var containerWidth = $element.innerWidth();
                    var childWidth = 0;

                    var children = $element.children(selector);

                    if (children.length === 0) {
                        UI.log.debug("No children, skipping calculating column margins");
                        return;
                    }

                    // find the biggest child, though really they should all be the same size...
                    children.each(function (child) {
                        var self = $(this);
                        if (!self.is(':visible')) {
                            return;
                        }
                        if (self.outerWidth() > childWidth) {
                            childWidth = self.outerWidth();
                        }
                    });

                    if (childWidth === 0) {
                        return;
                    }

                    childWidth = childWidth + (minMargin * 2);

                    var columns = Math.floor(containerWidth / childWidth);
                    if (children.length < columns) {
                        columns = children.length;
                    }
                    var margin = (containerWidth - (columns * childWidth)) / columns / 2;

                    //log.debug("child width: ", childWidth);
                    //log.debug("Inner width: ", containerWidth);
                    //log.debug("columns: ", columns);
                    //log.debug("margin: ", margin);
                    children.each(function (child) {
                        $(this).css({
                            'margin-left': margin,
                            'margin-right': margin
                        });
                    });
                };

                setTimeout(go, 300);
                $scope.$watch(go);
                $(window).resize(go);
            };
        }
        return AutoColumns;
    })();
    UI.AutoColumns = AutoColumns;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    /**
    * Simple helper class for creating <a href="http://angular-ui.github.io/bootstrap/#/modal">angular ui bootstrap modal dialogs</a>
    * @class Dialog
    */
    var Dialog = (function () {
        function Dialog() {
            this.show = false;
            this.options = {
                backdropFade: true,
                dialogFade: true
            };
        }
        /**
        * Opens the dialog
        * @method open
        */
        Dialog.prototype.open = function () {
            this.show = true;
        };

        /**
        * Closes the dialog
        * @method close
        */
        Dialog.prototype.close = function () {
            this.show = false;

            // lets make sure and remove any backgroup fades
            this.removeBackdropFadeDiv();
            setTimeout(this.removeBackdropFadeDiv, 100);
        };

        Dialog.prototype.removeBackdropFadeDiv = function () {
            $("div.modal-backdrop").remove();
        };
        return Dialog;
    })();
    UI.Dialog = Dialog;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function UITestController2($scope, $templateCache) {
        $scope.fileUploadExMode = 'text/html';

        $scope.menuItems = [];
        $scope.divs = [];

        for (var i = 0; i < 20; i++) {
            $scope.menuItems.push("Some Item " + i);
        }

        for (var i = 0; i < 20; i++) {
            $scope.divs.push(i + 1);
        }

        $scope.things = [
            {
                'name': 'stuff1',
                'foo1': 'bar1',
                'foo2': 'bar2'
            },
            {
                'name': 'stuff2',
                'foo3': 'bar3',
                'foo4': 'bar4'
            }
        ];

        $scope.someVal = 1;

        $scope.dropDownConfig = {
            icon: 'icon-cogs',
            title: 'My Awesome Menu',
            items: [
                {
                    title: 'Some Item',
                    action: 'someVal=2'
                }, {
                    title: 'Some other stuff',
                    icon: 'icon-twitter',
                    action: 'someVal=3'
                }, {
                    title: "I've got children",
                    icon: 'icon-file-text',
                    items: [
                        {
                            title: 'Hi!',
                            action: 'someVal=4'
                        }, {
                            title: 'Yo!',
                            items: [
                                {
                                    title: 'More!',
                                    action: 'someVal=5'
                                }, {
                                    title: 'Child',
                                    action: 'someVal=6'
                                }, {
                                    title: 'Menus!',
                                    action: 'someVal=7'
                                }]
                        }]
                }, {
                    title: "Call a function!",
                    action: function () {
                        notification("info", "Function called!");
                    }
                }]
        };
        $scope.dropDownConfigTxt = angular.toJson($scope.dropDownConfig, true);

        $scope.$watch('dropDownConfigTxt', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.dropDownConfig = angular.fromJson($scope.dropDownConfigTxt);
            }
        });

        $scope.breadcrumbSelection = 1;

        $scope.breadcrumbConfig = {
            path: '/root/first child',
            icon: 'icon-cogs',
            title: 'root',
            items: [
                {
                    title: 'first child',
                    icon: 'icon-folder-close-alt',
                    items: [{
                            title: "first child's first child",
                            icon: 'icon-file-text'
                        }]
                }, {
                    title: 'second child',
                    icon: 'icon-file'
                }, {
                    title: "third child",
                    icon: 'icon-folder-close-alt',
                    items: [
                        {
                            title: "third child's first child",
                            icon: 'icon-file-text'
                        }, {
                            title: "third child's second child",
                            icon: 'icon-file-text'
                        }, {
                            title: "third child's third child",
                            icon: 'icon-folder-close-alt',
                            items: [
                                {
                                    title: 'More!',
                                    icon: 'icon-file-text'
                                }, {
                                    title: 'Child',
                                    icon: 'icon-file-text'
                                }, {
                                    title: 'Menus!',
                                    icon: 'icon-file-text'
                                }]
                        }]
                }]
        };

        $scope.breadcrumbConfigTxt = angular.toJson($scope.breadcrumbConfig, true);

        $scope.$watch('breadcrumbConfigTxt', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.breadcrumbconfig = angular.toJson($scope.breadcrumbConfigTxt);
            }
        });

        $scope.breadcrumbEx = $templateCache.get("breadcrumbTemplate");

        $scope.dropDownEx = $templateCache.get("dropDownTemplate");

        $scope.autoDropDown = $templateCache.get("autoDropDownTemplate");
        $scope.zeroClipboard = $templateCache.get("zeroClipboardTemplate");

        $scope.popoverEx = $templateCache.get("myTemplate");
        $scope.popoverUsageEx = $templateCache.get("popoverExTemplate");

        $scope.autoColumnEx = $templateCache.get("autoColumnTemplate");
    }
    UI.UITestController2 = UITestController2;

    function UITestController1($scope, $templateCache) {
        $scope.jsplumbEx = $templateCache.get("jsplumbTemplate");

        $scope.nodes = ["node1", "node2"];
        $scope.otherNodes = ["node4", "node5", "node6"];

        $scope.anchors = ["Top", "Right", "Bottom", "Left"];

        $scope.createEndpoint = function (nodeId) {
            var node = $scope.jsPlumbNodesById[nodeId];
            if (node) {
                var anchors = $scope.anchors.subtract(node.anchors);
                console.log("anchors: ", anchors);
                if (anchors && anchors.length > 0) {
                    var anchor = anchors.first();
                    node.anchors.push(anchor);
                    node.endpoints.push($scope.jsPlumb.addEndpoint(node.el, {
                        anchor: anchor,
                        isSource: true,
                        isTarget: true,
                        maxConnections: -1
                    }));
                }
            }
        };

        $scope.expandableEx = '' + '<div class="expandable closed">\n' + '   <div title="The title" class="title">\n' + '     <i class="expandable-indicator"></i> Expandable title\n' + '   </div>\n' + '   <div class="expandable-body well">\n' + '     This is the expandable content...  Note that adding the "well" class isn\'t necessary but makes for a nice inset look\n' + '   </div>\n' + '</div>';

        $scope.editablePropertyEx1 = '<editable-property ng-model="editablePropertyModelEx1" property="property"></editable-property>';

        $scope.editablePropertyModelEx1 = {
            property: "This is editable (hover to edit)"
        };

        $scope.showDeleteOne = new UI.Dialog();
        $scope.showDeleteTwo = new UI.Dialog();

        $scope.fileUploadEx1 = '<div hawtio-file-upload="files" target="test1"></div>';
        $scope.fileUploadEx2 = '<div hawtio-file-upload="files" target="test2" show-files="false"></div>';
        $scope.fileUploadExMode = 'text/html';

        $scope.colorPickerEx = 'My Color ({{myColor}}): <div hawtio-color-picker="myColor"></div>';

        $scope.confirmationEx1 = '' + '<button class="btn" ng-click="showDeleteOne.open()">Delete stuff</button>\n' + '\n' + '<div hawtio-confirm-dialog="showDeleteOne.show"\n' + 'title="Delete stuff?"\n' + 'ok-button-text="Yes, Delete the Stuff"\n' + 'cancel-button-text="No, Keep the Stuff"\n' + 'on-cancel="onCancelled(\'One\')"\n' + 'on-ok="onOk(\'One\')">\n' + '  <div class="dialog-body">\n' + '    <p>\n' + '        Are you sure you want to delete all the stuff?\n' + '    </p>\n' + '  </div>\n' + '</div>\n';

        $scope.confirmationEx2 = '' + '<button class="btn" ng-click="showDeleteTwo.open()">Delete other stuff</button>\n' + '\n' + '<!-- Use more defaults -->\n' + '<div hawtio-confirm-dialog="showDeleteTwo.show\n"' + '  on-cancel="onCancelled(\'Two\')"\n' + '  on-ok="onOk(\'Two\')">\n' + '  <div class="dialog-body">\n' + '    <p>\n' + '      Are you sure you want to delete all the other stuff?\n' + '    </p>\n' + '  </div>\n' + '</div>';

        $scope.sliderEx1 = '' + '<button class="btn" ng-click="showSlideoutRight = !showSlideoutRight">Show slideout right</button>\n' + '<div hawtio-slideout="showSlideoutRight" title="Hey look a slider!">\n' + '   <div class="dialog-body">\n' + '     <div>\n' + '       Here is some content or whatever {{transcludedValue}}\n' + '     </div>\n' + '   </div>\n' + '</div>';

        $scope.sliderEx2 = '' + '<button class="btn" ng-click="showSlideoutLeft = !showSlideoutLeft">Show slideout left</button>\n' + '<div hawtio-slideout="showSlideoutLeft" direction="left" title="Hey, another slider!">\n' + '   <div class="dialog-body">\n' + '     <div hawtio-editor="someText" mode="javascript"></div>\n' + '   </div>\n' + '</div>\n';

        $scope.editorEx1 = '' + 'Instance 1\n' + '<div class="row-fluid">\n' + '   <div hawtio-editor="someText" mode="mode" dirty="dirty"></div>\n' + '   <div>Text : {{someText}}</div>\n' + '</div>\n' + '\n' + 'Instance 2 (readonly)\n' + '<div class="row-fluid">\n' + '   <div hawtio-editor="someText" read-only="true" mode="mode" dirty="dirty"></div>\n' + '   <div>Text : {{someText}}</div>\n' + '</div>';

        $scope.transcludedValue = "and this is transcluded";

        $scope.onCancelled = function (number) {
            notification('info', 'cancelled ' + number);
        };

        $scope.onOk = function (number) {
            notification('info', number + ' ok!');
        };

        $scope.showSlideoutRight = false;
        $scope.showSlideoutLeft = false;

        $scope.dirty = false;
        $scope.mode = 'javascript';

        $scope.someText = "var someValue = 0;\n" + "var someFunc = function() {\n" + "  return \"Hello World!\";\n" + "}\n";

        $scope.myColor = "#FF887C";
        $scope.showColorDialog = false;

        $scope.files = [];

        $scope.$watch('files', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                console.log("Files: ", $scope.files);
            }
        }, true);
    }
    UI.UITestController1 = UITestController1;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function HawtioTocDisplay(marked, $location, $anchorScroll, $compile) {
        var log = Logger.get("UI");

        return {
            restrict: 'A',
            scope: {
                getContents: '&'
            },
            controller: function ($scope, $element, $attrs) {
                $scope.remaining = -1;
                $scope.render = false;
                $scope.chapters = [];

                $scope.addChapter = function (item) {
                    console.log("Adding: ", item);
                    $scope.chapters.push(item);
                    if (!angular.isDefined(item['text'])) {
                        $scope.fetchItemContent(item);
                    }
                };

                $scope.getTarget = function (id) {
                    if (!id) {
                        return '';
                    }
                    return id.replace(".", "_");
                };

                $scope.getFilename = function (href, ext) {
                    var filename = href.split('/').last();
                    if (ext && !filename.endsWith(ext)) {
                        filename = filename + '.' + ext;
                    }
                    return filename;
                };

                $scope.$watch('remaining', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        var renderIfPageLoadFails = false;
                        if (newValue === 0 || renderIfPageLoadFails) {
                            $scope.render = true;
                        }
                    }
                });

                $scope.fetchItemContent = function (item) {
                    var me = $scope;
                    $scope.$eval(function (parent) {
                        parent.getContents({
                            filename: item['filename'],
                            cb: function (data) {
                                if (data) {
                                    if (item['filename'].endsWith(".md")) {
                                        item['text'] = marked(data);
                                    } else {
                                        item['text'] = data;
                                    }
                                    $scope.remaining--;
                                    Core.$apply(me);
                                }
                            }
                        });
                    });
                };
            },
            link: function ($scope, $element, $attrs) {
                var offsetTop = 0;
                var logbar = $('.logbar');
                var contentDiv = $("#toc-content");
                if (logbar.length) {
                    offsetTop = logbar.height() + logbar.offset().top;
                } else if (contentDiv.length) {
                    var offsetContentDiv = contentDiv.offset();
                    if (offsetContentDiv) {
                        offsetTop = offsetContentDiv.top;
                    }
                }
                if (!offsetTop) {
                    // set to a decent guestimate
                    offsetTop = 90;
                }
                var previousHtml = null;
                var html = $element;
                if (!contentDiv || !contentDiv.length) {
                    contentDiv = $element;
                }
                var ownerScope = $scope.$parent || $scope;
                var scrollDuration = 1000;

                var linkFilter = $attrs["linkFilter"];
                var htmlName = $attrs["html"];
                if (htmlName) {
                    ownerScope.$watch(htmlName, function () {
                        var htmlText = ownerScope[htmlName];
                        if (htmlText && htmlText !== previousHtml) {
                            previousHtml = htmlText;
                            var markup = $compile(htmlText)(ownerScope);
                            $element.children().remove();
                            $element.append(markup);
                            loadChapters();
                        }
                    });
                } else {
                    loadChapters();
                }

                // make the link active for the first panel on the view
                $(window).scroll(setFirstChapterActive);

                function setFirstChapterActive() {
                    // lets find the first panel which is visible...
                    var cutoff = $(window).scrollTop();
                    $element.find("li a").removeClass("active");
                    $('.panel-body').each(function () {
                        var offset = $(this).offset();
                        if (offset && offset.top >= cutoff) {
                            // lets make the related TOC link active
                            var id = $(this).attr("id");
                            if (id) {
                                var link = html.find("a[chapter-id='" + id + "']");
                                link.addClass("active");

                                // stop iterating and just make first one active
                                return false;
                            }
                        }
                    });
                }

                function findLinks() {
                    var answer = html.find('a');
                    if (linkFilter) {
                        answer = answer.filter(linkFilter);
                    }
                    return answer;
                }

                function loadChapters() {
                    if (!html.get(0).id) {
                        html.get(0).id = 'toc';
                    }
                    $scope.tocId = '#' + html.get(0).id;
                    $scope.remaining = findLinks().length;
                    findLinks().each(function (index, a) {
                        log.debug("Found: ", a);
                        var filename = $scope.getFilename(a.href, a.getAttribute('file-extension'));
                        var item = {
                            filename: filename,
                            title: a.textContent,
                            link: a
                        };
                        $scope.addChapter(item);
                    });

                    // TODO this doesn't seem to have any effect ;)
                    setTimeout(function () {
                        setFirstChapterActive();
                    }, 100);
                }

                $scope.$watch('render', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if (newValue) {
                            if (!contentDiv.next('.hawtio-toc').length) {
                                var div = $('<div class="hawtio-toc"></div>');
                                div.appendTo(contentDiv);

                                var selectedChapter = $location.search()["chapter"];

                                // lets load the chapter panels
                                $scope.chapters.forEach(function (chapter, index) {
                                    log.debug("index:", index);
                                    var panel = $('<div></div>');
                                    var panelHeader = null;

                                    var chapterId = $scope.getTarget(chapter['filename']);
                                    var link = chapter["link"];
                                    if (link) {
                                        link.setAttribute("chapter-id", chapterId);
                                    }
                                    if (index > 0) {
                                        panelHeader = $('<div class="panel-title"><a class="toc-back" href="">Back to Top</a></div>');
                                    }
                                    var panelBody = $('<div class="panel-body" id="' + chapterId + '">' + chapter['text'] + '</div>');
                                    if (panelHeader) {
                                        panel.append(panelHeader).append($compile(panelBody)($scope));
                                    } else {
                                        panel.append($compile(panelBody)($scope));
                                    }
                                    panel.hide().appendTo(div).fadeIn(1000);

                                    if (chapterId === selectedChapter) {
                                        // lets scroll on startup to allow for bookmarking
                                        scrollToChapter(chapterId);
                                    }
                                });

                                var pageTop = contentDiv.offset().top - offsetTop;

                                div.find('a.toc-back').each(function (index, a) {
                                    $(a).click(function (e) {
                                        e.preventDefault();
                                        $('body,html').animate({
                                            scrollTop: pageTop
                                        }, 2000);
                                    });
                                });

                                // handle clicking links in the TOC
                                findLinks().each(function (index, a) {
                                    var href = a.href;
                                    var filename = $scope.getFilename(href, a.getAttribute('file-extension'));
                                    $(a).click(function (e) {
                                        log.debug("Clicked: ", e);
                                        e.preventDefault();
                                        var chapterId = $scope.getTarget(filename);
                                        $location.search("chapter", chapterId);
                                        Core.$apply(ownerScope);
                                        scrollToChapter(chapterId);
                                        return true;
                                    });
                                });
                            }
                        }
                    }
                });

                // watch for back / forward / url changes
                ownerScope.$on("$locationChangeSuccess", function (event, current, previous) {
                    // lets do this asynchronously to avoid Error: $digest already in progress
                    setTimeout(function () {
                        // lets check if the chapter selection has changed
                        var currentChapter = $location.search()["chapter"];
                        scrollToChapter(currentChapter);
                    }, 50);
                });

                /**
                * Lets scroll to the given chapter ID
                *
                * @param chapterId
                */
                function scrollToChapter(chapterId) {
                    log.debug("selected chapter changed: " + chapterId);
                    if (chapterId) {
                        var target = '#' + chapterId;
                        var top = 0;
                        var targetElements = $(target);
                        if (targetElements.length) {
                            var offset = targetElements.offset();
                            if (offset) {
                                top = offset.top - offsetTop;
                            }
                            $('body,html').animate({
                                scrollTop: top
                            }, scrollDuration);
                        }
                    }
                }
            }
        };
    }
    UI.HawtioTocDisplay = HawtioTocDisplay;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function hawtioPane() {
        return {
            restrict: 'E',
            replace: true,
            transclude: true,
            templateUrl: UI.templatePath + 'pane.html',
            scope: {
                position: '@',
                width: '@',
                header: '@'
            },
            controller: function ($scope, $element, $attrs, $transclude, $document, $timeout, $compile, $templateCache) {
                $scope.moving = false;

                $transclude(function (clone) {
                    $element.find(".pane-content").append(clone);

                    if (Core.isBlank($scope.header)) {
                        return;
                    }

                    var headerTemplate = $templateCache.get($scope.header);

                    var wrapper = $element.find(".pane-header-wrapper");
                    wrapper.html($compile(headerTemplate)($scope));
                    $timeout(function () {
                        $element.find(".pane-viewport").css("top", wrapper.height());
                    }, 500);
                });

                $scope.setWidth = function (width) {
                    if (width < 6) {
                        return;
                    }
                    $element.width(width);
                    $element.parent().css($scope.padding, $element.width() + "px");
                };

                $scope.toggle = function () {
                    if ($scope.moving) {
                        return;
                    }
                    if ($element.width() > 6) {
                        $scope.width = $element.width();
                        $scope.setWidth(6);
                    } else {
                        $scope.setWidth($scope.width);
                    }
                };

                $scope.startMoving = function ($event) {
                    $event.stopPropagation();
                    $event.preventDefault();
                    $event.stopImmediatePropagation();

                    $document.on("mouseup.hawtio-pane", function ($event) {
                        $timeout(function () {
                            $scope.moving = false;
                        }, 250);
                        $event.stopPropagation();
                        $event.preventDefault();
                        $event.stopImmediatePropagation();
                        $document.off(".hawtio-pane");
                        Core.$apply($scope);
                    });

                    $document.on("mousemove.hawtio-pane", function ($event) {
                        $scope.moving = true;
                        $event.stopPropagation();
                        $event.preventDefault();
                        $event.stopImmediatePropagation();
                        $scope.setWidth($event.pageX + 2);
                        Core.$apply($scope);
                    });
                };
            },
            link: function ($scope, $element, $attr) {
                var parent = $element.parent();

                var position = "left";
                if ($scope.position) {
                    position = $scope.position;
                }
                $element.addClass(position);
                var width = $element.width();

                var padding = "padding-" + position;
                $scope.padding = padding;
                var original = parent.css(padding);
                parent.css(padding, width + "px");

                $scope.$on('$destroy', function () {
                    parent.css(padding, original);
                });
            }
        };
    }
    UI.hawtioPane = hawtioPane;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function ZeroClipboardDirective($parse) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attr) {
                var clip = new window.ZeroClipboard($element.get(0), {
                    moviePath: "img/ZeroClipboard.swf"
                });

                clip.on('complete', function (client, args) {
                    if (args.text && angular.isString(args.text)) {
                        notification('info', "Copied text to clipboard: " + args.text.truncate(20));
                    }
                    Core.$apply($scope);
                });

                if ('useCallback' in $attr) {
                    var func = $parse($attr['useCallback']);
                    if (func) {
                        func($scope, { clip: clip });
                    }
                }
            }
        };
    }
    UI.ZeroClipboardDirective = ZeroClipboardDirective;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var Expandable = (function () {
        function Expandable() {
            var _this = this;
            this.log = Logger.get("Expandable");
            this.restrict = 'C';
            this.replace = false;
            this.link = null;
            this.link = function (scope, element, attrs) {
                var self = _this;
                var expandable = element;
                var modelName = null;
                var model = null;

                if (angular.isDefined(attrs['model'])) {
                    modelName = attrs['model'];
                    model = scope[modelName];

                    if (!angular.isDefined(scope[modelName]['expanded'])) {
                        model['expanded'] = expandable.hasClass('opened');
                    } else {
                        if (model['expanded']) {
                            self.forceOpen(model, expandable, scope);
                        } else {
                            self.forceClose(model, expandable, scope);
                        }
                    }

                    if (modelName) {
                        scope.$watch(modelName + '.expanded', function (newValue, oldValue) {
                            if (asBoolean(newValue) !== asBoolean(oldValue)) {
                                if (newValue) {
                                    self.open(model, expandable, scope);
                                } else {
                                    self.close(model, expandable, scope);
                                }
                            }
                        });
                    }
                }

                var title = expandable.find('.title');
                var button = expandable.find('.cancel');

                button.bind('click', function () {
                    model = scope[modelName];
                    self.forceClose(model, expandable, scope);
                    return false;
                });

                title.bind('click', function () {
                    model = scope[modelName];
                    if (isOpen(expandable)) {
                        self.close(model, expandable, scope);
                    } else {
                        self.open(model, expandable, scope);
                    }
                    return false;
                });
            };
        }
        Expandable.prototype.open = function (model, expandable, scope) {
            expandable.find('.expandable-body').slideDown(400, function () {
                if (!expandable.hasClass('opened')) {
                    expandable.addClass('opened');
                }
                expandable.removeClass('closed');
                if (model) {
                    model['expanded'] = true;
                }
                Core.$apply(scope);
            });
        };

        Expandable.prototype.close = function (model, expandable, scope) {
            expandable.find('.expandable-body').slideUp(400, function () {
                expandable.removeClass('opened');
                if (!expandable.hasClass('closed')) {
                    expandable.addClass('closed');
                }
                if (model) {
                    model['expanded'] = false;
                }
                Core.$apply(scope);
            });
        };

        Expandable.prototype.forceClose = function (model, expandable, scope) {
            expandable.find('.expandable-body').slideUp(0, function () {
                if (!expandable.hasClass('closed')) {
                    expandable.addClass('closed');
                }
                expandable.removeClass('opened');
                if (model) {
                    model['expanded'] = false;
                }
                Core.$apply(scope);
            });
        };

        Expandable.prototype.forceOpen = function (model, expandable, scope) {
            expandable.find('.expandable-body').slideDown(0, function () {
                if (!expandable.hasClass('opened')) {
                    expandable.addClass('opened');
                }
                expandable.removeClass('closed');
                if (model) {
                    model['expanded'] = true;
                }
                Core.$apply(scope);
            });
        };
        return Expandable;
    })();
    UI.Expandable = Expandable;

    function isOpen(expandable) {
        return expandable.hasClass('opened') || !expandable.hasClass("closed");
    }

    function asBoolean(value) {
        return value ? true : false;
    }
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var EditableProperty = (function () {
        function EditableProperty($parse) {
            this.$parse = $parse;
            this.restrict = 'E';
            this.scope = true;
            this.templateUrl = UI.templatePath + 'editableProperty.html';
            this.require = 'ngModel';
            this.link = null;
            this.link = function (scope, element, attrs, ngModel) {
                scope.editing = false;
                $(element.find(".icon-pencil")[0]).hide();

                scope.getPropertyName = function () {
                    var propertyName = $parse(attrs['property'])(scope);
                    if (!propertyName && propertyName !== 0) {
                        propertyName = attrs['property'];
                    }
                    return propertyName;
                };

                scope.propertyName = scope.getPropertyName();

                ngModel.$render = function () {
                    if (!ngModel.$viewValue) {
                        return;
                    }
                    scope.text = ngModel.$viewValue[scope.propertyName];
                };

                scope.getInputStyle = function () {
                    if (!scope.text) {
                        return {};
                    }
                    return {
                        width: (scope.text + "").length / 1.5 + 'em'
                    };
                };

                scope.showEdit = function () {
                    $(element.find(".icon-pencil")[0]).show();
                };

                scope.hideEdit = function () {
                    $(element.find(".icon-pencil")[0]).hide();
                };

                scope.$watch('editing', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        if (newValue) {
                            $(element.find('input[type=text]')).focus();
                        }
                    }
                });

                scope.doEdit = function () {
                    scope.editing = true;
                };

                scope.stopEdit = function () {
                    $(element.find(":input[type=text]")[0]).val(ngModel.$viewValue[scope.propertyName]);
                    scope.editing = false;
                };

                scope.saveEdit = function () {
                    var value = $(element.find(":input[type=text]")[0]).val();
                    var obj = ngModel.$viewValue;

                    obj[scope.propertyName] = value;

                    ngModel.$setViewValue(obj);
                    ngModel.$render();
                    scope.editing = false;
                    scope.$parent.$eval(attrs['onSave']);
                };
            };
        }
        return EditableProperty;
    })();
    UI.EditableProperty = EditableProperty;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function TemplatePopover($templateCache, $compile, $document) {
        return {
            restrict: 'A',
            link: function ($scope, $element, $attr) {
                var title = UI.getIfSet('title', $attr, undefined);
                var trigger = UI.getIfSet('trigger', $attr, 'hover');
                var html = true;
                var contentTemplate = UI.getIfSet('content', $attr, 'popoverTemplate');
                var placement = UI.getIfSet('placement', $attr, 'auto');
                var delay = UI.getIfSet('delay', $attr, '100');
                var container = UI.getIfSet('container', $attr, 'body');
                var selector = UI.getIfSet('selector', $attr, 'false');

                if (container === 'false') {
                    container = false;
                }

                if (selector === 'false') {
                    selector = false;
                }

                var template = $templateCache.get(contentTemplate);

                if (!template) {
                    return;
                }

                $element.on('$destroy', function () {
                    $element.popover('destroy');
                });

                $element.popover({
                    title: title,
                    trigger: trigger,
                    html: html,
                    content: function () {
                        var res = $compile(template)($scope);
                        Core.$digest($scope);
                        return res;
                    },
                    delay: delay,
                    container: container,
                    selector: selector,
                    placement: function (tip, element) {
                        if (placement !== 'auto') {
                            return placement;
                        }

                        var el = $element;
                        var offset = el.offset();

                        /* not sure on auto bottom/top
                        
                        var elVerticalCenter = offset['top'] + (el.outerHeight() / 2);
                        if (elVerticalCenter < 300) {
                        return 'bottom';
                        }
                        
                        var height = window.innerHeight;
                        if (elVerticalCenter > window.innerHeight - 300) {
                        return 'top';
                        }
                        */
                        var width = $document.innerWidth();
                        var elHorizontalCenter = offset['left'] + (el.outerWidth() / 2);
                        var midpoint = width / 2;
                        if (elHorizontalCenter < midpoint) {
                            return 'right';
                        } else {
                            return 'left';
                        }
                    }
                });
            }
        };
    }
    UI.TemplatePopover = TemplatePopover;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    

    /**
    * Directive that opens a simple standard confirmation dialog.  See ConfigDialogConfig
    * for configuration properties
    *
    * @class ConfirmDialog
    */
    var ConfirmDialog = (function () {
        function ConfirmDialog() {
            this.restrict = 'A';
            this.replace = true;
            this.transclude = true;
            this.templateUrl = UI.templatePath + 'confirmDialog.html';
            /**
            * @property scope
            * @type ConfirmDialogConfig
            */
            this.scope = {
                show: '=hawtioConfirmDialog',
                title: '@',
                okButtonText: '@',
                showOkButton: '@',
                cancelButtonText: '@',
                onCancel: '&',
                onOk: '&',
                onClose: '&'
            };
            this.controller = function ($scope, $element, $attrs, $transclude, $compile) {
                $scope.clone = null;

                $transclude(function (clone) {
                    $scope.clone = $(clone).filter('.dialog-body');
                });

                $scope.$watch('show', function () {
                    if ($scope.show) {
                        setTimeout(function () {
                            $scope.body = $('.modal-body');
                            $scope.body.html($compile($scope.clone.html())($scope.$parent));
                            Core.$apply($scope);
                        }, 50);
                    }
                });

                $attrs.$observe('okButtonText', function (value) {
                    if (!angular.isDefined(value)) {
                        $scope.okButtonText = "OK";
                    }
                });
                $attrs.$observe('cancelButtonText', function (value) {
                    if (!angular.isDefined(value)) {
                        $scope.cancelButtonText = "Cancel";
                    }
                });
                $attrs.$observe('title', function (value) {
                    if (!angular.isDefined(value)) {
                        $scope.title = "Are you sure?";
                    }
                });

                function checkClosed() {
                    setTimeout(function () {
                        // lets make sure we don't have a modal-backdrop hanging around!
                        var backdrop = $("div.modal-backdrop");
                        if (backdrop && backdrop.length) {
                            Logger.get("ConfirmDialog").debug("Removing the backdrop div! " + backdrop);
                            backdrop.remove();
                        }
                    }, 200);
                }

                $scope.cancel = function () {
                    $scope.show = false;
                    $scope.$parent.$eval($scope.onCancel);
                    checkClosed();
                };

                $scope.submit = function () {
                    $scope.show = false;
                    $scope.$parent.$eval($scope.onOk);
                    checkClosed();
                };

                $scope.close = function () {
                    $scope.$parent.$eval($scope.onClose);
                    checkClosed();
                };
            };
        }
        return ConfirmDialog;
    })();
    UI.ConfirmDialog = ConfirmDialog;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    /**
    * Test controller for the icon help page
    * @param $scope
    * @param $templateCache
    * @constructor
    */
    function IconTestController($scope, $templateCache) {
        $scope.exampleHtml = $templateCache.get('example-html');
        $scope.exampleConfigJson = $templateCache.get('example-config-json');

        $scope.$watch('exampleConfigJson', function (newValue, oldValue) {
            $scope.icons = angular.fromJson($scope.exampleConfigJson);
            //log.debug("Icons: ", $scope.icons);
        });
    }
    UI.IconTestController = IconTestController;

    /**
    * The hawtio-icon directive
    * @returns {{}}
    */
    function hawtioIcon() {
        UI.log.debug("Creating icon directive");
        return {
            restrict: 'E',
            replace: true,
            templateUrl: UI.templatePath + 'icon.html',
            scope: {
                icon: '=config'
            },
            link: function ($scope, $element, $attrs) {
                if (!$scope.icon) {
                    return;
                }
                if (!('type' in $scope.icon) && !Core.isBlank($scope.icon.src)) {
                    if ($scope.icon.src.startsWith("icon-")) {
                        $scope.icon.type = "icon";
                    } else {
                        $scope.icon.type = "img";
                    }
                }
                //log.debug("Created icon: ", $scope.icon);
            }
        };
    }
    UI.hawtioIcon = hawtioIcon;
})(UI || (UI = {}));
/**
* Module that contains several helper functions related to hawtio's code editor
*
* @module CodeEditor
* @main CodeEditor
*/
var CodeEditor;
(function (CodeEditor) {
    

    /**
    * @property GlobalCodeMirrorOptions
    * @for CodeEditor
    * @type CodeMirrorOptions
    */
    CodeEditor.GlobalCodeMirrorOptions = {
        theme: "default",
        tabSize: 4,
        lineNumbers: true,
        indentWithTabs: true,
        lineWrapping: true,
        autoCloseTags: true
    };

    /**
    * Controller used on the preferences page to configure the editor
    *
    * @method PreferencesController
    * @for CodeEditor
    * @static
    * @param $scope
    * @param localStorage
    * @param $templateCache
    */
    function PreferencesController($scope, localStorage, $templateCache) {
        $scope.exampleText = $templateCache.get("exampleText");
        $scope.codeMirrorEx = $templateCache.get("codeMirrorExTemplate");
        $scope.javascript = "javascript";

        $scope.preferences = CodeEditor.GlobalCodeMirrorOptions;

        // If any of the preferences change, make sure to save them automatically
        $scope.$watch("preferences", function (newValue, oldValue) {
            if (newValue !== oldValue) {
                // such a cheap and easy way to update the example view :-)
                $scope.codeMirrorEx += " ";
                localStorage['CodeMirrorOptions'] = angular.toJson(angular.extend(CodeEditor.GlobalCodeMirrorOptions, $scope.preferences));
            }
        }, true);
    }
    CodeEditor.PreferencesController = PreferencesController;

    /**
    * Tries to figure out what kind of text we're going to render in the editor, either
    * text, javascript or XML.
    *
    * @method detectTextFormat
    * @for CodeEditor
    * @static
    * @param value
    * @returns {string}
    */
    function detectTextFormat(value) {
        var answer = "text";
        if (value) {
            answer = "javascript";
            var trimmed = value.toString().trimLeft().trimRight();
            if (trimmed && trimmed.first() === '<' && trimmed.last() === '>') {
                answer = "xml";
            }
        }
        return answer;
    }
    CodeEditor.detectTextFormat = detectTextFormat;

    /**
    * Auto formats the CodeMirror editor content to pretty print
    *
    * @method autoFormatEditor
    * @for CodeEditor
    * @static
    * @param {CodeMirrorEditor} editor
    * @return {void}
    */
    function autoFormatEditor(editor) {
        if (editor) {
            var totalLines = editor.lineCount();

            //var totalChars = editor.getValue().length;
            var start = { line: 0, ch: 0 };
            var end = { line: totalLines - 1, ch: editor.getLine(totalLines - 1).length };
            editor.autoFormatRange(start, end);
            editor.setSelection(start, start);
        }
    }
    CodeEditor.autoFormatEditor = autoFormatEditor;

    /**
    * Used to configures the default editor settings (per Editor Instance)
    *
    * @method createEditorSettings
    * @for CodeEditor
    * @static
    * @param {Object} options
    * @return {Object}
    */
    function createEditorSettings(options) {
        if (typeof options === "undefined") { options = {}; }
        options.extraKeys = options.extraKeys || {};

        // Handle Mode
        (function (mode) {
            mode = mode || { name: "text" };

            if (typeof mode !== "object") {
                mode = { name: mode };
            }

            var modeName = mode.name;
            if (modeName === "javascript") {
                angular.extend(mode, {
                    "json": true
                });
            }
        })(options.mode);

        // Handle Code folding folding
        (function (options) {
            var javascriptFolding = CodeMirror.newFoldFunction(CodeMirror.braceRangeFinder);
            var xmlFolding = CodeMirror.newFoldFunction(CodeMirror.tagRangeFinder);

            // Mode logic inside foldFunction to allow for dynamic changing of the mode.
            // So don't have to listen to the options model and deal with re-attaching events etc...
            var foldFunction = function (codeMirror, line) {
                var mode = codeMirror.getOption("mode");
                var modeName = mode["name"];
                if (!mode || !modeName)
                    return;
                if (modeName === 'javascript') {
                    javascriptFolding(codeMirror, line);
                } else if (modeName === "xml" || modeName.startsWith("html")) {
                    xmlFolding(codeMirror, line);
                }
                ;
            };

            options.onGutterClick = foldFunction;
            options.extraKeys = angular.extend(options.extraKeys, {
                "Ctrl-Q": function (codeMirror) {
                    foldFunction(codeMirror, codeMirror.getCursor().line);
                }
            });
        })(options);

        var readOnly = options.readOnly;
        if (!readOnly) {
            /*
            options.extraKeys = angular.extend(options.extraKeys, {
            "'>'": function (codeMirror) {
            codeMirror.closeTag(codeMirror, '>');
            },
            "'/'": function (codeMirror) {
            codeMirror.closeTag(codeMirror, '/');
            }
            });
            */
            options.matchBrackets = true;
        }

        // Merge the global config in to this instance of CodeMirror
        angular.extend(options, CodeEditor.GlobalCodeMirrorOptions);

        return options;
    }
    CodeEditor.createEditorSettings = createEditorSettings;
})(CodeEditor || (CodeEditor = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var SlideOut = (function () {
        function SlideOut() {
            this.restrict = 'A';
            this.replace = true;
            this.transclude = true;
            this.templateUrl = UI.templatePath + 'slideout.html';
            this.scope = {
                show: '=hawtioSlideout',
                direction: '@',
                top: '@',
                height: '@',
                title: '@'
            };
            this.controller = function ($scope, $element, $attrs, $transclude, $compile) {
                $scope.clone = null;

                $transclude(function (clone) {
                    $scope.clone = $(clone).filter('.dialog-body');
                });

                UI.observe($scope, $attrs, 'direction', 'right');
                UI.observe($scope, $attrs, 'top', '10%', function (value) {
                    $element.css('top', value);
                });
                UI.observe($scope, $attrs, 'height', '80%', function (value) {
                    $element.css('height', value);
                });
                UI.observe($scope, $attrs, 'title', '');

                $scope.$watch('show', function () {
                    if ($scope.show) {
                        $scope.body = $element.find('.slideout-body');
                        $scope.body.html($compile($scope.clone.html())($scope.$parent));
                    }
                });
            };
            this.link = function ($scope, $element, $attrs) {
                $scope.element = $($element);

                $scope.element.blur(function () {
                    $scope.show = false;
                    Core.$apply($scope);
                    return false;
                });

                $scope.$watch('show', function () {
                    if ($scope.show) {
                        $scope.element.addClass('out');
                        $scope.element.focus();
                    } else {
                        $scope.element.removeClass('out');
                    }
                });
            };
        }
        return SlideOut;
    })();
    UI.SlideOut = SlideOut;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    UI.selected = "selected";
    UI.unselected = "unselected";

    /**
    * Pre defined colors used in the color picker
    * @property colors
    * @for UI
    * @type Array
    */
    UI.colors = [
        "#5484ED", "#A4BDFC", "#46D6DB", "#7AE7BF",
        "#51B749", "#FBD75B", "#FFB878", "#FF887C", "#DC2127",
        "#DBADFF", "#E1E1E1"];

    /**
    Directive that allows the user to pick a color from a pre-defined pallete of colors.
    
    Use it like:
    
    ```html
    <div hawtio-color-picker="myModel"></div>
    ```
    
    'myModel' will be bound to the color the user clicks on
    
    @class ColorPicker
    */
    var ColorPicker = (function () {
        function ColorPicker() {
            this.restrict = 'A';
            this.replace = true;
            this.scope = {
                property: '=hawtioColorPicker'
            };
            this.templateUrl = UI.templatePath + "colorPicker.html";
            this.compile = function (tElement, tAttrs, transclude) {
                return {
                    post: function postLink(scope, iElement, iAttrs, controller) {
                        scope.colorList = [];

                        angular.forEach(UI.colors, function (color) {
                            var select = UI.unselected;

                            if (scope.property === color) {
                                select = UI.selected;
                            }

                            scope.colorList.push({
                                color: color,
                                select: select
                            });
                        });
                    }
                };
            };
            this.controller = function ($scope, $element, $timeout) {
                $scope.popout = false;

                $scope.$watch('popout', function () {
                    $element.find('.color-picker-popout').toggleClass('popout-open', $scope.popout);
                });

                $scope.selectColor = function (color) {
                    for (var i = 0; i < $scope.colorList.length; i++) {
                        $scope.colorList[i].select = UI.unselected;
                        if ($scope.colorList[i] === color) {
                            $scope.property = color.color;
                            $scope.colorList[i].select = UI.selected;
                        }
                    }
                };
            };
        }
        return ColorPicker;
    })();
    UI.ColorPicker = ColorPicker;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function hawtioDropDown($templateCache) {
        return {
            restrict: 'A',
            replace: true,
            templateUrl: UI.templatePath + 'dropDown.html',
            scope: {
                config: '=hawtioDropDown'
            },
            controller: function ($scope, $element, $attrs) {
                if (!$scope.config) {
                    $scope.config = {};
                }

                if (!('open' in $scope.config)) {
                    $scope.config['open'] = false;
                }

                $scope.action = function (config, $event) {
                    //log.debug("doAction on : ", config, "event: ", $event);
                    if ('items' in config && !('action' in config)) {
                        config.open = !config.open;
                        $event.preventDefault();
                        $event.stopPropagation();
                    } else if ('action' in config) {
                        //log.debug("executing action: ", config.action);
                        var action = config['action'];
                        if (angular.isFunction(action)) {
                            action.apply();
                        } else if (angular.isString(action)) {
                            $scope.$parent.$eval(action, {
                                config: config,
                                '$event': $event
                            });
                        }
                    }
                };

                $scope.$watch('config.items', function (newValue, oldValue) {
                    if (newValue !== oldValue) {
                        // just add some space to force a redraw
                        $scope.menuStyle = $scope.menuStyle + " ";
                    }
                }, true);

                $scope.submenu = function (config) {
                    if (config && config.submenu) {
                        return "sub-menu";
                    }
                    return "";
                };

                $scope.icon = function (config) {
                    if (config && !Core.isBlank(config.icon)) {
                        return config.icon;
                    } else {
                        return 'icon-spacer';
                    }
                };

                $scope.open = function (config) {
                    if (config && !config.open) {
                        return '';
                    }
                    return 'open';
                };
            },
            link: function ($scope, $element, $attrs) {
                $scope.menuStyle = $templateCache.get("withsubmenus.html");

                if ('processSubmenus' in $attrs) {
                    if (!Core.parseBooleanValue($attrs['processSubmenus'])) {
                        $scope.menuStyle = $templateCache.get("withoutsubmenus.html");
                    }
                }
            }
        };
    }
    UI.hawtioDropDown = hawtioDropDown;
})(UI || (UI = {}));
/**
* Module that contains a bunch of re-usable directives to assemble into pages in hawtio
*
* @module UI
* @main UI
*/
var UI;
(function (UI) {
    UI.pluginName = 'hawtio-ui';

    UI.templatePath = 'app/ui/html/';

    angular.module(UI.pluginName, ['bootstrap', 'ngResource', 'ui', 'ui.bootstrap']).config(function ($routeProvider) {
        $routeProvider.when('/ui/developerPage', { templateUrl: UI.templatePath + 'developerPage.html', reloadOnSearch: false });
    }).factory('UI', function () {
        return UI;
    }).factory('marked', function () {
        marked.setOptions({
            gfm: true,
            tables: true,
            breaks: false,
            pedantic: true,
            sanitize: false,
            smartLists: true,
            langPrefix: 'language-'
        });
        return marked;
    }).directive('hawtioConfirmDialog', function () {
        return new UI.ConfirmDialog();
    }).directive('hawtioSlideout', function () {
        return new UI.SlideOut();
    }).directive('hawtioPager', function () {
        return new UI.TablePager();
    }).directive('hawtioEditor', function ($parse) {
        return UI.Editor($parse);
    }).directive('hawtioColorPicker', function () {
        return new UI.ColorPicker();
    }).directive('expandable', function () {
        return new UI.Expandable();
    }).directive('gridster', function () {
        return new UI.GridsterDirective();
    }).directive('editableProperty', function ($parse) {
        return new UI.EditableProperty($parse);
    }).directive('hawtioViewport', function () {
        return new UI.ViewportHeight();
    }).directive('hawtioHorizontalViewport', function () {
        return new UI.HorizontalViewport();
    }).directive('hawtioRow', function () {
        return new UI.DivRow();
    }).directive('hawtioJsplumb', function () {
        return new UI.JSPlumb();
    }).directive('zeroClipboard', function ($parse) {
        return UI.ZeroClipboardDirective($parse);
    }).directive('hawtioAutoDropdown', function () {
        return UI.AutoDropDown;
    }).directive('hawtioMessagePanel', function () {
        return new UI.MessagePanel();
    }).directive('hawtioInfoPanel', function () {
        return new UI.InfoPanel();
    }).directive('hawtioAutoColumns', function () {
        return new UI.AutoColumns();
    }).directive('hawtioTemplatePopover', function ($templateCache, $compile, $document) {
        return UI.TemplatePopover($templateCache, $compile, $document);
    }).directive('hawtioTocDisplay', function (marked, $location, $anchorScroll, $compile) {
        return UI.HawtioTocDisplay(marked, $location, $anchorScroll, $compile);
    }).directive('hawtioDropDown', function ($templateCache) {
        return UI.hawtioDropDown($templateCache);
    }).directive('hawtioBreadcrumbs', function () {
        return UI.hawtioBreadcrumbs();
    }).directive('hawtioIcon', function () {
        return UI.hawtioIcon();
    }).directive('hawtioPane', function () {
        return UI.hawtioPane();
    }).filter('hawtioGroupBy', function () {
        return UI.groupBy();
    }).directive('compile', [
        '$compile', function ($compile) {
            return function (scope, element, attrs) {
                scope.$watch(function (scope) {
                    // watch the 'compile' expression for changes
                    return scope.$eval(attrs.compile);
                }, function (value) {
                    // when the 'compile' expression changes
                    // assign it into the current DOM
                    element.html(value);

                    // compile the new DOM and link it to the current
                    // scope.
                    // NOTE: we only compile .childNodes so that
                    // we don't get into infinite loop compiling ourselves
                    $compile(element.contents())(scope);
                });
            };
        }]);

    hawtioPluginLoader.addModule(UI.pluginName);
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    function DeveloperPageController($scope, $http) {
        $scope.getContents = function (filename, cb) {
            var fullUrl = "app/ui/html/test/" + filename;
            $http({ method: 'GET', url: fullUrl }).success(function (data, status, headers, config) {
                cb(data);
            }).error(function (data, status, headers, config) {
                cb("Failed to fetch " + filename + ": " + data);
            });
        };
    }
    UI.DeveloperPageController = DeveloperPageController;
})(UI || (UI = {}));
/**
* @module UI
*/
var UI;
(function (UI) {
    var GridsterDirective = (function () {
        function GridsterDirective() {
            this.restrict = 'A';
            this.replace = true;
            this.controller = function ($scope, $element, $attrs) {
            };
            this.link = function ($scope, $element, $attrs) {
                var widgetMargins = [6, 6];
                var widgetBaseDimensions = [150, 150];
                var gridSize = [150, 150];
                var extraRows = 10;
                var extraCols = 6;

                /*
                if (angular.isDefined($attrs['dimensions'])) {
                var dimension = $attrs['dimensions'].toNumber();
                widgetBaseDimensions = [dimension, dimension];
                }
                
                
                if (angular.isDefined($attrs['margins'])) {
                var margins = $attrs['margins'].toNumber();
                widgetMargins = [margins, margins];
                }
                
                if (angular.isDefined($attrs['gridSize'])) {
                var size = $attrs['gridSize'].toNumber();
                gridSize = [size, size];
                }
                */
                if (angular.isDefined($attrs['extraRows'])) {
                    extraRows = $attrs['extraRows'].toNumber();
                }

                if (angular.isDefined($attrs['extraCols'])) {
                    extraCols = $attrs['extraCols'].toNumber();
                }

                var grid = $('<ul style="margin: 0"></ul>');

                var styleStr = '<style type="text/css">';

                var styleStr = styleStr + '</style>';

                $element.append($(styleStr));
                $element.append(grid);

                $scope.gridster = grid.gridster({
                    widget_margins: widgetMargins,
                    grid_size: gridSize,
                    extra_rows: extraRows,
                    extra_cols: extraCols
                }).data('gridster');
            };
        }
        return GridsterDirective;
    })();
    UI.GridsterDirective = GridsterDirective;
})(UI || (UI = {}));
/**
* @module Jclouds
* @main Jclouds
*/
var Jclouds;
(function (Jclouds) {
    var pluginName = 'jclouds';

    angular.module(pluginName, ['bootstrap', 'ngResource', 'ngGrid', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/jclouds/api', { templateUrl: 'app/jclouds/html/api-list.html' }).when('/jclouds/api/:apiId', { templateUrl: 'app/jclouds/html/api.html' }).when('/jclouds/provider', { templateUrl: 'app/jclouds/html/provider-list.html' }).when('/jclouds/provider/:providerId', { templateUrl: 'app/jclouds/html/provider.html' }).when('/jclouds/compute/service', { templateUrl: 'app/jclouds/html/compute/compute-service-list.html' }).when('/jclouds/compute/service/:computeId', { templateUrl: 'app/jclouds/html/compute/compute-service.html' }).when('/jclouds/compute/node/:computeId', { templateUrl: 'app/jclouds/html/compute/node-list.html' }).when('/jclouds/compute/node/:computeId/*nodeId', { templateUrl: 'app/jclouds/html/compute/node.html' }).when('/jclouds/compute/image/:computeId', { templateUrl: 'app/jclouds/html/compute/image-list.html' }).when('/jclouds/compute/image/:computeId/*imageId', { templateUrl: 'app/jclouds/html/compute/image.html' }).when('/jclouds/compute/hardware/:computeId', { templateUrl: 'app/jclouds/html/compute/hardware-list.html' }).when('/jclouds/compute/hardware/:computeId/*hardwareId', { templateUrl: 'app/jclouds/html/compute/hardware.html' }).when('/jclouds/compute/location/:computeId', { templateUrl: 'app/jclouds/html/compute/location-list.html' }).when('/jclouds/compute/location/:computeId/*locationId', { templateUrl: 'app/jclouds/html/compute/location.html' }).when('/jclouds/blobstore/service', { templateUrl: 'app/jclouds/html/blobstore/blobstore-service-list.html' }).when('/jclouds/blobstore/service/:blobstoreId', { templateUrl: 'app/jclouds/html/blobstore/blobstore-service.html' }).when('/jclouds/blobstore/location/:blobstoreId', { templateUrl: 'app/jclouds/html/blobstore/location-list.html' }).when('/jclouds/blobstore/location/:blobstoreId/*locationId', { templateUrl: 'app/jclouds/html/blobstore/location.html' }).when('/jclouds/blobstore/container/:blobstoreId', { templateUrl: 'app/jclouds/html/blobstore/container-list.html' }).when('/jclouds/blobstore/container/:blobstoreId/:containerId', { templateUrl: 'app/jclouds/html/blobstore/container.html' }).when('/jclouds/blobstore/container/:blobstoreId/:containerId/*directory', { templateUrl: 'app/jclouds/html/blobstore/container.html' });
    }).run(function (workspace, viewRegistry, helpRegistry) {
        viewRegistry['jclouds'] = "app/jclouds/html/layoutJclouds.html";
        helpRegistry.addUserDoc('jclouds', 'app/' + 'jclouds' + '/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties("org.jclouds");
        });

        workspace.topLevelTabs.push({
            id: "jclouds",
            content: "jclouds",
            title: "Visualise and manage the Jclouds Compute/BlobStore providers and apis",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties("org.jclouds");
            },
            href: function () {
                return "#/jclouds/api";
            },
            isActive: function (workspace) {
                return workspace.isLinkActive("jclouds");
            }
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function HardwareController($scope, $filter, workspace, $routeParams) {
        $scope.computeId = $routeParams.computeId;
        $scope.hardwareId = $routeParams.hardwareId;

        updateTableContents();

        $scope.processorsTable = {
            plugins: [],
            data: 'processors',
            showFilter: false,
            displayFooter: false,
            displaySelectionCheckbox: false,
            showColumnMenu: false,
            rowHeight: 32,
            columnDefs: [
                {
                    field: 'cores',
                    displayName: 'Cores',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 50,
                    resizable: false
                },
                {
                    field: 'speed',
                    displayName: 'Speed',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                }
            ]
        };

        $scope.volumesTable = {
            plugins: [],
            data: 'volumes',
            showFilter: false,
            displayFooter: false,
            displaySelectionCheckbox: false,
            showColumnMenu: false,
            rowHeight: 32,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                },
                {
                    field: 'type',
                    displayName: 'Type',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                },
                {
                    field: 'device',
                    displayName: 'Device',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                },
                {
                    field: 'size',
                    displayName: 'Size',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                },
                {
                    field: 'bootDevice',
                    displayName: 'Boot Device',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                },
                {
                    field: 'durable',
                    displayName: 'Durable',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100,
                    resizable: false
                }
            ]
        };

        function setHardwareProfiles(hardwareProfiles) {
            $scope.row = findHardwareById(hardwareProfiles, $scope.hardwareId);
            $scope.processors = $scope.row["processors"];
            $scope.volumes = $scope.row["volumes"];
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jolokia = workspace.jolokia;
            var computeMbean = Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId);

            if (computeMbean) {
                setHardwareProfiles(jolokia.request({ type: 'exec', mbean: computeMbean, operation: 'listHardwareProfiles()' }).value);
            }
        }

        function findHardwareById(hardwareProfiles, id) {
            return hardwareProfiles.find(function (hardware) {
                return hardware.id === id;
            });
        }
    }
    Jclouds.HardwareController = HardwareController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ComputeListController($scope, $location, workspace, jolokia) {
        $scope.result = {};
        $scope.computeServiceIds = [];
        $scope.computeServices = [];

        $scope.computeTable = {
            plugins: [],
            data: 'computeServices',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedComputeServices,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Service Name',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/service/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'providerId',
                    displayName: 'Proivder',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'identity',
                    displayName: 'Identity',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        render(Jclouds.listJcloudsMBeanNameOfType(workspace, "compute"));

        function render(response) {
            if (!Object.equal($scope.result, response)) {
                $scope.result = response;
                $scope.computeServiceIds = $scope.result;
                var computeServices = [];
                angular.forEach($scope.computeServiceIds, function (id) {
                    computeServices.push(Jclouds.findContextByName(workspace, id));
                });
                $scope.computeServices = computeServices;
                Core.$apply($scope);
            }
        }
    }
    Jclouds.ComputeListController = ComputeListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ImageListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.computeId = $routeParams.computeId;

        $scope.result = {};
        $scope.images = [];

        $scope.operatingSystemFamily = "";
        $scope.operatingSystemFamilies = [];
        var os = $location.search()['os'];
        if (os) {
            $scope.operatingSystemFamily = os;
        }

        // selected images
        $scope.selectedImages = [];

        /*
        var SearchProvider = function (scope, location) {
        var self = this;
        self.scope = scope;
        self.location = location;
        
        self.callback = function (newValue, oldValue) {
        if (newValue === oldValue) {
        return;
        }
        self.scope.images = filterImages(self.scope.images, self.scope.operatingSystemFamily);
        self.scope.operatingSystemFamily = setSelect(self.scope.operatingSystemFamily, self.scope.operatingSystemFamilies);
        
        var q = location.search();
        q['os'] = self.scope.operatingSystemFamily;
        location.search(q);
        self.evalFilter();
        };
        
        self.scope.$watch('operatingSystemFamily', self.callback);
        self.scope.$watch('location', self.callback);
        
        self.init = function (childScope, grid) {
        self.grid = grid;
        self.childScope = childScope;
        grid.searchProvider = self;
        };
        
        self.evalFilter = function () {
        var byOperatingSystemFamily = self.grid.sortedData;
        if (self.scope.operatingSystemFamily !== "") {
        byOperatingSystemFamily = self.grid.sortedData.findAll(function (item) {
        return item["operatingSystem"]["family"] === self.scope.operatingSystemFamily
        });
        }
        
        self.grid.filteredData = byOperatingSystemFamily;
        self.grid.rowFactory.filteredDataChanged();
        };
        }
        
        var searchProvider = new SearchProvider($scope, $location);
        */
        $scope.imageTable = {
            //plugins: [searchProvider],
            data: 'images',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedImages,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/image/{{computeId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'operatingSystem.family',
                    displayName: 'Operating System',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200
                },
                {
                    field: 'operatingSystem.is64Bit',
                    displayName: '64 bit',
                    cellTemplate: '<div class="ngCellText pagination-centered"><i class="icon1point5x {{is64BitIcon(row.getProperty(col.field))}}"></i></div>',
                    width: 200
                },
                {
                    field: 'status',
                    displayName: 'Status',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 300
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId), operation: 'listImages()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.images = $scope.result;
                $scope.operatingSystemFamilies = extractOperatingSystemFamilies($scope.images);
                Core.$apply($scope);
            }
        }

        function extractOperatingSystemFamilies(images) {
            var operatingSystemFamilies = [];
            operatingSystemFamilies.push("");
            angular.forEach(images, function (image) {
                var operatingSystemFamily = image["operatingSystem"]["family"];

                operatingSystemFamilies.push(operatingSystemFamily);
            });
            return operatingSystemFamilies.unique();
        }

        $scope.is64BitIcon = function (is64bit) {
            if (is64bit) {
                return 'icon-thumbs-up';
            } else {
                return 'icon-thumbs-down';
            }
        };
    }
    Jclouds.ImageListController = ImageListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ComputeLocationListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.computeId = $routeParams.computeId;

        $scope.result = {};
        $scope.locations = [];

        // selected locations
        $scope.selectedLocations = [];

        $scope.locationTable = {
            plugins: [],
            data: 'locations',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedLocations,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/location/{{computeId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'description',
                    displayName: 'Description',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId), operation: 'listAssignableLocations()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.locations = $scope.result;
                Core.$apply($scope);
            }
        }
    }
    Jclouds.ComputeLocationListController = ComputeLocationListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function HardwareListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.computeId = $routeParams.computeId;

        $scope.result = {};
        $scope.hardwares = [];

        // selected hardwares
        $scope.selectedHardwares = [];

        $scope.hardwareTable = {
            plugins: [],
            data: 'hardwares',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedHardwares,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/hardware/{{computeId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'ram',
                    displayName: 'Ram',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'hypervisor',
                    displayName: 'Hypervisor',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId), operation: 'listHardwareProfiles()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.hardwares = $scope.result;
                Core.$apply($scope);
            }
        }

        $scope.is64BitIcon = function (is64bit) {
            if (is64bit) {
                return 'icon-thumbs-up';
            } else {
                return 'icon-thumbs-down';
            }
        };
    }
    Jclouds.HardwareListController = HardwareListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function NodeListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.computeId = $routeParams.computeId;

        $scope.result = {};
        $scope.nodes = [];

        $scope.group = "";
        $scope.groups = [];

        $scope.location = "";
        $scope.locations = [];

        var grp = $location.search()['group'];
        if (grp) {
            $scope.group = grp;
        }

        var loc = $location.search()['location'];
        if (loc) {
            $scope.location = loc;
        }

        // selected nodes
        $scope.selectedNodes = [];

        /*
        var SearchProvider = function (scope, location) {
        var self = this;
        self.scope = scope;
        self.location = location;
        
        self.callback = function (newValue, oldValue) {
        if (newValue === oldValue) {
        return;
        }
        self.scope.nodes = filterNodes(self.scope.nodes, self.scope.group, self.scope.location);
        self.scope.group = setSelect(self.scope.group, self.scope.groups);
        self.scope.location = setSelect(self.scope.location, self.scope.locations);
        
        var q = location.search();
        q['group'] = self.scope.group;
        q['location'] = self.scope.location;
        location.search(q);
        self.evalFilter();
        };
        
        self.scope.$watch('group', self.callback);
        self.scope.$watch('location', self.callback);
        
        self.init = function (childScope, grid) {
        self.grid = grid;
        self.childScope = childScope;
        grid.searchProvider = self;
        };
        
        self.evalFilter = function () {
        var byGroup = self.grid.sortedData;
        if (self.scope.group !== "") {
        byGroup = self.grid.sortedData.findAll(function (item) {
        return item["group"] === self.scope.group
        });
        }
        
        var byLocation = byGroup;
        if (self.scope.location !== "") {
        byLocation = self.grid.sortedData.findAll(function (item) {
        return item["locationId"] === self.scope.location
        });
        }
        
        self.grid.filteredData = byLocation;
        self.grid.rowFactory.filteredDataChanged();
        };
        }
        
        var searchProvider = new SearchProvider($scope, $location);
        */
        $scope.nodeTable = {
            //plugins: [searchProvider],
            data: 'nodes',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedNodes,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/node/{{computeId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'group',
                    displayName: 'Group',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/compute/node/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'operatingSystem.family',
                    displayName: 'Operating System',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200
                },
                {
                    field: 'locationId',
                    displayName: 'Location',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200
                },
                {
                    field: 'hostname',
                    displayName: 'Host Name',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 300
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId), operation: 'listNodes()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.nodes = $scope.result;
                $scope.locations = extractLocations($scope.nodes);
                $scope.groups = extractGroups($scope.nodes);
                Core.$apply($scope);
            }
        }

        function extractGroups(nodes) {
            var groups = [];
            groups.push("");
            angular.forEach(nodes, function (node) {
                var group = node["group"];
                groups.push(group);
            });
            return groups.unique();
        }

        function extractLocations(nodes) {
            var locations = [];
            locations.push("");
            angular.forEach(nodes, function (node) {
                var location = node["locationId"];
                locations.push(location);
            });
            return locations.unique();
        }

        $scope.resume = function () {
            $scope.selectedNodes.forEach(function (node) {
                Jclouds.resumeNode(workspace, jolokia, $scope.computeId, node.id, function () {
                    console.log("Resumed!");
                }, function () {
                    console.log("Failed to resume!");
                });
            });
        };

        $scope.suspend = function () {
            $scope.selectedNodes.forEach(function (node) {
                Jclouds.suspendNode(workspace, jolokia, $scope.computeId, node.id, function () {
                    console.log("Suspended!");
                }, function () {
                    console.log("Failed to suspend!");
                });
            });
        };

        $scope.reboot = function () {
            $scope.selectedNodes.forEach(function (node) {
                Jclouds.rebootNode(workspace, jolokia, $scope.computeId, node.id, function () {
                    console.log("Rebooted!");
                }, function () {
                    console.log("Failed to reboot!");
                });
            });
        };

        $scope.destroy = function () {
            $scope.selectedNodes.forEach(function (node) {
                Jclouds.destroyNode(workspace, jolokia, $scope.computeId, node.id, function () {
                    console.log("Destroyed!");
                }, function () {
                    console.log("Failed to destroy!");
                });
            });
        };
    }
    Jclouds.NodeListController = NodeListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ComputeNavigationController($scope, $routeParams, workspace) {
        $scope.computeId = $routeParams.computeId;

        $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());
        };
    }
    Jclouds.ComputeNavigationController = ComputeNavigationController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ImageController($scope, $filter, workspace, $routeParams) {
        $scope.computeId = $routeParams.computeId;
        $scope.imageId = $routeParams.imageId;

        updateTableContents();

        function setImage(api) {
            $scope.row = api;
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jolokia = workspace.jolokia;
            var computeMbean = Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId);

            if (computeMbean) {
                setImage(jolokia.request({ type: 'exec', mbean: computeMbean, operation: 'getImage(java.lang.String)', arguments: [$scope.imageId] }).value);
            }
        }
    }
    Jclouds.ImageController = ImageController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ComputeLocationController($scope, $filter, workspace, $routeParams) {
        $scope.computeId = $routeParams.computeId;
        $scope.locationId = $routeParams.locationId;

        updateTableContents();

        function setLocationProfiles(locationProfiles) {
            $scope.row = findLocationById(locationProfiles, $scope.locationId);
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jolokia = workspace.jolokia;
            var computeMbean = Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId);

            if (computeMbean) {
                setLocationProfiles(jolokia.request({ type: 'exec', mbean: computeMbean, operation: 'listAssignableLocations()' }).value);
            }
        }

        function findLocationById(locationProfiles, id) {
            return locationProfiles.find(function (location) {
                return location.id === id;
            });
        }
    }
    Jclouds.ComputeLocationController = ComputeLocationController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function NodeController($scope, $filter, workspace, $routeParams) {
        $scope.computeId = $routeParams.computeId;
        $scope.nodeId = $routeParams.nodeId;

        updateTableContents();

        function setNode(api) {
            $scope.row = api;
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var computeMbean = Jclouds.getSelectionJcloudsComputeMBean(workspace, $scope.computeId);
            var jolokia = workspace.jolokia;

            if (computeMbean) {
                setNode(jolokia.request({ type: 'exec', mbean: computeMbean, operation: 'getNode(java.lang.String)', arguments: [$scope.nodeId] }).value);
            }
        }

        $scope.resume = function () {
            Jclouds.resumeNode(workspace, workspace.jolokia, $scope.computeId, $scope.nodeId, function () {
                console.log("Resumed!");
            }, function () {
                console.log("Failed to resume!");
            });
        };

        $scope.suspend = function () {
            Jclouds.suspendNode(workspace, workspace.jolokia, $scope.computeId, $scope.nodeId, function () {
                console.log("Suspended!");
            }, function () {
                console.log("Failed to suspend!");
            });
        };

        $scope.reboot = function () {
            Jclouds.rebootNode(workspace, workspace.jolokia, $scope.computeId, $scope.nodeId, function () {
                console.log("Rebooted!");
            }, function () {
                console.log("Failed to reboot!");
            });
        };

        $scope.destroy = function () {
            Jclouds.destroyNode(workspace, workspace.jolokia, $scope.computeId, $scope.nodeId, function () {
                console.log("Destroyed!");
            }, function () {
                console.log("Failed to destroy!");
            });
        };
    }
    Jclouds.NodeController = NodeController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ProviderListController($scope, $location, workspace, jolokia) {
        $scope.result = {};
        $scope.providers = [];
        $scope.type = "";
        $scope.types = ["", "blobstore", "compute", "loadbalancer"];

        var key = $location.search()['type'];
        if (key) {
            $scope.type = key;
        }

        // selected providers
        $scope.selectedProviders = [];

        /*
        var SearchProvider = function (scope, location) {
        var self = this;
        self.scope = scope;
        self.location = location;
        
        self.callback = function (newValue, oldValue) {
        if (newValue === oldValue) {
        return;
        }
        self.scope.providers = providersOfType(self.scope.providers, self.scope.type);
        self.scope.type = setSelect(self.scope.type, self.scope.types);
        
        var q = location.search();
        q['type'] = self.scope.type;
        location.search(q);
        self.evalFilter();
        };
        
        self.scope.$watch('type', self.callback);
        
        self.init = function (childScope, grid) {
        self.grid = grid;
        self.childScope = childScope;
        grid.searchProvider = self;
        };
        
        self.evalFilter = function () {
        var byType = self.grid.sortedData;
        if (self.scope.type !== "") {
        byType = self.grid.sortedData.findAll(function (item) {
        return item["type"] === self.scope.type
        });
        }
        self.grid.filteredData = byType;
        self.grid.rowFactory.filteredDataChanged();
        };
        }
        
        var searchProvider = new SearchProvider($scope, $location);
        */
        $scope.providerTable = {
            //plugins: [searchProvider],
            data: 'providers',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedProviders,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/provider/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 350
                },
                {
                    field: 'type',
                    displayName: 'Type',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'read', mbean: Jclouds.getSelectionJcloudsMBean(workspace)
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.providers = $scope.result["Providers"];
                Jclouds.populateTypeForProviders($scope.providers);
                Core.$apply($scope);
            }
        }
    }
    Jclouds.ProviderListController = ProviderListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    /**
    * Controller to show the details of a Jclouds provider
    *
    * @method ProviderController
    * @for Jclouds
    * @param {*} $scope
    * @param {ng.IFilterService} $filter
    * @param {Workspace} workspace
    * @param {ng.IRouteParamsService} $routeParams
    */
    function ProviderController($scope, $filter, workspace, $routeParams) {
        $scope.providerId = $routeParams.providerId;

        updateTableContents();

        function setProvider(provider) {
            Jclouds.populateTypeForProvider(provider);
            $scope.row = provider;
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jcloudsCoreMbean = Jclouds.getSelectionJcloudsMBean(workspace);
            var jolokia = workspace.jolokia;

            if (jcloudsCoreMbean) {
                setProvider(jolokia.request({ type: 'exec', mbean: Jclouds.getSelectionJcloudsMBean(workspace), operation: 'findProviderById(java.lang.String)', arguments: [$scope.providerId] }).value);
            }
        }
    }
    Jclouds.ProviderController = ProviderController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ApiController($scope, $filter, workspace, $routeParams) {
        $scope.apiId = $routeParams.apiId;

        updateTableContents();

        function setApi(api) {
            Jclouds.populateTypeForApi(api);
            $scope.row = api;
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jcloudsCoreMbean = Jclouds.getSelectionJcloudsMBean(workspace);
            var jolokia = workspace.jolokia;

            if (jcloudsCoreMbean) {
                setApi(jolokia.request({ type: 'exec', mbean: Jclouds.getSelectionJcloudsMBean(workspace), operation: 'findApiById(java.lang.String)', arguments: [$scope.apiId] }).value);
            }
        }
    }
    Jclouds.ApiController = ApiController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function ApiListController($scope, $location, workspace, jolokia) {
        $scope.result = {};
        $scope.apis = [];
        $scope.type = "";
        $scope.types = ["", "blobstore", "compute", "loadbalancer"];

        var key = $location.search()['type'];
        if (key) {
            $scope.type = key;
        }

        // selected apis
        $scope.selectedApis = [];

        /*
        var SearchProvider = function (scope, location) {
        var self = this;
        self.scope = scope;
        self.location = location;
        
        self.callback = function (newValue, oldValue) {
        if (newValue === oldValue) {
        return;
        }
        self.scope.apis = apisOfType(self.scope.apis, self.scope.type);
        self.scope.type = setSelect(self.scope.type, self.scope.types);
        
        var q = location.search();
        q['type'] = self.scope.type;
        location.search(q);
        self.evalFilter();
        };
        
        self.scope.$watch('type', self.callback);
        
        self.init = function (childScope, grid) {
        self.grid = grid;
        self.childScope = childScope;
        grid.searchProvider = self;
        };
        
        self.evalFilter = function () {
        var byType = self.grid.sortedData;
        if (self.scope.type !== "") {
        byType = self.grid.sortedData.findAll(function (item) {
        return item["type"] === self.scope.type
        });
        }
        self.grid.filteredData = byType;
        self.grid.rowFactory.filteredDataChanged();
        };
        }
        
        var searchProvider = new SearchProvider($scope, $location);
        */
        $scope.apiTable = {
            //plugins: [searchProvider],
            data: 'apis',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedApis,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/api/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 350
                },
                {
                    field: 'type',
                    displayName: 'Type',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 100
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'read', mbean: Jclouds.getSelectionJcloudsMBean(workspace)
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.apis = $scope.result["Apis"];
                Jclouds.populateTypeForApis($scope.apis);
                Core.$apply($scope);
            }
        }
    }
    Jclouds.ApiListController = ApiListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    Array.prototype.unique = function () {
        var a = [], l = this.length;
        for (var i = 0; i < l; i++) {
            for (var j = i + 1; j < l; j++)
                if (this[i] === this[j])
                    j = ++i;
            a.push(this[i]);
        }
        return a;
    };

    function setSelect(selection, group) {
        if (!angular.isDefined(selection)) {
            return group[0];
        }
        var answer = group.findIndex(function (item) {
            return item === selection;
        });
        if (answer !== -1) {
            return group[answer];
        } else {
            return group[0];
        }
    }
    Jclouds.setSelect = setSelect;

    /**
    * Returns context by name.
    * @method fundContextByName
    * @for Jclouds
    * @param {Workspace} workspace
    * @param {String} name
    * @return {any}
    */
    function findContextByName(workspace, name) {
        var jcloudsMBean = getSelectionJcloudsMBean(workspace);
        var response = workspace.jolokia.request({ type: 'read', mbean: jcloudsMBean });
        return response.value["Contexts"].find(function (context) {
            return context.name === name;
        });
    }
    Jclouds.findContextByName = findContextByName;

    /**
    * Add the type attribute to an array of apis
    * @method populateTypeForApis
    * @for Jclouds
    * @param {Array} apis
    */
    function populateTypeForApis(apis) {
        angular.forEach(apis, function (api) {
            populateTypeForApi(api);
        });
    }
    Jclouds.populateTypeForApis = populateTypeForApis;

    /**
    * Add the type attribute to a singe api.
    * @method populateTypeForApi
    * @for Jclouds
    * @param {any} api
    */
    function populateTypeForApi(api) {
        var views = api["views"];
        var found = false;
        angular.forEach(views, function (view) {
            if (!found) {
                if (view.has("blob")) {
                    api["type"] = "blobstore";
                    found = true;
                } else if (view.has("compute")) {
                    api["type"] = "compute";
                    found = true;
                }
            }
        });
    }
    Jclouds.populateTypeForApi = populateTypeForApi;

    /**
    * Filters Images based on Operating System.
    * @method filterImages
    * @for Jclouds
    * @param {Array} images
    * @param {any} operatingSystemFamily
    */
    function filterImages(images, operatingSystemFamily) {
        if (operatingSystemFamily === "") {
            return images;
        } else {
            return images.findAll(function (image) {
                return image["operatingSystem"]["family"] === operatingSystemFamily;
            });
        }
    }
    Jclouds.filterImages = filterImages;

    /**
    * Filters Nodes based on type.
    * @method filterNodes
    * @for Jclouds
    * @param {Array} nodes
    * @param {String} group
    * @param {String} location}
    * @return {any}
    */
    function filterNodes(nodes, group, location) {
        var filteredNodes = [];
        if (group === "") {
            filteredNodes = nodes;
        } else {
            filteredNodes = nodes.findAll(function (node) {
                return node.group === group;
            });
        }

        if (location === "") {
            return filteredNodes;
        } else {
            return filteredNodes.findAll(function (node) {
                return node.locationId === location;
            });
        }
    }
    Jclouds.filterNodes = filterNodes;

    function resumeNode(workspace, jolokia, compute, id, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionJcloudsComputeMBean(workspace, compute),
            operation: 'resumeNode(java.lang.String)',
            arguments: [id]
        }, onSuccess(success, { error: error }));
    }
    Jclouds.resumeNode = resumeNode;

    function suspendNode(workspace, jolokia, compute, id, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionJcloudsComputeMBean(workspace, compute),
            operation: 'suspendNode(java.lang.String)',
            arguments: [id]
        }, onSuccess(success, { error: error }));
    }
    Jclouds.suspendNode = suspendNode;

    function rebootNode(workspace, jolokia, compute, id, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionJcloudsComputeMBean(workspace, compute),
            operation: 'rebootNode(java.lang.String)',
            arguments: [id]
        }, onSuccess(success, { error: error }));
    }
    Jclouds.rebootNode = rebootNode;

    function destroyNode(workspace, jolokia, compute, id, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionJcloudsComputeMBean(workspace, compute),
            operation: 'destroyNode(java.lang.String)',
            arguments: [id]
        }, onSuccess(success, { error: error }));
    }
    Jclouds.destroyNode = destroyNode;

    /**
    * Filters Apis based on type.
    * @method apisOfType
    * @for Jclouds
    * @param {Array} apis
    * @param {String} type
    * @return {Array}
    */
    function apisOfType(apis, type) {
        if (type === "") {
            return apis;
        }

        return apis.findAll(function (api) {
            return api.type === type;
        });
    }
    Jclouds.apisOfType = apisOfType;

    /**
    * Add the type attribute to an array of providers.
    * @method populateTypeForProviders
    * @for Jclouds
    * @param {Array} providers
    */
    function populateTypeForProviders(providers) {
        angular.forEach(providers, function (provider) {
            populateTypeForProvider(provider);
        });
    }
    Jclouds.populateTypeForProviders = populateTypeForProviders;

    /**
    * Add the type attribute to a singe provider.
    * @method populateTypeForProvider
    * @for Jclouds
    * @param {any} provider
    */
    function populateTypeForProvider(provider) {
        var views = provider["api"]["views"];
        var found = false;
        angular.forEach(views, function (view) {
            if (!found) {
                if (view.has("blob")) {
                    provider["type"] = "blobstore";
                    found = true;
                } else if (view.has("compute")) {
                    provider["type"] = "compute";
                    found = true;
                }
            }
        });
    }
    Jclouds.populateTypeForProvider = populateTypeForProvider;

    /**
    * Filters Providers based on type.
    * @method providersOfType
    * @for Jclouds
    * @param {Array} providers
    * @param {String} type
    * @return {Array}
    */
    function providersOfType(providers, type) {
        if (type === "") {
            return providers;
        }

        return providers.findAll(function (provider) {
            return provider.type === type;
        });
    }
    Jclouds.providersOfType = providersOfType;

    /**
    * Walks the tree looking in the first child all the way down until we find an objectName
    * @method findFirstObjectName
    * @for Jclouds
    * @param {any} node
    * @return {any}
    */
    function findFirstObjectName(node) {
        if (node) {
            var answer = node.objectName;
            if (answer) {
                return answer;
            } else {
                var children = node.children;
                if (children && children.length) {
                    return findFirstObjectName(children[0]);
                }
            }
        }
        return null;
    }
    Jclouds.findFirstObjectName = findFirstObjectName;

    /**
    * Walks the tree looking for all available names.
    * @method childsOfType
    * @for Jclouds
    * @param {any} node
    * @return {Array}
    */
    function childsOfType(node) {
        var types = [];
        angular.forEach(node.children, function (child) {
            types.push(child.title);
        });

        return types;
    }
    Jclouds.childsOfType = childsOfType;

    /**
    * Jclouds MBeans are all listed under org.jclouds <type> <name>.
    * This method lists all <names> for the specified <type>.
    * @method listJcloudsMBeanNameOfType
    * @for Jclouds
    * @param {Workspace} workspace
    * @param {String} type
    * @return {any}
    */
    function listJcloudsMBeanNameOfType(workspace, type) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.jclouds", type);
            return childsOfType(folder);
        }
        return null;
    }
    Jclouds.listJcloudsMBeanNameOfType = listJcloudsMBeanNameOfType;

    /**
    * Returns the Jclouds Core Management MBean
    * @method getSelectionJcloudsMBean
    * @for Jclouds
    * @param {Workspace} workspace
    * @return {String}
    */
    function getSelectionJcloudsMBean(workspace) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.jclouds", "management", "core");
            return findFirstObjectName(folder);
        }
        return null;
    }
    Jclouds.getSelectionJcloudsMBean = getSelectionJcloudsMBean;

    /**
    * Returns the Jclouds Compute Management MBean for the specified name.
    * @method getSelectionJcloudsComputeMBean
    * @for Jclouds
    * @param {Workspace} workspace
    * @param {String} name
    * @return {String}
    */
    function getSelectionJcloudsComputeMBean(workspace, name) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.jclouds", "compute", name);
            return findFirstObjectName(folder);
        }
        return null;
    }
    Jclouds.getSelectionJcloudsComputeMBean = getSelectionJcloudsComputeMBean;

    /**
    * Returns the Jclouds Compute Management MBean for the specified name.
    * @method getSelectionJcloudsBlobstoreMBean
    * @for Jclouds
    * @param {Workspace} workspace
    * @param {String} name
    * @return {String}
    */
    function getSelectionJcloudsBlobstoreMBean(workspace, name) {
        if (workspace) {
            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.jclouds", "blobstore", name);
            return findFirstObjectName(folder);
        }
        return null;
    }
    Jclouds.getSelectionJcloudsBlobstoreMBean = getSelectionJcloudsBlobstoreMBean;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreContainerController($scope, $filter, workspace, $routeParams) {
        $scope.blobstoreId = $routeParams.blobstoreId;
        $scope.containerId = $routeParams.containerId;
        $scope.directory = $routeParams.directory;
        $scope.contents = [];
        $scope.breadcrumbs = loadBreadcrumbs($scope.blobstoreId, $scope.containerId, $scope.directory);

        $scope.contentTable = {
            data: 'contents',
            displayFooter: false,
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Content',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/blobstore/container/{{blobstoreId}}/{{containerId}}/{{row.entity.fullpath}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    cellFilter: ""
                },
                {
                    field: 'createdDate',
                    displayName: 'Created',
                    cellFilter: "date:'EEE, MMM d, y : hh:mm:ss a'"
                },
                {
                    field: 'lastModifiedDate',
                    displayName: 'Modified',
                    cellFilter: "date:'EEE, MMM d, y : hh:mm:ss a'"
                }
            ]
        };

        updateTableContents();

        function setContainers(containers) {
            $scope.contents = populatePathAndName(filterContainers(containers, $scope.directory), $scope.directory);
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jolokia = workspace.jolokia;
            var blobstoreMbean = Jclouds.getSelectionJcloudsBlobstoreMBean(workspace, $scope.blobstoreId);

            if (blobstoreMbean) {
                if ($scope.directory) {
                    setContainers(jolokia.request({ type: 'exec', mbean: blobstoreMbean, operation: 'list(java.lang.String, java.lang.String)', arguments: [$scope.containerId, $scope.directory] }).value);
                } else {
                    setContainers(jolokia.request({ type: 'exec', mbean: blobstoreMbean, operation: 'list(java.lang.String)', arguments: [$scope.containerId] }).value);
                }
            }
        }

        function filterContainers(containers, directory) {
            return containers.filter(function (container) {
                return container.name !== directory;
            });
        }

        function populatePathAndName(containers, directory) {
            var updatedContainers = [];
            angular.forEach(containers, function (container) {
                var updateContainer = container;
                updateContainer.fullpath = container.name;
                if (updateContainer.name.startsWith(directory)) {
                    updateContainer.name = updateContainer.name.substring(directory.length + 1);
                }
                updatedContainers.push(updateContainer);
            });
            return updatedContainers;
        }

        $scope.isBlob = function (container) {
            return container.type === 'BLOB';
        };

        function loadBreadcrumbs(blobstore, container, directory) {
            var href = "#/jclouds/blobstore/container/" + blobstore + "/" + container;
            var breadcrumbs = [
                { href: href, name: "/" + container }
            ];

            var array = directory ? directory.split("/") : [];
            angular.forEach(array, function (name) {
                if (!name.startsWith("/") && !href.endsWith("/")) {
                    href += "/";
                }
                href += name;
                breadcrumbs.push({ href: href, name: name });
            });
            return breadcrumbs;
        }
    }
    Jclouds.BlobstoreContainerController = BlobstoreContainerController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreLocationListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.blobstoreId = $routeParams.blobstoreId;

        $scope.result = {};
        $scope.locations = [];

        // selected locations
        $scope.selectedLocations = [];

        $scope.locationTable = {
            plugins: [],
            data: 'locations',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedLocations,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'id',
                    displayName: 'Id',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/blobstore/location/{{blobstoreId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'description',
                    displayName: 'Description',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsBlobstoreMBean(workspace, $scope.blobstoreId), operation: 'listAssignableLocations()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.locations = $scope.result;
                Core.$apply($scope);
            }
        }
    }
    Jclouds.BlobstoreLocationListController = BlobstoreLocationListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreListController($scope, $location, workspace, jolokia) {
        $scope.result = {};
        $scope.blobstoreServiceIds = [];
        $scope.blobstoreServices = [];

        $scope.blobstoreTable = {
            plugins: [],
            data: 'blobstoreServices',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedBlobstoreServices,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Service Name',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/blobstore/service/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'providerId',
                    displayName: 'Proivder',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'identity',
                    displayName: 'Identity',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        render(Jclouds.listJcloudsMBeanNameOfType(workspace, "blobstore"));

        function render(response) {
            if (!Object.equal($scope.result, response)) {
                $scope.result = response;
                $scope.blobstoreServiceIds = $scope.result;
                var blobstoreServices = [];
                angular.forEach($scope.blobstoreServiceIds, function (id) {
                    blobstoreServices.push(Jclouds.findContextByName(workspace, id));
                });
                $scope.blobstoreServices = blobstoreServices;
                Core.$apply($scope);
            }
        }
    }
    Jclouds.BlobstoreListController = BlobstoreListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreContainerListController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.blobstoreId = $routeParams.blobstoreId;

        $scope.result = {};
        $scope.containers = [];

        // selected containers
        $scope.selectedContainers = [];

        $scope.containerTable = {
            plugins: [],
            data: 'containers',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: true
            },
            selectedItems: $scope.selectedContainers,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText"><a href="#/jclouds/blobstore/container/{{blobstoreId}}/{{row.getProperty(col.field)}}{{hash}}">{{row.getProperty(col.field)}}</a></div>',
                    width: 200,
                    resizable: false
                },
                {
                    field: 'creationDate',
                    displayName: 'Created',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200,
                    resizable: false
                }
            ]
        };

        Core.register(jolokia, $scope, {
            type: 'exec', mbean: Jclouds.getSelectionJcloudsBlobstoreMBean(workspace, $scope.blobstoreId), operation: 'list()'
        }, onSuccess(render));

        function render(response) {
            if (!Object.equal($scope.result, response.value)) {
                $scope.result = response.value;
                $scope.containers = $scope.result;
                Core.$apply($scope);
            }
        }
    }
    Jclouds.BlobstoreContainerListController = BlobstoreContainerListController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreLocationController($scope, $filter, workspace, $routeParams) {
        $scope.blobstoreId = $routeParams.blobstoreId;
        $scope.locationId = $routeParams.locationId;

        updateTableContents();

        function setLocationProfiles(locationProfiles) {
            $scope.row = findLocationById(locationProfiles, $scope.locationId);
            Core.$apply($scope);
        }
        ;

        function updateTableContents() {
            var jolokia = workspace.jolokia;
            var blobstoreMbean = Jclouds.getSelectionJcloudsBlobstoreMBean(workspace, $scope.blobstoreId);

            if (blobstoreMbean) {
                setLocationProfiles(jolokia.request({ type: 'exec', mbean: blobstoreMbean, operation: 'listAssignableLocations()' }).value);
            }
        }

        function findLocationById(locationProfiles, id) {
            return locationProfiles.find(function (location) {
                return location.id === id;
            });
        }
    }
    Jclouds.BlobstoreLocationController = BlobstoreLocationController;
})(Jclouds || (Jclouds = {}));
/**
* @module Jclouds
*/
var Jclouds;
(function (Jclouds) {
    function BlobstoreNavigationController($scope, $routeParams, workspace) {
        $scope.blobstoreId = $routeParams.blobstoreId;

        $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());
        };
    }
    Jclouds.BlobstoreNavigationController = BlobstoreNavigationController;
})(Jclouds || (Jclouds = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    Karaf.log = Logger.get("Karaf");

    function setSelect(selection, group) {
        if (!angular.isDefined(selection)) {
            return group[0];
        }
        var answer = group.findIndex(function (item) {
            return item.id === selection.id;
        });
        if (answer !== -1) {
            return group[answer];
        } else {
            return group[0];
        }
    }
    Karaf.setSelect = setSelect;

    function installRepository(workspace, jolokia, uri, success, error) {
        Karaf.log.info("installing URI: ", uri);
        jolokia.request({
            type: 'exec', mbean: getSelectionFeaturesMBean(workspace),
            operation: 'addRepository(java.lang.String)',
            arguments: [uri]
        }, onSuccess(success, { error: error }));
    }
    Karaf.installRepository = installRepository;

    function uninstallRepository(workspace, jolokia, uri, success, error) {
        Karaf.log.info("uninstalling URI: ", uri);
        jolokia.request({
            type: 'exec', mbean: getSelectionFeaturesMBean(workspace),
            operation: 'removeRepository(java.lang.String)',
            arguments: [uri]
        }, onSuccess(success, { error: error }));
    }
    Karaf.uninstallRepository = uninstallRepository;

    function installFeature(workspace, jolokia, feature, version, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionFeaturesMBean(workspace),
            operation: 'installFeature(java.lang.String, java.lang.String)',
            arguments: [feature, version]
        }, onSuccess(success, { error: error }));
    }
    Karaf.installFeature = installFeature;

    function uninstallFeature(workspace, jolokia, feature, version, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionFeaturesMBean(workspace),
            operation: 'uninstallFeature(java.lang.String, java.lang.String)',
            arguments: [feature, version]
        }, onSuccess(success, { error: error }));
    }
    Karaf.uninstallFeature = uninstallFeature;

    // TODO move to core?
    function toCollection(values) {
        var collection = values;
        if (!angular.isArray(values)) {
            collection = [values];
        }
        return collection;
    }
    Karaf.toCollection = toCollection;

    function featureLinks(workspace, name, version) {
        return "<a href='" + url("#/karaf/feature/" + name + "/" + version + workspace.hash()) + "'>" + version + "</a>";
    }
    Karaf.featureLinks = featureLinks;

    function extractFeature(attributes, name, version) {
        var features = [];
        var repos = [];
        populateFeaturesAndRepos(attributes, features, repos);
        return features.find(function (feature) {
            return feature.Name == name && feature.Version == version;
        });
        /*
        var f = {};
        angular.forEach(attributes["Features"], (feature) => {
        angular.forEach(feature, (entry) => {
        if (entry["Name"] === name && entry["Version"] === version) {
        var deps = [];
        populateDependencies(attributes, entry["Dependencies"], deps);
        f["Name"] = entry["Name"];
        f["Version"] = entry["Version"];
        f["Bundles"] = entry["Bundles"];
        f["Dependencies"] = deps;
        f["Installed"] = entry["Installed"];
        f["Configurations"] = entry["Configurations"];
        f["Configuration Files"] = entry["Configuration Files"];
        f["Files"] = entry["Configuration Files"];
        }
        });
        });
        return f;
        */
    }
    Karaf.extractFeature = extractFeature;

    var platformBundlePatterns = [
        "^org.apache.aries",
        "^org.apache.karaf",
        "^activemq-karaf",
        "^org.apache.commons",
        "^org.apache.felix",
        "^io.fabric8",
        "^org.apache.geronimo.specs",
        "^org.apache.servicemix.bundles",
        "^org.objectweb.asm",
        "^io.hawt",
        "^javax.mail",
        "^org.jvnet",
        "^org.apache.mina.core",
        "^org.apache.sshd.core",
        "^org.apache.neethi",
        "^org.apache.servicemix.specs",
        "^org.apache.xbean",
        "^org.apache.santuario.xmlsec",
        "^biz.aQute.bndlib",
        "^groovy-all",
        "^com.google.guava",
        "jackson-\\w+-asl",
        "^org.ops4j",
        "^org.springframework",
        "^bcprov$",
        "^jline$",
        "^scala-library$",
        "^stax2-api$",
        "^woodstox-core-asl",
        "^org.jboss.amq.mq-fabric",
        "^joda-time$",
        "^org.apache.ws",
        "-commands$",
        "patch.patch",
        "org.fusesource.insight",
        "activeio-core",
        "activemq-osgi",
        "^org.eclipse.jetty",
        "org.codehaus.jettison.jettison"
    ];

    var platformBundleRegex = new RegExp(platformBundlePatterns.join('|'));

    var camelBundlePatterns = ["^org.apache.camel", "activemq-camel$"];
    var camelBundleRegex = new RegExp(camelBundlePatterns.join('|'));

    var cxfBundlePatterns = ["^org.apache.cxf"];
    var cxfBundleRegex = new RegExp(cxfBundlePatterns.join('|'));

    function isPlatformBundle(symbolicName) {
        return platformBundleRegex.test(symbolicName);
    }
    Karaf.isPlatformBundle = isPlatformBundle;

    function isCamelBundle(symbolicName) {
        return camelBundleRegex.test(symbolicName);
    }
    Karaf.isCamelBundle = isCamelBundle;

    function isCxfBundle(symbolicName) {
        return cxfBundleRegex.test(symbolicName);
    }
    Karaf.isCxfBundle = isCxfBundle;

    function populateFeaturesAndRepos(attributes, features, repositories) {
        var fullFeatures = attributes["Features"];
        angular.forEach(attributes["Repositories"], function (repo) {
            repositories.push({
                id: repo["Name"],
                uri: repo["Uri"]
            });

            if (!fullFeatures) {
                return;
            }

            angular.forEach(repo["Features"], function (feature) {
                angular.forEach(feature, function (entry) {
                    var f = Object.extended(fullFeatures[entry['Name']][entry['Version']]).clone();
                    f["Id"] = entry["Name"] + "/" + entry["Version"];
                    f["RepositoryName"] = repo["Name"];
                    f["RepositoryURI"] = repo["Uri"];
                    features.push(f);
                });
            });
        });
    }
    Karaf.populateFeaturesAndRepos = populateFeaturesAndRepos;

    function createScrComponentsView(workspace, jolokia, components) {
        var result = [];
        angular.forEach(components, function (component) {
            result.push({
                Name: component,
                State: getComponentStateDescription(getComponentState(workspace, jolokia, component))
            });
        });
        return result;
    }
    Karaf.createScrComponentsView = createScrComponentsView;

    function getComponentStateDescription(state) {
        switch (state) {
            case 2:
                return "Enabled";
            case 4:
                return "Unsatisfied";
            case 8:
                return "Activating";
            case 16:
                return "Active";
            case 32:
                return "Registered";
            case 64:
                return "Factory";
            case 128:
                return "Deactivating";
            case 256:
                return "Destroying";
            case 1024:
                return "Disabling";
            case 2048:
                return "Disposing";
        }
        return "Unknown";
    }
    Karaf.getComponentStateDescription = getComponentStateDescription;
    ;

    function getAllComponents(workspace, jolokia) {
        var scrMBean = getSelectionScrMBean(workspace);
        var response = jolokia.request({
            type: 'read', mbean: scrMBean,
            arguments: []
        });

        //Check if the MBean provides the Components attribute.
        if (!('Components' in response.value)) {
            response = jolokia.request({
                type: 'exec', mbean: scrMBean, operation: 'listComponents()'
            });
            return createScrComponentsView(workspace, jolokia, response.value);
        }
        return response.value['Components'].values;
    }
    Karaf.getAllComponents = getAllComponents;

    function getComponentByName(workspace, jolokia, componentName) {
        var components = getAllComponents(workspace, jolokia);
        return components.find(function (c) {
            return c.Name == componentName;
        });
    }
    Karaf.getComponentByName = getComponentByName;

    function isComponentActive(workspace, jolokia, component) {
        var response = jolokia.request({
            type: 'exec', mbean: getSelectionScrMBean(workspace),
            operation: 'isComponentActive(java.lang.String)',
            arguments: [component]
        });
        return response.value;
    }
    Karaf.isComponentActive = isComponentActive;

    function getComponentState(workspace, jolokia, component) {
        var response = jolokia.request({
            type: 'exec', mbean: getSelectionScrMBean(workspace),
            operation: 'componentState(java.lang.String)',
            arguments: [component]
        });
        return response.value;
    }
    Karaf.getComponentState = getComponentState;

    function activateComponent(workspace, jolokia, component, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionScrMBean(workspace),
            operation: 'activateComponent(java.lang.String)',
            arguments: [component]
        }, onSuccess(success, { error: error }));
    }
    Karaf.activateComponent = activateComponent;

    function deactivateComponent(workspace, jolokia, component, success, error) {
        jolokia.request({
            type: 'exec', mbean: getSelectionScrMBean(workspace),
            operation: 'deactiveateComponent(java.lang.String)',
            arguments: [component]
        }, onSuccess(success, { error: error }));
    }
    Karaf.deactivateComponent = deactivateComponent;

    function populateDependencies(attributes, dependencies, features) {
        angular.forEach(dependencies, function (feature) {
            angular.forEach(feature, function (entry) {
                var enhancedFeature = extractFeature(attributes, entry["Name"], entry["Version"]);
                enhancedFeature["id"] = entry["Name"] + "/" + entry["Version"];

                //enhancedFeature["repository"] = repo["Name"];
                features.push(enhancedFeature);
            });
        });
    }
    Karaf.populateDependencies = populateDependencies;

    function getSelectionFeaturesMBean(workspace) {
        if (workspace) {
            var featuresStuff = workspace.mbeanTypesToDomain["features"] || {};
            var karaf = featuresStuff["org.apache.karaf"] || {};
            var mbean = karaf.objectName;
            if (mbean) {
                return mbean;
            }

            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.apache.karaf", "features");
            if (!folder) {
                // sometimes the features mbean is inside the 'root' folder
                folder = workspace.tree.navigate("org.apache.karaf");
                if (folder) {
                    var children = folder.children;
                    folder = null;
                    angular.forEach(children, function (child) {
                        if (!folder) {
                            folder = child.navigate("features");
                        }
                    });
                }
            }
            if (folder) {
                var children = folder.children;
                if (children) {
                    var node = children[0];
                    if (node) {
                        return node.objectName;
                    }
                }
                return folder.objectName;
            }
        }
        return null;
    }
    Karaf.getSelectionFeaturesMBean = getSelectionFeaturesMBean;

    function getSelectionScrMBean(workspace) {
        if (workspace) {
            var scrStuff = workspace.mbeanTypesToDomain["scr"] || {};
            var karaf = scrStuff["org.apache.karaf"] || {};
            var mbean = karaf.objectName;
            if (mbean) {
                return mbean;
            }

            // lets navigate to the tree item based on paths
            var folder = workspace.tree.navigate("org.apache.karaf", "scr");
            if (!folder) {
                // sometimes the features mbean is inside the 'root' folder
                folder = workspace.tree.navigate("org.apache.karaf");
                if (folder) {
                    var children = folder.children;
                    folder = null;
                    angular.forEach(children, function (child) {
                        if (!folder) {
                            folder = child.navigate("scr");
                        }
                    });
                }
            }
            if (folder) {
                var children = folder.children;
                if (children) {
                    var node = children[0];
                    if (node) {
                        return node.objectName;
                    }
                }
                return folder.objectName;
            }
        }
        return null;
    }
    Karaf.getSelectionScrMBean = getSelectionScrMBean;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
* @main Karaf
*/
var Karaf;
(function (Karaf) {
    var pluginName = 'karaf';
    angular.module(pluginName, ['bootstrap', 'ngResource', 'hawtioCore']).config(function ($routeProvider) {
        $routeProvider.when('/osgi/server', { templateUrl: 'app/karaf/html/server.html' }).when('/osgi/features', { templateUrl: 'app/karaf/html/features.html', reloadOnSearch: false }).when('/osgi/scr-components', { templateUrl: 'app/karaf/html/scr-components.html' }).when('/osgi/scr-component/:name', { templateUrl: 'app/karaf/html/scr-component.html' }).when('/osgi/feature/:name/:version', { templateUrl: 'app/karaf/html/feature.html' });
    }).run(function (workspace, viewRegistry, helpRegistry) {
        helpRegistry.addUserDoc('karaf', 'app/karaf/doc/help.md', function () {
            return workspace.treeContainsDomainAndProperties('org.apache.karaf');
        });
    });

    hawtioPluginLoader.addModule(pluginName);
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function ServerController($scope, $location, workspace, jolokia) {
        $scope.data = {
            name: "",
            version: "",
            state: "",
            root: "",
            startLevel: "",
            framework: "",
            frameworkVersion: "",
            location: "",
            sshPort: "",
            rmiRegistryPort: "",
            rmiServerPort: "",
            pid: "" };

        $scope.$on('jmxTreeUpdated', reloadFunction);
        $scope.$watch('workspace.tree', reloadFunction);

        function reloadFunction() {
            // if the JMX tree is reloaded its probably because a new MBean has been added or removed
            // so lets reload, asynchronously just in case
            setTimeout(loadData, 50);
        }

        function loadData() {
            console.log("Loading Karaf data...");
            jolokia.search("org.apache.karaf:type=admin,*", onSuccess(render));
        }

        function render(response) {
            // grab the first mbean as there should ideally only be one karaf in the JVM
            if (angular.isArray(response)) {
                var mbean = response[0];
                if (mbean) {
                    jolokia.getAttribute(mbean, "Instances", onSuccess(function (response) {
                        onInstances(response, mbean);
                    }));
                }
            }
        }

        function onInstances(instances, mbean) {
            if (instances) {
                var parsedMBean = Core.parseMBean(mbean);
                var instanceName = 'root';
                if ('attributes' in parsedMBean) {
                    if ('name' in parsedMBean['attributes']) {
                        instanceName = parsedMBean['attributes']['name'];
                    }
                }

                //log.debug("mbean: ", Core.parseMBean(mbean));
                //log.debug("Instances: ", instances);
                // the name is the first child
                var rootInstance = instances[instanceName];
                $scope.data.name = rootInstance.Name;
                $scope.data.state = rootInstance.State;
                $scope.data.root = rootInstance["Is Root"];
                $scope.data.location = rootInstance.Location;
                $scope.data.sshPort = rootInstance["SSH Port"];
                $scope.data.rmiRegistryPort = rootInstance["RMI Registry Port"];
                $scope.data.rmiServerPort = rootInstance["RMI Server Port"];
                $scope.data.pid = rootInstance.Pid;

                // we need to get these data from the system mbean
                $scope.data.version = "?";
                $scope.data.startLevel = "?";
                $scope.data.framework = "?";
                $scope.data.frameworkVersion = "?";

                var systemMbean = "org.apache.karaf:type=system,name=" + rootInstance.Name;

                // get more data, and its okay to do this synchronously
                var response = jolokia.request({
                    type: "read", mbean: systemMbean,
                    attribute: ["StartLevel", "Framework", "Version"] }, onSuccess(null));

                var obj = response.value;
                if (obj) {
                    $scope.data.version = obj.Version;
                    $scope.data.startLevel = obj.StartLevel;
                    $scope.data.framework = obj.Framework;
                }

                // and the osgi framework version is the bundle version
                var response2 = jolokia.search("osgi.core:type=bundleState,*", onSuccess(null));
                if (angular.isArray(response2)) {
                    var mbean = response2[0];
                    if (mbean) {
                        // get more data, and its okay to do this synchronously
                        var response3 = jolokia.request({ type: 'exec', mbean: mbean, operation: 'getVersion(long)', arguments: [0] }, onSuccess(null));
                        var obj3 = response3.value;
                        if (obj3) {
                            $scope.data.frameworkVersion = obj3;
                        }
                    }
                }
            }

            // ensure web page is updated
            Core.$apply($scope);
        }
    }
    Karaf.ServerController = ServerController;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function ScrComponentsController($scope, $location, workspace, jolokia) {
        $scope.component = empty();

        // caches last jolokia result
        $scope.result = [];

        // rows in components table
        $scope.components = [];

        // selected components
        $scope.selectedComponents = [];

        $scope.scrOptions = {
            //plugins: [searchProvider],
            data: 'components',
            showFilter: false,
            showColumnMenu: false,
            filterOptions: {
                useExternalFilter: false
            },
            sortInfo: { fields: ['Name'], directions: ['asc'] },
            selectedItems: $scope.selectedComponents,
            rowHeight: 32,
            selectWithCheckboxOnly: true,
            columnDefs: [
                {
                    field: 'Name',
                    displayName: 'Name',
                    cellTemplate: '<div class="ngCellText"><a href="#/osgi/scr-component/{{row.entity.Name}}?p=container">{{row.getProperty(col.field)}}</a></div>',
                    width: 400
                },
                {
                    field: 'State',
                    displayName: 'State',
                    cellTemplate: '<div class="ngCellText">{{row.getProperty(col.field)}}</div>',
                    width: 200
                }
            ]
        };

        var scrMBean = Karaf.getSelectionScrMBean(workspace);
        if (scrMBean) {
            render(Karaf.getAllComponents(workspace, jolokia));
        }

        $scope.activate = function () {
            $scope.selectedComponents.forEach(function (component) {
                Karaf.activateComponent(workspace, jolokia, component.Name, function () {
                    console.log("Activated!");
                }, function () {
                    console.log("Failed to activate!");
                });
            });
        };

        $scope.deactivate = function () {
            $scope.selectedComponents.forEach(function (component) {
                Karaf.deactivateComponent(workspace, jolokia, component.Name, function () {
                    console.log("Deactivated!");
                }, function () {
                    console.log("Failed to deactivate!");
                });
            });
        };

        function empty() {
            return [
                {
                    Name: "",
                    Status: false }
            ];
        }

        function render(components) {
            if (!Object.equal($scope.result, components)) {
                $scope.components = components;
                $scope.result = $scope.components;
                Core.$apply($scope);
            }
        }
    }
    Karaf.ScrComponentsController = ScrComponentsController;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function FeatureController($scope, jolokia, workspace, $routeParams) {
        $scope.hasFabric = Fabric.hasFabric(workspace);
        $scope.name = $routeParams.name;
        $scope.version = $routeParams.version;
        $scope.bundlesByLocation = {};
        $scope.props = "properties";

        updateTableContents();

        $scope.install = function () {
            Karaf.installFeature(workspace, jolokia, $scope.name, $scope.version, function () {
                notification('success', 'Installed feature ' + $scope.name);
            }, function (response) {
                notification('error', 'Failed to install feature ' + $scope.name + ' due to ' + response.error);
            });
        };

        $scope.uninstall = function () {
            Karaf.uninstallFeature(workspace, jolokia, $scope.name, $scope.version, function () {
                notification('success', 'Uninstalled feature ' + $scope.name);
            }, function (response) {
                notification('error', 'Failed to uninstall feature ' + $scope.name + ' due to ' + response.error);
            });
        };

        $scope.toProperties = function (elements) {
            var answer = '';
            angular.forEach(elements, function (value, name) {
                answer += value['Key'] + " = " + value['Value'] + "\n";
            });
            return answer.trim();
        };

        function populateTable(response) {
            $scope.row = Karaf.extractFeature(response.value, $scope.name, $scope.version);
            if ($scope.row) {
                addBundleDetails($scope.row);
                var dependencies = [];

                //TODO - if the version isn't set or is 0.0.0 then maybe we show the highest available?
                angular.forEach($scope.row.Dependencies, function (version, name) {
                    angular.forEach(version, function (data, version) {
                        dependencies.push({
                            Name: name,
                            Version: version
                        });
                    });
                });
                $scope.row.Dependencies = dependencies;
            }

            //console.log("row: ", $scope.row);
            Core.$apply($scope);
        }

        function setBundles(response) {
            var bundleMap = {};
            Osgi.defaultBundleValues(workspace, $scope, response.values);
            angular.forEach(response.value, function (bundle) {
                var location = bundle["Location"];
                $scope.bundlesByLocation[location] = bundle;
            });
        }
        ;

        function updateTableContents() {
            var featureMbean = Karaf.getSelectionFeaturesMBean(workspace);
            var bundleMbean = Osgi.getSelectionBundleMBean(workspace);
            var jolokia = workspace.jolokia;

            if (bundleMbean) {
                setBundles(jolokia.request({ type: 'exec', mbean: bundleMbean, operation: 'listBundles()' }));
            }

            if (featureMbean) {
                jolokia.request({ type: 'read', mbean: featureMbean }, onSuccess(populateTable));
            }
        }

        function addBundleDetails(feature) {
            var bundleDetails = [];
            angular.forEach(feature["Bundles"], function (bundleLocation) {
                var bundle = $scope.bundlesByLocation[bundleLocation];
                if (bundle) {
                    bundle["Installed"] = true;
                    bundleDetails.push(bundle);
                } else {
                    bundleDetails.push({
                        "Location": bundleLocation,
                        "Installed": false
                    });
                }
            });
            feature["BundleDetails"] = bundleDetails;
        }
    }
    Karaf.FeatureController = FeatureController;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function ScrComponentController($scope, $location, workspace, jolokia, $routeParams) {
        $scope.name = $routeParams.name;
        populateTable();

        function populateTable() {
            $scope.row = Karaf.getComponentByName(workspace, jolokia, $scope.name);
            Core.$apply($scope);
        }

        $scope.activate = function () {
            Karaf.activateComponent(workspace, jolokia, $scope.row['Name'], function () {
                console.log("Activated!");
            }, function () {
                console.log("Failed to activate!");
            });
        };

        $scope.deactivate = function () {
            Karaf.deactivateComponent(workspace, jolokia, $scope.row['Name'], function () {
                console.log("Deactivated!");
            }, function () {
                console.log("Failed to deactivate!");
            });
        };
    }
    Karaf.ScrComponentController = ScrComponentController;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function NavBarController($scope, workspace) {
        $scope.hash = workspace.hash();

        $scope.isKarafEnabled = workspace.treeContainsDomainAndProperties("org.apache.karaf");
        $scope.isFeaturesEnabled = Karaf.getSelectionFeaturesMBean(workspace);
        $scope.isScrEnabled = Karaf.getSelectionScrMBean(workspace);

        $scope.$on('$routeChangeSuccess', function () {
            $scope.hash = workspace.hash();
        });

        $scope.isActive = function (nav) {
            return workspace.isLinkActive(nav);
        };

        $scope.isPrefixActive = function (nav) {
            return workspace.isLinkPrefixActive(nav);
        };
    }
    Karaf.NavBarController = NavBarController;
})(Karaf || (Karaf = {}));
/**
* @module Karaf
*/
var Karaf;
(function (Karaf) {
    function FeaturesController($scope, $location, workspace, jolokia) {
        $scope.hasFabric = Fabric.hasFabric(workspace);
        $scope.responseJson = '';
        $scope.filter = '';

        $scope.installedFeatures = [];

        $scope.features = [];
        $scope.repositories = [];
        $scope.selectedRepositoryId = '';
        $scope.selectedRepository = {};

        $scope.newRepositoryURI = '';

        $scope.init = function () {
            var selectedRepositoryId = $location.search()['repositoryId'];
            if (selectedRepositoryId) {
                $scope.selectedRepositoryId = selectedRepositoryId;
            }

            var filter = $location.search()['filter'];
            if (filter) {
                $scope.filter = filter;
            }
        };

        $scope.init();

        $scope.$watch('selectedRepository', function (newValue, oldValue) {
            //log.debug("selectedRepository: ", $scope.selectedRepository);
            if (newValue !== oldValue) {
                if (!newValue) {
                    $scope.selectedRepositoryId = '';
                } else {
                    $scope.selectedRepositoryId = newValue['repository'];
                }
                $location.search('repositoryId', $scope.selectedRepositoryId);
            }
        }, true);

        $scope.$watch('filter', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $location.search('filter', newValue);
            }
        });

        var featuresMBean = Karaf.getSelectionFeaturesMBean(workspace);

        Karaf.log.debug("Features mbean: ", featuresMBean);

        if (featuresMBean) {
            Core.register(jolokia, $scope, {
                type: 'read', mbean: featuresMBean
            }, onSuccess(render));
        }

        $scope.inSelectedRepository = function (feature) {
            if (!$scope.selectedRepository || !('repository' in $scope.selectedRepository)) {
                return "";
            }
            if (!feature || !('RepositoryName' in feature)) {
                return "";
            }
            if (feature['RepositoryName'] === $scope.selectedRepository['repository']) {
                return "in-selected-repository";
            }
            return "";
        };

        $scope.isValidRepository = function () {
            return Core.isBlank($scope.newRepositoryURI);
        };

        $scope.installRepository = function () {
            var repoURL = $scope.newRepositoryURI;
            notification('info', 'Adding feature repository URL');
            Karaf.installRepository(workspace, jolokia, repoURL, function () {
                notification('success', 'Added feature repository URL');
                $scope.selectedRepository = {};
                $scope.selectedRepositoryId = '';
                $scope.responseJson = null;
                $scope.triggerRefresh();
            }, function (response) {
                Karaf.log.error('Failed to add feature repository URL ', repoURL, ' due to ', response.error);
                Karaf.log.info('stack trace: ', response.stacktrace);
                Core.$apply($scope);
            });
        };

        $scope.uninstallRepository = function () {
            var repoURI = $scope.selectedRepository['uri'];
            notification('info', 'Removing feature repository ' + repoURI);
            Karaf.uninstallRepository(workspace, jolokia, repoURI, function () {
                notification('success', 'Removed feature repository ' + repoURI);
                $scope.responseJson = null;
                $scope.selectedRepositoryId = '';
                $scope.selectedRepository = {};
                $scope.triggerRefresh();
            }, function (response) {
                Karaf.log.error('Failed to remove feature repository ', repoURI, ' due to ', response.error);
                Karaf.log.info('stack trace: ', response.stacktrace);
                Core.$apply($scope);
            });
        };

        $scope.triggerRefresh = function () {
            jolokia.request({
                type: 'read',
                method: 'POST',
                mbean: featuresMBean
            }, onSuccess(render));
        };

        $scope.install = function (feature) {
            if ($scope.hasFabric) {
                return;
            }

            //$('.popover').remove();
            notification('info', 'Installing feature ' + feature.Name);
            Karaf.installFeature(workspace, jolokia, feature.Name, feature.Version, function () {
                notification('success', 'Installed feature ' + feature.Name);
                $scope.installedFeatures.add(feature);
                $scope.responseJson = null;
                $scope.triggerRefresh();
                //Core.$apply($scope);
            }, function (response) {
                Karaf.log.error('Failed to install feature ', feature.Name, ' due to ', response.error);
                Karaf.log.info('stack trace: ', response.stacktrace);
                Core.$apply($scope);
            });
        };

        $scope.uninstall = function (feature) {
            if ($scope.hasFabric) {
                return;
            }

            //$('.popover').remove();
            notification('info', 'Uninstalling feature ' + feature.Name);
            Karaf.uninstallFeature(workspace, jolokia, feature.Name, feature.Version, function () {
                notification('success', 'Uninstalled feature ' + feature.Name);
                $scope.installedFeatures.remove(feature);
                $scope.responseJson = null;
                $scope.triggerRefresh();
                //Core.$apply($scope);
            }, function (response) {
                Karaf.log.error('Failed to uninstall feature ', feature.Name, ' due to ', response.error);
                Karaf.log.info('stack trace: ', response.stacktrace);
                Core.$apply($scope);
            });
        };

        $scope.filteredRows = ['Bundles', 'Configurations', 'Configuration Files', 'Dependencies'];

        $scope.showRow = function (key, value) {
            if ($scope.filteredRows.any(key)) {
                return false;
            }

            if (angular.isArray(value)) {
                if (value.length === 0) {
                    return false;
                }
            }

            if (angular.isString(value)) {
                if (Core.isBlank(value)) {
                    return false;
                }
            }

            if (angular.isObject(value)) {
                if (!value || Object.equal(value, {})) {
                    return false;
                }
            }

            return true;
        };

        $scope.installed = function (installed) {
            var answer = Core.parseBooleanValue(installed);
            return answer;
        };

        $scope.showValue = function (value) {
            if (angular.isArray(value)) {
                var answer = ['<ul class="zebra-list">'];
                value.forEach(function (v) {
                    answer.push('<li>' + v + '</li>');
                });
                answer.push('</ul>');
                return answer.join('\n');
            }
            if (angular.isObject(value)) {
                var answer = ['<table class="table">', '<tbody>'];

                angular.forEach(value, function (value, key) {
                    answer.push('<tr>');
                    answer.push('<td>' + key + '</td>');
                    answer.push('<td>' + value + '</td>');
                    answer.push('</tr>');
                });

                answer.push('</tbody>');
                answer.push('</table>');

                return answer.join('\n');
            }
            return "" + value;
        };

        $scope.getStateStyle = function (feature) {
            if (Core.parseBooleanValue(feature.Installed)) {
                return "badge badge-success";
            }
            return "badge";
        };

        $scope.filterFeature = function (feature) {
            if (Core.isBlank($scope.filter)) {
                return true;
            }
            if (feature.Id.has($scope.filter)) {
                return true;
            }
            return false;
        };

        function render(response) {
            var responseJson = angular.toJson(response.value);
            if ($scope.responseJson !== responseJson) {
                $scope.responseJson = responseJson;

                //log.debug("Got response: ", response.value);
                if (response['value']['Features'] === null) {
                    $scope.featuresError = true;
                } else {
                    $scope.featuresError = false;
                }

                $scope.features = [];
                $scope.repositories = [];

                var features = [];
                var repositories = [];

                Karaf.populateFeaturesAndRepos(response.value, features, repositories);

                var installedFeatures = features.filter(function (f) {
                    return Core.parseBooleanValue(f.Installed);
                });
                var uninstalledFeatures = features.filter(function (f) {
                    return !Core.parseBooleanValue(f.Installed);
                });

                //log.debug("repositories: ", repositories);
                $scope.installedFeatures = installedFeatures.sortBy(function (f) {
                    return f['Name'];
                });
                uninstalledFeatures = uninstalledFeatures.sortBy(function (f) {
                    return f['Name'];
                });

                repositories.sortBy('id').forEach(function (repo) {
                    $scope.repositories.push({
                        repository: repo['id'],
                        uri: repo['uri'],
                        features: uninstalledFeatures.filter(function (f) {
                            return f['RepositoryName'] === repo['id'];
                        })
                    });
                });

                if (!Core.isBlank($scope.newRepositoryURI)) {
                    var selectedRepo = repositories.find(function (r) {
                        return r['uri'] === $scope.newRepositoryURI;
                    });
                    if (selectedRepo) {
                        $scope.selectedRepositoryId = selectedRepo['id'];
                    }
                    $scope.newRepositoryURI = '';
                }

                if (Core.isBlank($scope.selectedRepositoryId)) {
                    $scope.selectedRepository = $scope.repositories.first();
                } else {
                    $scope.selectedRepository = $scope.repositories.find(function (r) {
                        return r.repository === $scope.selectedRepositoryId;
                    });
                }

                Core.$apply($scope);
            }
        }
    }
    Karaf.FeaturesController = FeaturesController;
})(Karaf || (Karaf = {}));
/**
* @module Dozer
* @main Dozer
*/
var Dozer;
(function (Dozer) {
    /**
    * The JMX domain for Dozer
    * @property jmxDomain
    * @for Dozer
    * @type String
    */
    Dozer.jmxDomain = 'net.sourceforge.dozer';

    Dozer.introspectorMBean = "hawtio:type=Introspector";

    /**
    * Don't try and load properties for these types
    * @property excludedPackages
    * @for Dozer
    * @type {Array}
    */
    Dozer.excludedPackages = [
        'java.lang',
        'int',
        'double',
        'long'
    ];

    /**
    * Lets map the class names to element names
    * @property elementNameMappings
    * @for Dozer
    * @type {Array}
    */
    Dozer.elementNameMappings = {
        "Mapping": "mapping",
        "MappingClass": "class",
        "Field": "field"
    };

    Dozer.log = Logger.get("Dozer");

    /**
    * Converts the XML string or DOM node to a Dozer model
    * @method loadDozerModel
    * @for Dozer
    * @static
    * @param {Object} xml
    * @param {String} pageId
    * @return {Mappings}
    */
    function loadDozerModel(xml, pageId) {
        var doc = xml;
        if (angular.isString(xml)) {
            doc = $.parseXML(xml);
        }
        console.log("Has Dozer XML document: " + doc);

        var model = new Dozer.Mappings(doc);
        var mappingsElement = doc.documentElement;
        copyAttributes(model, mappingsElement);

        $(mappingsElement).children("mapping").each(function (idx, element) {
            var mapping = createMapping(element);
            model.mappings.push(mapping);
        });

        return model;
    }
    Dozer.loadDozerModel = loadDozerModel;

    function saveToXmlText(model) {
        // lets copy the original doc then replace the mapping elements
        var element = model.doc.documentElement.cloneNode(false);
        appendElement(model.mappings, element, null, 1);
        Dozer.addTextNode(element, "\n");
        var xmlText = Core.xmlNodeToString(element);
        return '<?xml version="1.0" encoding="UTF-8"?>\n' + xmlText;
    }
    Dozer.saveToXmlText = saveToXmlText;

    function findUnmappedFields(workspace, mapping, fn) {
        // lets find the fields which are unmapped
        var className = mapping.class_a.value;
        findProperties(workspace, className, null, function (properties) {
            var answer = [];
            angular.forEach(properties, function (property) {
                console.log("got property " + JSON.stringify(property, null, "  "));
                var name = property.name;
                if (name) {
                    if (mapping.hasFromField(name)) {
                        // ignore this one
                    } else {
                        // TODO auto-detect this property name in the to classes?
                        answer.push(new Dozer.UnmappedField(name, property));
                    }
                }
            });
            fn(answer);
        });
    }
    Dozer.findUnmappedFields = findUnmappedFields;

    /**
    * Finds the properties on the given class and returns them; and either invokes the given function
    * or does a sync request and returns them
    * @method findProperties
    * @for Dozer
    * @static
    * @param {Core.Workspace} workspace
    * @param {String} className
    * @param {String} filter
    * @param {Function} fn
    * @return {any}
    */
    function findProperties(workspace, className, filter, fn) {
        if (typeof filter === "undefined") { filter = null; }
        if (typeof fn === "undefined") { fn = null; }
        var mbean = getIntrospectorMBean(workspace);
        if (mbean) {
            if (filter) {
                return workspace.jolokia.execute(mbean, "findProperties", className, filter, onSuccess(fn));
            } else {
                return workspace.jolokia.execute(mbean, "getProperties", className, onSuccess(fn));
            }
        } else {
            if (fn) {
                return fn([]);
            } else {
                return [];
            }
        }
    }
    Dozer.findProperties = findProperties;

    /**
    * Finds class names matching the given search text and either invokes the function with the results
    * or does a sync request and returns them.
    * @method findClassNames
    * @for Dozer
    * @static
    * @param {Core.Workspace} workspace
    * @param {String} searchText
    * @param {Number} limit @default 20
    * @param {Function} fn
    * @return {any}
    */
    function findClassNames(workspace, searchText, limit, fn) {
        if (typeof limit === "undefined") { limit = 20; }
        if (typeof fn === "undefined") { fn = null; }
        var mbean = getIntrospectorMBean(workspace);
        if (mbean) {
            return workspace.jolokia.execute(mbean, "findClassNames", searchText, limit, onSuccess(fn));
        } else {
            if (fn) {
                return fn([]);
            } else {
                return [];
            }
        }
    }
    Dozer.findClassNames = findClassNames;

    function getIntrospectorMBean(workspace) {
        // lets hard code this so its easy to use in any JVM
        return Dozer.introspectorMBean;
        // return Core.getMBeanTypeObjectName(workspace, "hawtio", "Introspector");
    }
    Dozer.getIntrospectorMBean = getIntrospectorMBean;

    function loadModelFromTree(rootTreeNode, oldModel) {
        oldModel.mappings = [];
        angular.forEach(rootTreeNode.childList, function (treeNode) {
            var mapping = Core.pathGet(treeNode, ["data", "entity"]);
            if (mapping) {
                oldModel.mappings.push(mapping);
            }
        });
        return oldModel;
    }
    Dozer.loadModelFromTree = loadModelFromTree;

    function createDozerTree(model) {
        var id = "mappings";
        var folder = new Folder(id);
        folder.addClass = "net-sourceforge-dozer-mappings";
        folder.domain = Dozer.jmxDomain;
        folder.typeName = "mappings";
        folder.entity = model;

        folder.key = Core.toSafeDomID(id);

        angular.forEach(model.mappings, function (mapping) {
            var mappingFolder = createMappingFolder(mapping, folder);
            folder.children.push(mappingFolder);
        });
        return folder;
    }
    Dozer.createDozerTree = createDozerTree;

    function createMappingFolder(mapping, parentFolder) {
        var mappingName = mapping.name();
        var mappingFolder = new Folder(mappingName);
        mappingFolder.addClass = "net-sourceforge-dozer-mapping";
        mappingFolder.typeName = "mapping";
        mappingFolder.domain = Dozer.jmxDomain;
        mappingFolder.key = (parentFolder ? parentFolder.key + "_" : "") + Core.toSafeDomID(mappingName);
        mappingFolder.parent = parentFolder;
        mappingFolder.entity = mapping;
        mappingFolder.icon = url("/app/dozer/img/class.gif");

        /*
        mappingFolder.tooltip = nodeSettings["tooltip"] || nodeSettings["description"] || id;
        */
        angular.forEach(mapping.fields, function (field) {
            addMappingFieldFolder(field, mappingFolder);
        });
        return mappingFolder;
    }
    Dozer.createMappingFolder = createMappingFolder;

    function addMappingFieldFolder(field, mappingFolder) {
        var name = field.name();
        var fieldFolder = new Folder(name);
        fieldFolder.addClass = "net-sourceforge-dozer-field";
        fieldFolder.typeName = "field";
        fieldFolder.domain = Dozer.jmxDomain;
        fieldFolder.key = mappingFolder.key + "_" + Core.toSafeDomID(name);
        fieldFolder.parent = mappingFolder;
        fieldFolder.entity = field;
        fieldFolder.icon = url("/app/dozer/img/attribute.gif");

        /*
        fieldFolder.tooltip = nodeSettings["tooltip"] || nodeSettings["description"] || id;
        */
        mappingFolder.children.push(fieldFolder);
        return fieldFolder;
    }
    Dozer.addMappingFieldFolder = addMappingFieldFolder;

    function createMapping(element) {
        var mapping = new Dozer.Mapping();
        var elementJQ = $(element);
        mapping.class_a = createMappingClass(elementJQ.children("class-a"));
        mapping.class_b = createMappingClass(elementJQ.children("class-b"));
        elementJQ.children("field").each(function (idx, fieldElement) {
            var field = createField(fieldElement);
            mapping.fields.push(field);
        });
        copyAttributes(mapping, element);
        return mapping;
    }

    function createField(element) {
        if (element) {
            var jqe = $(element);
            var a = jqe.children("a").text();
            var b = jqe.children("b").text();
            var field = new Dozer.Field(new Dozer.FieldDefinition(a), new Dozer.FieldDefinition(b));
            copyAttributes(field, element);
            return field;
        }
        return new Dozer.Field(new Dozer.FieldDefinition(""), new Dozer.FieldDefinition(""));
    }

    function createMappingClass(jqElement) {
        if (jqElement && jqElement[0]) {
            var element = jqElement[0];
            var text = element.textContent;
            if (text) {
                var mappingClass = new Dozer.MappingClass(text);
                copyAttributes(mappingClass, element);
                return mappingClass;
            }
        }

        // lets create a default empty mapping
        return new Dozer.MappingClass("");
    }

    function copyAttributes(object, element) {
        var attributeMap = element.attributes;
        for (var i = 0; i < attributeMap.length; i++) {
            // TODO hacky work around for compiler issue ;)
            //var attr = attributeMap.item(i);
            var attMap = attributeMap;
            var attr = attMap.item(i);
            if (attr) {
                var name = attr.localName;
                var value = attr.value;
                if (name && !name.startsWith("xmlns")) {
                    var safeName = Forms.safeIdentifier(name);
                    object[safeName] = value;
                }
            }
        }
    }

    function appendAttributes(object, element, ignorePropertyNames) {
        angular.forEach(object, function (value, key) {
            if (ignorePropertyNames.any(key)) {
                //console.log("Ignored key " + key);
            } else {
                // lets add an attribute value
                if (value) {
                    var text = value.toString();

                    // lets replace any underscores with dashes
                    var name = key.replace(/_/g, '-');
                    element.setAttribute(name, text);
                }
            }
        });
    }
    Dozer.appendAttributes = appendAttributes;

    /**
    * Adds a new child element for this mapping to the given element
    * @method appendElement
    * @for Dozer
    * @static
    * @param {any} object
    * @param {any} element
    * @param {String} elementName
    * @param {Number} indentLevel
    * @return the last child element created
    */
    function appendElement(object, element, elementName, indentLevel) {
        if (typeof elementName === "undefined") { elementName = null; }
        if (typeof indentLevel === "undefined") { indentLevel = 0; }
        var answer = null;
        if (angular.isArray(object)) {
            angular.forEach(object, function (child) {
                answer = appendElement(child, element, elementName, indentLevel);
            });
        } else if (object) {
            if (!elementName) {
                var className = Core.pathGet(object, ["constructor", "name"]);
                if (!className) {
                    console.log("WARNING: no class name for value " + object);
                } else {
                    elementName = Dozer.elementNameMappings[className];
                    if (!elementName) {
                        console.log("WARNING: could not map class name " + className + " to an XML element name");
                    }
                }
            }
            if (elementName) {
                if (indentLevel) {
                    var text = indentText(indentLevel);
                    Dozer.addTextNode(element, text);
                }
                var doc = element.ownerDocument || document;
                var child = doc.createElement(elementName);

                // navigate child properties...
                var fn = object.saveToElement;
                if (fn) {
                    fn.apply(object, [child]);
                } else {
                    angular.forEach(object, function (value, key) {
                        console.log("has key " + key + " value " + value);
                    });
                }

                // if we have any element children then add newline text node
                if ($(child).children().length) {
                    //var text = indentText(indentLevel - 1);
                    var text = indentText(indentLevel);
                    Dozer.addTextNode(child, text);
                }
                element.appendChild(child);
                answer = child;
            }
        }
        return answer;
    }
    Dozer.appendElement = appendElement;

    function nameOf(object) {
        var text = angular.isObject(object) ? object["value"] : null;
        if (!text && angular.isString(object)) {
            text = object;
        }
        return text || "?";
    }
    Dozer.nameOf = nameOf;

    function addTextNode(element, text) {
        if (text) {
            var doc = element.ownerDocument || document;
            var child = doc.createTextNode(text);
            element.appendChild(child);
        }
    }
    Dozer.addTextNode = addTextNode;

    function indentText(indentLevel) {
        var text = "\n";
        for (var i = 0; i < indentLevel; i++) {
            text += "  ";
        }
        return text;
    }
})(Dozer || (Dozer = {}));
/**
* @module Dozer
*/
var Dozer;
(function (Dozer) {
    /**
    * @class Mappings
    */
    var Mappings = (function () {
        function Mappings(doc, mappings) {
            if (typeof mappings === "undefined") { mappings = []; }
            this.doc = doc;
            this.mappings = mappings;
        }
        return Mappings;
    })();
    Dozer.Mappings = Mappings;

    /**
    * @class Mapping
    */
    var Mapping = (function () {
        function Mapping() {
            this.fields = [];
            this.map_id = Core.getUUID();
            this.class_a = new MappingClass('');
            this.class_b = new MappingClass('');
        }
        Mapping.prototype.name = function () {
            return Dozer.nameOf(this.class_a) + " -> " + Dozer.nameOf(this.class_b);
        };

        Mapping.prototype.hasFromField = function (name) {
            return this.fields.find(function (f) {
                return name === f.a.value;
            });
        };

        Mapping.prototype.hasToField = function (name) {
            return this.fields.find(function (f) {
                return name === f.b.value;
            });
        };

        Mapping.prototype.saveToElement = function (element) {
            Dozer.appendElement(this.class_a, element, "class-a", 2);
            Dozer.appendElement(this.class_b, element, "class-b", 2);
            Dozer.appendElement(this.fields, element, "field", 2);
            Dozer.appendAttributes(this, element, ["class_a", "class_b", "fields"]);
        };
        return Mapping;
    })();
    Dozer.Mapping = Mapping;

    /**
    * @class MappingClass
    */
    var MappingClass = (function () {
        function MappingClass(value) {
            this.value = value;
        }
        MappingClass.prototype.saveToElement = function (element) {
            Dozer.addTextNode(element, this.value);
            Dozer.appendAttributes(this, element, ["value", "properties", "error"]);
        };
        return MappingClass;
    })();
    Dozer.MappingClass = MappingClass;

    /**
    * @class Field
    */
    var Field = (function () {
        function Field(a, b) {
            this.a = a;
            this.b = b;
        }
        Field.prototype.name = function () {
            return Dozer.nameOf(this.a) + " -> " + Dozer.nameOf(this.b);
        };

        Field.prototype.saveToElement = function (element) {
            Dozer.appendElement(this.a, element, "a", 3);
            Dozer.appendElement(this.b, element, "b", 3);
            Dozer.appendAttributes(this, element, ["a", "b"]);
        };
        return Field;
    })();
    Dozer.Field = Field;

    /**
    * @class FieldDefinition
    */
    var FieldDefinition = (function () {
        function FieldDefinition(value) {
            this.value = value;
        }
        FieldDefinition.prototype.saveToElement = function (element) {
            Dozer.addTextNode(element, this.value);
            Dozer.appendAttributes(this, element, ["value", "properties", "error"]);
        };
        return FieldDefinition;
    })();
    Dozer.FieldDefinition = FieldDefinition;

    /**
    * @class UnmappedField
    */
    var UnmappedField = (function () {
        function UnmappedField(fromField, property, toField) {
            if (typeof toField === "undefined") { toField = null; }
            this.fromField = fromField;
            this.property = property;
            this.toField = toField;
        }
        return UnmappedField;
    })();
    Dozer.UnmappedField = UnmappedField;
})(Dozer || (Dozer = {}));
/**
* @module Dozer
*/
var Dozer;
(function (Dozer) {
    /**
    * Configures the JSON schemas to improve the UI models
    * @method schemaConfigure
    * @for Dozer
    */
    function schemaConfigure() {
        io_hawt_dozer_schema_Field["tabs"] = {
            'Fields': ['a.value', 'b.value'],
            'From Field': ['a\\..*'],
            'To Field': ['b\\..*'],
            'Field Configuration': ['*']
        };
        io_hawt_dozer_schema_Mapping["tabs"] = {
            'Classes': ['class-a.value', 'class-b.value'],
            'From Class': ['class-a\\..*'],
            'To Class': ['class-b\\..*'],
            'Class Configuration': ['*']
        };

        // hide the fields table from the class configuration tab
        io_hawt_dozer_schema_Mapping.properties.fieldOrFieldExclude.hidden = true;

        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "a", "properties", "value", "label"], "From Field");
        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "b", "properties", "value", "label"], "To Field");

        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-a", "properties", "value", "label"], "From Class");
        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-b", "properties", "value", "label"], "To Class");

        // ignore prefixes in the generated labels
        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "a", "ignorePrefixInLabel"], true);
        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "b", "ignorePrefixInLabel"], true);
        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-a", "ignorePrefixInLabel"], true);
        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-b", "ignorePrefixInLabel"], true);

        // add custom widgets
        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-a", "properties", "value", "formTemplate"], classNameWidget("class_a"));
        Core.pathSet(io_hawt_dozer_schema_Mapping, ["properties", "class-b", "properties", "value", "formTemplate"], classNameWidget("class_b"));

        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "a", "properties", "value", "formTemplate"], '<input type="text" ng-model="dozerEntity.a.value" ' + 'typeahead="title for title in fromFieldNames($viewValue) | filter:$viewValue" ' + 'typeahead-editable="true"  title="The Java class name"/>');
        Core.pathSet(io_hawt_dozer_schema_Field, ["properties", "b", "properties", "value", "formTemplate"], '<input type="text" ng-model="dozerEntity.b.value" ' + 'typeahead="title for title in toFieldNames($viewValue) | filter:$viewValue" ' + 'typeahead-editable="true"  title="The Java class name"/>');

        function classNameWidget(propertyName) {
            return '<input type="text" ng-model="dozerEntity.' + propertyName + '.value" ' + 'typeahead="title for title in classNames($viewValue) | filter:$viewValue" ' + 'typeahead-editable="true"  title="The Java class name"/>';
        }
    }
    Dozer.schemaConfigure = schemaConfigure;
})(Dozer || (Dozer = {}));
/**
* @module Threads
* @main Threads
*/
var Threads;
(function (Threads) {
    Threads.pluginName = 'threads';
    Threads.templatePath = 'app/threads/html/';
    Threads.log = Logger.get("Threads");
    Threads.jmxDomain = 'java.lang';
    Threads.mbeanType = 'Threading';
    Threads.mbean = Threads.jmxDomain + ":type=" + Threads.mbeanType;

    Threads.angularModule = angular.module(Threads.pluginName, ['bootstrap', 'ngResource', 'hawtioCore', 'ui']);

    Threads.angularModule.config(function ($routeProvider) {
        $routeProvider.when('/threads', { templateUrl: Threads.templatePath + 'index.html' });
    });

    Threads.angularModule.run(function ($location, workspace, viewRegistry, layoutFull, helpRegistry) {
        viewRegistry['threads'] = layoutFull;
        helpRegistry.addUserDoc('threads', 'app/threads/doc/help.md');

        workspace.topLevelTabs.push({
            id: "threads",
            content: "Threads",
            title: "JVM Threads",
            isValid: function (workspace) {
                return workspace.treeContainsDomainAndProperties(Threads.jmxDomain, { type: Threads.mbeanType });
            },
            href: function () {
                return "#/threads";
            },
            isActive: function (workspace) {
                return workspace.isTopTabActive("threads");
            }
        });
    });

    hawtioPluginLoader.addModule(Threads.pluginName);
})(Threads || (Threads = {}));
/**
* @module Threads
*/
var Threads;
(function (Threads) {
    function ThreadsController($scope, $routeParams, $templateCache, jolokia) {
        $scope.selectedRowJson = '';

        $scope.lastThreadJson = '';
        $scope.getThreadInfoResponseJson = '';
        $scope.threads = [];
        $scope.totals = {};
        $scope.support = {};

        $scope.row = {};
        $scope.threadSelected = false;
        $scope.selectedRowIndex = -1;

        $scope.stateFilter = 'NONE';

        $scope.showRaw = {
            expanded: false
        };

        $scope.addToDashboardLink = function () {
            var href = "#/threads";
            var size = angular.toJson({
                size_x: 8,
                size_y: 2
            });
            var title = "Threads";
            return "#/dashboard/add?tab=dashboard&href=" + encodeURIComponent(href) + "&title=" + encodeURIComponent(title) + "&size=" + encodeURIComponent(size);
        };

        $scope.isInDashboardClass = function () {
            if (angular.isDefined($scope.inDashboard && $scope.inDashboard)) {
                return "threads-dashboard";
            }
            return "threads logbar";
        };

        $scope.$watch('searchFilter', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                $scope.threadGridOptions.filterOptions.filterText = newValue;
            }
        });

        $scope.$watch('stateFilter', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if ($scope.stateFilter === 'NONE') {
                    $scope.threads = $scope.unfilteredThreads;
                } else {
                    $scope.threads = $scope.filterThreads($scope.stateFilter, $scope.unfilteredThreads);
                }
            }
        });

        $scope.threadGridOptions = {
            selectedItems: [],
            data: 'threads',
            showSelectionCheckbox: false,
            enableRowClickSelection: true,
            multiSelect: false,
            primaryKeyFn: function (entity, idx) {
                return entity.threadId;
            },
            filterOptions: {
                filterText: ''
            },
            sortInfo: {
                sortBy: 'threadId',
                ascending: false
            },
            columnDefs: [
                {
                    field: 'threadId',
                    displayName: 'ID'
                },
                {
                    field: 'threadState',
                    displayName: 'State',
                    cellTemplate: $templateCache.get("threadStateTemplate")
                },
                {
                    field: 'threadName',
                    displayName: 'Name'
                },
                {
                    field: 'waitedTime',
                    displayName: 'Waited Time',
                    cellTemplate: '<div class="ngCellText" ng-show="row.entity.waitedTime > 0">{{row.entity.waitedTime | humanizeMs}}</div>'
                },
                {
                    field: 'blockedTime',
                    displayName: 'Blocked Time',
                    cellTemplate: '<div class="ngCellText" ng-show="row.entity.blockedTime > 0">{{row.entity.blockedTime | humanizeMs}}</div>'
                },
                {
                    field: 'inNative',
                    displayName: 'Native',
                    cellTemplate: '<div class="ngCellText"><span ng-show="row.entity.inNative" class="orange">(in native)</span></div>'
                },
                {
                    field: 'suspended',
                    displayName: 'Suspended',
                    cellTemplate: '<div class="ngCellText"><span ng-show="row.entity.suspended" class="red">(suspended)</span></div>'
                }
            ]
        };

        $scope.$watch('threadGridOptions.selectedItems', function (newValue, oldValue) {
            if (newValue !== oldValue) {
                if (newValue.length === 0) {
                    $scope.row = {};
                    $scope.threadSelected = false;
                    $scope.selectedRowIndex = -1;
                } else {
                    $scope.row = newValue.first();
                    $scope.threadSelected = true;
                    $scope.selectedRowIndex = Core.pathGet($scope, ['hawtioSimpleTable', 'threads', 'rows']).findIndex(function (t) {
                        return t.entity['threadId'] === $scope.row['threadId'];
                    });
                }
                $scope.selectedRowJson = angular.toJson($scope.row, true);
            }
        }, true);

        $scope.filterOn = function (state) {
            $scope.stateFilter = state;
        };

        $scope.filterThreads = function (state, threads) {
            Threads.log.debug("Filtering threads by: ", state);
            if (state === 'NONE') {
                return threads;
            }
            return threads.filter(function (t) {
                return t && t['threadState'] === state;
            });
        };

        $scope.selectedFilterClass = function (state) {
            if (state === $scope.stateFilter) {
                return "active";
            } else {
                return "";
            }
        };

        $scope.deselect = function () {
            $scope.threadGridOptions.selectedItems = [];
        };

        $scope.selectThreadById = function (id) {
            $scope.threadGridOptions.selectedItems = $scope.threads.filter(function (t) {
                return t.threadId === id;
            });
        };

        $scope.selectThreadByIndex = function (idx) {
            var selectedThread = Core.pathGet($scope, ['hawtioSimpleTable', 'threads', 'rows'])[idx];
            $scope.threadGridOptions.selectedItems = $scope.threads.filter(function (t) {
                return t && t['threadId'] == selectedThread.entity['threadId'];
            });
        };

        $scope.init = function () {
            jolokia.request([
                {
                    type: 'read',
                    mbean: Threads.mbean,
                    attribute: 'ThreadContentionMonitoringSupported'
                }, {
                    type: 'read',
                    mbean: Threads.mbean,
                    attribute: 'ObjectMonitorUsageSupported'
                }, {
                    type: 'read',
                    mbean: Threads.mbean,
                    attribute: 'SynchronizerUsageSupported'
                }], {
                method: 'post',
                success: [
                    function (response) {
                        $scope.support.threadContentionMonitoringSupported = response.value;
                        Threads.log.debug("ThreadContentionMonitoringSupported: ", $scope.support.threadContentionMonitoringSupported);
                        $scope.maybeRegister();
                    },
                    function (response) {
                        $scope.support.objectMonitorUsageSupported = response.value;
                        Threads.log.debug("ObjectMonitorUsageSupported: ", $scope.support.objectMonitorUsageSupported);
                        $scope.maybeRegister();
                    },
                    function (response) {
                        $scope.support.synchronizerUsageSupported = response.value;
                        Threads.log.debug("SynchronizerUsageSupported: ", $scope.support.synchronizerUsageSupported);
                        $scope.maybeRegister();
                    }],
                error: function (response) {
                    Threads.log.error('Failed to query for supported usages: ', response.error);
                }
            });
        };

        var initFunc = Core.throttled($scope.init, 500);

        $scope.maybeRegister = function () {
            if ('objectMonitorUsageSupported' in $scope.support && 'synchronizerUsageSupported' in $scope.support && 'threadContentionMonitoringSupported' in $scope.support) {
                Threads.log.debug("Registering dumpAllThreads polling");
                Core.register(jolokia, $scope, {
                    type: 'exec',
                    mbean: Threads.mbean,
                    operation: 'dumpAllThreads',
                    arguments: [$scope.support.objectMonitorUsageSupported, $scope.support.synchronizerUsageSupported]
                }, onSuccess(render));

                if ($scope.support.threadContentionMonitoringSupported) {
                    // check and see if it's actually turned on, if not
                    // enable it
                    jolokia.request({
                        type: 'read',
                        mbean: Threads.mbean,
                        attribute: 'ThreadContentionMonitoringEnabled'
                    }, onSuccess($scope.maybeEnableThreadContentionMonitoring));
                }
            }
        };

        function disabledContentionMonitoring(response) {
            Threads.log.info("Disabled contention monitoring: ", response);
            Core.$apply($scope);
        }

        function enabledContentionMonitoring(response) {
            $scope.$on('$routeChangeStart', function () {
                jolokia.setAttribute(Threads.mbean, 'ThreadContentionMonitoringEnabled', false, onSuccess(disabledContentionMonitoring));
            });
            Threads.log.info("Enabled contention monitoring");
            Core.$apply($scope);
        }

        $scope.maybeEnableThreadContentionMonitoring = function (response) {
            if (response.value === false) {
                Threads.log.info("Thread contention monitoring not enabled, enabling");
                jolokia.setAttribute(Threads.mbean, 'ThreadContentionMonitoringEnabled', true, onSuccess(enabledContentionMonitoring));
            } else {
                Threads.log.info("Thread contention monitoring already enabled");
            }
            Core.$apply($scope);
        };

        $scope.getMonitorClass = function (name, value) {
            return value.toString();
        };

        $scope.getMonitorName = function (name) {
            name = name.replace('Supported', '');
            return name.titleize();
        };

        function render(response) {
            var responseJson = angular.toJson(response.value, true);
            if ($scope.getThreadInfoResponseJson !== responseJson) {
                $scope.getThreadInfoResponseJson = responseJson;

                var threads = response.value.exclude(function (t) {
                    return t === null;
                });

                $scope.unfilteredThreads = threads;
                $scope.totals = {};
                threads.forEach(function (t) {
                    // calculate totals
                    var state = t.threadState;
                    if (!(state in $scope.totals)) {
                        $scope.totals[state] = 1;
                    } else {
                        $scope.totals[state]++;
                    }
                });

                threads = $scope.filterThreads($scope.stateFilter, threads);

                $scope.threads = threads;
                Core.$apply($scope);
            }
        }

        initFunc();
    }
    Threads.ThreadsController = ThreadsController;
})(Threads || (Threads = {}));
