<commit_before>/**
 * @module Fabric
 */
module Fabric {

  export var log:Logging.Logger = Logger.get("Fabric");

  export var jmxDomain = 'io.fabric8';

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

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

  export var schemaLookupMBean = schemaLookupDomain + ":type=" + schemaLookupType;

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

  export var jolokiaWebAppGroupId = jmxDomain + ".fabric-jolokia";

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

  export function canBootstrapFabric(workspace) {
    return hasClusterBootstrapManager(workspace);
  }

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

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

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

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

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

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

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

  export 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;
  }

  export function hasFabric(workspace):boolean{
    // 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));
  }

  /**
   * 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
   */
  export function initScope($scope, $location, jolokia, workspace) {

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

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

    $scope.isCurrentContainer = (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 = (container) => {
      if (!container) {
        return false;
      }
      if (Core.isBlank(container['jolokiaUrl'])) {
        return false;
      }

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

    $scope.refreshProfile = (versionId, profileId) => {
      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: () => {
          notification('success', 'Triggered refresh of profile ' + profileId + '/' + versionId);
          Core.$apply($scope);
        },
        error: (response) => {
          log.warn('Failed to trigger refresh for profile ' + profileId + '/' + versionId + ' due to: ', response.error);
          log.info("Stack trace: ", response.stacktrace);
          Core.$apply($scope);
        }
      })
    };

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

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

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

    $scope.createRequiredContainers = (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 = () => {
      var kind = null;
      // lets see if there is an openshift option
      var providers = registeredProviders(jolokia);
      angular.forEach(["openshift", "docker", "jclouds"], (value) => {
        if (!kind && providers[value]) {
          kind = value;
        }
      });
      if (!kind) {
        kind = 'child';
      }
      $location.url('/fabric/containers/createContainer').search('tab', kind);
    };

    $scope.createChildContainer = (container) => {
      $location.url('/fabric/containers/createContainer').search({ 'tab': 'child', 'parentId': container.id });
    };


    $scope.createChildContainer = (container) => {
      $location.url('/fabric/containers/createContainer').search({ 'tab': 'child', 'parentId': container.id });
    };

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

    $scope.getSelectedClass = (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 = (row) => {
      return Fabric.statusIcon(row);
    };


    $scope.isEnsembleContainer = (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: <PASSWORD>,
      container: null,
      view: null,

      onOK: () => {
        var userName = $scope.connect.userName;
        var password = $scope.connect.password;
        var container = $scope.connect.container;
        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(() => {
          $scope.connect.dialog.close();
          Core.$apply($scope);
        }, 100);
      }
    };

    $scope.doConnect = (container, view) => {
      // 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 || "/logs";

      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: () => {
        $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((c) => {
            doDeleteContainer($scope, jolokia, c.id);
          });

        } else {
          // bail...
          log.info("Asked to delete containers but no containerId or selectedContainers attributes available");
        }
      },
      open: () => {
        $scope.confirmDeleteDialog.dialog.open();
      },
      close: () => {
        $scope.confirmDeleteDialog.dialog.close();
      }
    };

    $scope.createVersionDialog = {
      dialog: new UI.Dialog(),
      newVersionName: "",

      open: () => {
        $scope.createVersionDialog.newVersionName = "";
        $scope.createVersionDialog.dialog.open();
      },
      onOk: () => {
        Fabric.doCreateVersion($scope, jolokia, $location, $scope.createVersionDialog.newVersionName);
        $scope.createVersionDialog.newVersionName = "";
        $scope.createVersionDialog.dialog.close();
      }
    };

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

    $scope.onVersionChange = (version) => {

      var containerIds = [];

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

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

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

  }


  export 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) {
      log.error("Failed to create version due to :", response.error);
      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);
    }

  }




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

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

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

  export 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;
  }

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

  export function gotoProfile(workspace, jolokia, localStorage, $location, versionId, profile:any) {
    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);
    }
  }

  export 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];
    }
  }

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

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

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

  export var urlResolvers = ['http:', 'ftp:', 'mvn:'];

  export function completeUri ($q, $scope, workspace, jolokia, something) {


  }

  export 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);
  }

  export function setContainerProperty(jolokia, containerId, property, value, success, error) {<commit_msg>don't gobble up stack traces if fabric operations fail; log them to the console/log<commit_after>/**
 * @module Fabric
 */
module Fabric {

  export var log:Logging.Logger = Logger.get("Fabric");

  export var jmxDomain = 'io.fabric8';

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

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

  export var schemaLookupMBean = schemaLookupDomain + ":type=" + schemaLookupType;

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

  export var jolokiaWebAppGroupId = jmxDomain + ".fabric-jolokia";

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

  export function canBootstrapFabric(workspace) {
    return hasClusterBootstrapManager(workspace);
  }

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

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

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

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

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

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

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

  export 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;
  }

  export function hasFabric(workspace):boolean{
    // 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));
  }

  /**
   * 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
   */
  export function initScope($scope, $location, jolokia, workspace) {

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

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

    $scope.isCurrentContainer = (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 = (container) => {
      if (!container) {
        return false;
      }
      if (Core.isBlank(container['jolokiaUrl'])) {
        return false;
      }

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

    $scope.refreshProfile = (versionId, profileId) => {
      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: () => {
          notification('success', 'Triggered refresh of profile ' + profileId + '/' + versionId);
          Core.$apply($scope);
        },
        error: (response) => {
          log.warn('Failed to trigger refresh for profile ' + profileId + '/' + versionId + ' due to: ', response.error);
          log.info("Stack trace: ", response.stacktrace);
          Core.$apply($scope);
        }
      })
    };

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

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

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

    $scope.createRequiredContainers = (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 = () => {
      var kind = null;
      // lets see if there is an openshift option
      var providers = registeredProviders(jolokia);
      angular.forEach(["openshift", "docker", "jclouds"], (value) => {
        if (!kind && providers[value]) {
          kind = value;
        }
      });
      if (!kind) {
        kind = 'child';
      }
      $location.url('/fabric/containers/createContainer').search('tab', kind);
    };

    $scope.createChildContainer = (container) => {
      $location.url('/fabric/containers/createContainer').search({ 'tab': 'child', 'parentId': container.id });
    };


    $scope.createChildContainer = (container) => {
      $location.url('/fabric/containers/createContainer').search({ 'tab': 'child', 'parentId': container.id });
    };

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

    $scope.getSelectedClass = (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 = (row) => {
      return Fabric.statusIcon(row);
    };


    $scope.isEnsembleContainer = (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: <PASSWORD>,
      container: null,
      view: null,

      onOK: () => {
        var userName = $scope.connect.userName;
        var password = $<PASSWORD>;
        var container = $scope.connect.container;
        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(() => {
          $scope.connect.dialog.close();
          Core.$apply($scope);
        }, 100);
      }
    };

    $scope.doConnect = (container, view) => {
      // 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 || "/logs";

      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: () => {
        $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((c) => {
            doDeleteContainer($scope, jolokia, c.id);
          });

        } else {
          // bail...
          log.info("Asked to delete containers but no containerId or selectedContainers attributes available");
        }
      },
      open: () => {
        $scope.confirmDeleteDialog.dialog.open();
      },
      close: () => {
        $scope.confirmDeleteDialog.dialog.close();
      }
    };

    $scope.createVersionDialog = {
      dialog: new UI.Dialog(),
      newVersionName: "",

      open: () => {
        $scope.createVersionDialog.newVersionName = "";
        $scope.createVersionDialog.dialog.open();
      },
      onOk: () => {
        Fabric.doCreateVersion($scope, jolokia, $location, $scope.createVersionDialog.newVersionName);
        $scope.createVersionDialog.newVersionName = "";
        $scope.createVersionDialog.dialog.close();
      }
    };

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

    $scope.onVersionChange = (version) => {

      var containerIds = [];

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

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

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

  }


  export 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) {
      log.error("Failed to create version due to :", response.error);
      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);
    }

  }




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

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

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

  export 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;
  }

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

  export function gotoProfile(workspace, jolokia, localStorage, $location, versionId, profile:any) {
    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);
    }
  }

  export 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];
    }
  }

  export function doDeleteContainer($scope, jolokia, name, onDelete:() => any = null) {
    notification('info', "Deleting " + name);
    destroyContainer(jolokia, name, () => {
      notification('success', "Deleted " + name);
      if (onDelete) {
        onDelete();
      }
      Core.$apply($scope);
    }, (response) => {
      notification('error', "Failed to delete " + name + " due to " + response.error);
      Core.defaultJolokiaErrorHandler(response);
      Core.$apply($scope);
    });
  }

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

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

  export var urlResolvers = ['http:', 'ftp:', 'mvn:'];

  export function completeUri ($q, $scope, workspace, jolokia, something) {


  }

  export 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);
  }

  export function setContainerProperty(jolokia, containerId, property, value, success, error) {
{'fraction_non_alphanumeric': 0.13979479169471898, 'fraction_numerical': 0.0008617671612851103, 'mean_word_length': 2.7833927661742233, 'pattern_counts': {'":': 12, '<': 16, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3831461', 'n_tokens_mistral': 11140, 'n_tokens_neox': 10730, 'n_words': 2258}