Viewing File: /usr/local/cpanel/base/frontend/jupiter/version_control/index.cmb.js

(function(root) {
define("jquery-chosen", ["jquery"], function() {
  return (function() {
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com

Version 1.5.1
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011-2016 Harvest http://getharvest.com

MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/

(function() {
  var $, AbstractChosen, Chosen, SelectParser, _ref,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

  SelectParser = (function() {
    function SelectParser() {
      this.options_index = 0;
      this.parsed = [];
    }

    SelectParser.prototype.add_node = function(child) {
      if (child.nodeName.toUpperCase() === "OPTGROUP") {
        return this.add_group(child);
      } else {
        return this.add_option(child);
      }
    };

    SelectParser.prototype.add_group = function(group) {
      var group_position, option, _i, _len, _ref, _results;
      group_position = this.parsed.length;
      this.parsed.push({
        array_index: group_position,
        group: true,
        label: this.escapeExpression(group.label),
        title: group.title ? group.title : void 0,
        children: 0,
        disabled: group.disabled,
        classes: group.className
      });
      _ref = group.childNodes;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        option = _ref[_i];
        _results.push(this.add_option(option, group_position, group.disabled));
      }
      return _results;
    };

    SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
      if (option.nodeName.toUpperCase() === "OPTION") {
        if (option.text !== "") {
          if (group_position != null) {
            this.parsed[group_position].children += 1;
          }
          this.parsed.push({
            array_index: this.parsed.length,
            options_index: this.options_index,
            value: option.value,
            text: option.text,
            html: option.innerHTML,
            title: option.title ? option.title : void 0,
            selected: option.selected,
            disabled: group_disabled === true ? group_disabled : option.disabled,
            group_array_index: group_position,
            group_label: group_position != null ? this.parsed[group_position].label : null,
            classes: option.className,
            style: option.style.cssText
          });
        } else {
          this.parsed.push({
            array_index: this.parsed.length,
            options_index: this.options_index,
            empty: true
          });
        }
        return this.options_index += 1;
      }
    };

    SelectParser.prototype.escapeExpression = function(text) {
      var map, unsafe_chars;
      if ((text == null) || text === false) {
        return "";
      }
      if (!/[\&\<\>\"\'\`]/.test(text)) {
        return text;
      }
      map = {
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#x27;",
        "`": "&#x60;"
      };
      unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
      return text.replace(unsafe_chars, function(chr) {
        return map[chr] || "&amp;";
      });
    };

    return SelectParser;

  })();

  SelectParser.select_to_array = function(select) {
    var child, parser, _i, _len, _ref;
    parser = new SelectParser();
    _ref = select.childNodes;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      child = _ref[_i];
      parser.add_node(child);
    }
    return parser.parsed;
  };

  AbstractChosen = (function() {
    function AbstractChosen(form_field, options) {
      this.form_field = form_field;
      this.options = options != null ? options : {};
      if (!AbstractChosen.browser_is_supported()) {
        return;
      }
      this.is_multiple = this.form_field.multiple;
      this.set_default_text();
      this.set_default_values();
      this.setup();
      this.set_up_html();
      this.register_observers();
      this.on_ready();
    }

    AbstractChosen.prototype.set_default_values = function() {
      var _this = this;
      this.click_test_action = function(evt) {
        return _this.test_active_click(evt);
      };
      this.activate_action = function(evt) {
        return _this.activate_field(evt);
      };
      this.active_field = false;
      this.mouse_on_container = false;
      this.results_showing = false;
      this.result_highlighted = null;
      this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
      this.disable_search_threshold = this.options.disable_search_threshold || 0;
      this.disable_search = this.options.disable_search || false;
      this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
      this.group_search = this.options.group_search != null ? this.options.group_search : true;
      this.search_contains = this.options.search_contains || false;
      this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
      this.max_selected_options = this.options.max_selected_options || Infinity;
      this.inherit_select_classes = this.options.inherit_select_classes || false;
      this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
      this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
      this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
      return this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY;
    };

    AbstractChosen.prototype.set_default_text = function() {
      if (this.form_field.getAttribute("data-placeholder")) {
        this.default_text = this.form_field.getAttribute("data-placeholder");
      } else if (this.is_multiple) {
        this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
      } else {
        this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
      }
      return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
    };

    AbstractChosen.prototype.choice_label = function(item) {
      if (this.include_group_label_in_selected && (item.group_label != null)) {
        return "<b class='group-name'>" + item.group_label + "</b>" + item.html;
      } else {
        return item.html;
      }
    };

    AbstractChosen.prototype.mouse_enter = function() {
      return this.mouse_on_container = true;
    };

    AbstractChosen.prototype.mouse_leave = function() {
      return this.mouse_on_container = false;
    };

    AbstractChosen.prototype.input_focus = function(evt) {
      var _this = this;
      if (this.is_multiple) {
        if (!this.active_field) {
          return setTimeout((function() {
            return _this.container_mousedown();
          }), 50);
        }
      } else {
        if (!this.active_field) {
          return this.activate_field();
        }
      }
    };

    AbstractChosen.prototype.input_blur = function(evt) {
      var _this = this;
      if (!this.mouse_on_container) {
        this.active_field = false;
        return setTimeout((function() {
          return _this.blur_test();
        }), 100);
      }
    };

    AbstractChosen.prototype.results_option_build = function(options) {
      var content, data, data_content, shown_results, _i, _len, _ref;
      content = '';
      shown_results = 0;
      _ref = this.results_data;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        data = _ref[_i];
        data_content = '';
        if (data.group) {
          data_content = this.result_add_group(data);
        } else {
          data_content = this.result_add_option(data);
        }
        if (data_content !== '') {
          shown_results++;
          content += data_content;
        }
        if (options != null ? options.first : void 0) {
          if (data.selected && this.is_multiple) {
            this.choice_build(data);
          } else if (data.selected && !this.is_multiple) {
            this.single_set_selected_text(this.choice_label(data));
          }
        }
        if (shown_results >= this.max_shown_results) {
          break;
        }
      }
      return content;
    };

    AbstractChosen.prototype.result_add_option = function(option) {
      var classes, option_el;
      if (!option.search_match) {
        return '';
      }
      if (!this.include_option_in_results(option)) {
        return '';
      }
      classes = [];
      if (!option.disabled && !(option.selected && this.is_multiple)) {
        classes.push("active-result");
      }
      if (option.disabled && !(option.selected && this.is_multiple)) {
        classes.push("disabled-result");
      }
      if (option.selected) {
        classes.push("result-selected");
      }
      if (option.group_array_index != null) {
        classes.push("group-option");
      }
      if (option.classes !== "") {
        classes.push(option.classes);
      }
      option_el = document.createElement("li");
      option_el.className = classes.join(" ");
      option_el.style.cssText = option.style;
      option_el.setAttribute("data-option-array-index", option.array_index);
      option_el.innerHTML = option.search_text;
      if (option.title) {
        option_el.title = option.title;
      }
      return this.outerHTML(option_el);
    };

    AbstractChosen.prototype.result_add_group = function(group) {
      var classes, group_el;
      if (!(group.search_match || group.group_match)) {
        return '';
      }
      if (!(group.active_options > 0)) {
        return '';
      }
      classes = [];
      classes.push("group-result");
      if (group.classes) {
        classes.push(group.classes);
      }
      group_el = document.createElement("li");
      group_el.className = classes.join(" ");
      group_el.innerHTML = group.search_text;
      if (group.title) {
        group_el.title = group.title;
      }
      return this.outerHTML(group_el);
    };

    AbstractChosen.prototype.results_update_field = function() {
      this.set_default_text();
      if (!this.is_multiple) {
        this.results_reset_cleanup();
      }
      this.result_clear_highlight();
      this.results_build();
      if (this.results_showing) {
        return this.winnow_results();
      }
    };

    AbstractChosen.prototype.reset_single_select_options = function() {
      var result, _i, _len, _ref, _results;
      _ref = this.results_data;
      _results = [];
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        result = _ref[_i];
        if (result.selected) {
          _results.push(result.selected = false);
        } else {
          _results.push(void 0);
        }
      }
      return _results;
    };

    AbstractChosen.prototype.results_toggle = function() {
      if (this.results_showing) {
        return this.results_hide();
      } else {
        return this.results_show();
      }
    };

    AbstractChosen.prototype.results_search = function(evt) {
      if (this.results_showing) {
        return this.winnow_results();
      } else {
        return this.results_show();
      }
    };

    AbstractChosen.prototype.winnow_results = function() {
      var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
      this.no_results_clear();
      results = 0;
      searchText = this.get_search_text();
      escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
      zregex = new RegExp(escapedSearchText, 'i');
      regex = this.get_search_regex(escapedSearchText);
      _ref = this.results_data;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        option = _ref[_i];
        option.search_match = false;
        results_group = null;
        if (this.include_option_in_results(option)) {
          if (option.group) {
            option.group_match = false;
            option.active_options = 0;
          }
          if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
            results_group = this.results_data[option.group_array_index];
            if (results_group.active_options === 0 && results_group.search_match) {
              results += 1;
            }
            results_group.active_options += 1;
          }
          option.search_text = option.group ? option.label : option.html;
          if (!(option.group && !this.group_search)) {
            option.search_match = this.search_string_match(option.search_text, regex);
            if (option.search_match && !option.group) {
              results += 1;
            }
            if (option.search_match) {
              if (searchText.length) {
                startpos = option.search_text.search(zregex);
                text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
                option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
              }
              if (results_group != null) {
                results_group.group_match = true;
              }
            } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
              option.search_match = true;
            }
          }
        }
      }
      this.result_clear_highlight();
      if (results < 1 && searchText.length) {
        this.update_results_content("");
        return this.no_results(searchText);
      } else {
        this.update_results_content(this.results_option_build());
        return this.winnow_results_set_highlight();
      }
    };

    AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
      var regex_anchor;
      regex_anchor = this.search_contains ? "" : "^";
      return new RegExp(regex_anchor + escaped_search_string, 'i');
    };

    AbstractChosen.prototype.search_string_match = function(search_string, regex) {
      var part, parts, _i, _len;
      if (regex.test(search_string)) {
        return true;
      } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
        parts = search_string.replace(/\[|\]/g, "").split(" ");
        if (parts.length) {
          for (_i = 0, _len = parts.length; _i < _len; _i++) {
            part = parts[_i];
            if (regex.test(part)) {
              return true;
            }
          }
        }
      }
    };

    AbstractChosen.prototype.choices_count = function() {
      var option, _i, _len, _ref;
      if (this.selected_option_count != null) {
        return this.selected_option_count;
      }
      this.selected_option_count = 0;
      _ref = this.form_field.options;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        option = _ref[_i];
        if (option.selected) {
          this.selected_option_count += 1;
        }
      }
      return this.selected_option_count;
    };

    AbstractChosen.prototype.choices_click = function(evt) {
      evt.preventDefault();
      if (!(this.results_showing || this.is_disabled)) {
        return this.results_show();
      }
    };

    AbstractChosen.prototype.keyup_checker = function(evt) {
      var stroke, _ref;
      stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
      this.search_field_scale();
      switch (stroke) {
        case 8:
          if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
            return this.keydown_backstroke();
          } else if (!this.pending_backstroke) {
            this.result_clear_highlight();
            return this.results_search();
          }
          break;
        case 13:
          evt.preventDefault();
          if (this.results_showing) {
            return this.result_select(evt);
          }
          break;
        case 27:
          if (this.results_showing) {
            this.results_hide();
          }
          return true;
        case 9:
        case 38:
        case 40:
        case 16:
        case 91:
        case 17:
        case 18:
          break;
        default:
          return this.results_search();
      }
    };

    AbstractChosen.prototype.clipboard_event_checker = function(evt) {
      var _this = this;
      return setTimeout((function() {
        return _this.results_search();
      }), 50);
    };

    AbstractChosen.prototype.container_width = function() {
      if (this.options.width != null) {
        return this.options.width;
      } else {
        return "" + this.form_field.offsetWidth + "px";
      }
    };

    AbstractChosen.prototype.include_option_in_results = function(option) {
      if (this.is_multiple && (!this.display_selected_options && option.selected)) {
        return false;
      }
      if (!this.display_disabled_options && option.disabled) {
        return false;
      }
      if (option.empty) {
        return false;
      }
      return true;
    };

    AbstractChosen.prototype.search_results_touchstart = function(evt) {
      this.touch_started = true;
      return this.search_results_mouseover(evt);
    };

    AbstractChosen.prototype.search_results_touchmove = function(evt) {
      this.touch_started = false;
      return this.search_results_mouseout(evt);
    };

    AbstractChosen.prototype.search_results_touchend = function(evt) {
      if (this.touch_started) {
        return this.search_results_mouseup(evt);
      }
    };

    AbstractChosen.prototype.outerHTML = function(element) {
      var tmp;
      if (element.outerHTML) {
        return element.outerHTML;
      }
      tmp = document.createElement("div");
      tmp.appendChild(element);
      return tmp.innerHTML;
    };

    AbstractChosen.browser_is_supported = function() {
      if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
        return false;
      }
      if (/Android/i.test(window.navigator.userAgent)) {
        if (/Mobile/i.test(window.navigator.userAgent)) {
          return false;
        }
      }
      if (/IEMobile/i.test(window.navigator.userAgent)) {
        return false;
      }
      if (/Windows Phone/i.test(window.navigator.userAgent)) {
        return false;
      }
      if (/BlackBerry/i.test(window.navigator.userAgent)) {
        return false;
      }
      if (/BB10/i.test(window.navigator.userAgent)) {
        return false;
      }
      if (window.navigator.appName === "Microsoft Internet Explorer") {
        return document.documentMode >= 8;
      }
      return true;
    };

    AbstractChosen.default_multiple_text = "Select Some Options";

    AbstractChosen.default_single_text = "Select an Option";

    AbstractChosen.default_no_result_text = "No results match";

    return AbstractChosen;

  })();

  $ = jQuery;

  $.fn.extend({
    chosen: function(options) {
      if (!AbstractChosen.browser_is_supported()) {
        return this;
      }
      return this.each(function(input_field) {
        var $this, chosen;
        $this = $(this);
        chosen = $this.data('chosen');
        if (options === 'destroy') {
          if (chosen instanceof Chosen) {
            chosen.destroy();
          }
          return;
        }
        if (!(chosen instanceof Chosen)) {
          $this.data('chosen', new Chosen(this, options));
        }
      });
    }
  });

  Chosen = (function(_super) {
    __extends(Chosen, _super);

    function Chosen() {
      _ref = Chosen.__super__.constructor.apply(this, arguments);
      return _ref;
    }

    Chosen.prototype.setup = function() {
      this.form_field_jq = $(this.form_field);
      this.current_selectedIndex = this.form_field.selectedIndex;
      return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
    };

    Chosen.prototype.set_up_html = function() {
      var container_classes, container_props;
      container_classes = ["chosen-container"];
      container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
      if (this.inherit_select_classes && this.form_field.className) {
        container_classes.push(this.form_field.className);
      }
      if (this.is_rtl) {
        container_classes.push("chosen-rtl");
      }
      container_props = {
        'class': container_classes.join(' '),
        'style': "width: " + (this.container_width()) + ";",
        'title': this.form_field.title
      };
      if (this.form_field.id.length) {
        container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
      }
      this.container = $("<div />", container_props);
      if (this.is_multiple) {
        this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
      } else {
        this.container.html('<a class="chosen-single chosen-default"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
      }
      this.form_field_jq.hide().after(this.container);
      this.dropdown = this.container.find('div.chosen-drop').first();
      this.search_field = this.container.find('input').first();
      this.search_results = this.container.find('ul.chosen-results').first();
      this.search_field_scale();
      this.search_no_results = this.container.find('li.no-results').first();
      if (this.is_multiple) {
        this.search_choices = this.container.find('ul.chosen-choices').first();
        this.search_container = this.container.find('li.search-field').first();
      } else {
        this.search_container = this.container.find('div.chosen-search').first();
        this.selected_item = this.container.find('.chosen-single').first();
      }
      this.results_build();
      this.set_tab_index();
      return this.set_label_behavior();
    };

    Chosen.prototype.on_ready = function() {
      return this.form_field_jq.trigger("chosen:ready", {
        chosen: this
      });
    };

    Chosen.prototype.register_observers = function() {
      var _this = this;
      this.container.bind('touchstart.chosen', function(evt) {
        _this.container_mousedown(evt);
        return evt.preventDefault();
      });
      this.container.bind('touchend.chosen', function(evt) {
        _this.container_mouseup(evt);
        return evt.preventDefault();
      });
      this.container.bind('mousedown.chosen', function(evt) {
        _this.container_mousedown(evt);
      });
      this.container.bind('mouseup.chosen', function(evt) {
        _this.container_mouseup(evt);
      });
      this.container.bind('mouseenter.chosen', function(evt) {
        _this.mouse_enter(evt);
      });
      this.container.bind('mouseleave.chosen', function(evt) {
        _this.mouse_leave(evt);
      });
      this.search_results.bind('mouseup.chosen', function(evt) {
        _this.search_results_mouseup(evt);
      });
      this.search_results.bind('mouseover.chosen', function(evt) {
        _this.search_results_mouseover(evt);
      });
      this.search_results.bind('mouseout.chosen', function(evt) {
        _this.search_results_mouseout(evt);
      });
      this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
        _this.search_results_mousewheel(evt);
      });
      this.search_results.bind('touchstart.chosen', function(evt) {
        _this.search_results_touchstart(evt);
      });
      this.search_results.bind('touchmove.chosen', function(evt) {
        _this.search_results_touchmove(evt);
      });
      this.search_results.bind('touchend.chosen', function(evt) {
        _this.search_results_touchend(evt);
      });
      this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
        _this.results_update_field(evt);
      });
      this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
        _this.activate_field(evt);
      });
      this.form_field_jq.bind("chosen:open.chosen", function(evt) {
        _this.container_mousedown(evt);
      });
      this.form_field_jq.bind("chosen:close.chosen", function(evt) {
        _this.input_blur(evt);
      });
      this.search_field.bind('blur.chosen', function(evt) {
        _this.input_blur(evt);
      });
      this.search_field.bind('keyup.chosen', function(evt) {
        _this.keyup_checker(evt);
      });
      this.search_field.bind('keydown.chosen', function(evt) {
        _this.keydown_checker(evt);
      });
      this.search_field.bind('focus.chosen', function(evt) {
        _this.input_focus(evt);
      });
      this.search_field.bind('cut.chosen', function(evt) {
        _this.clipboard_event_checker(evt);
      });
      this.search_field.bind('paste.chosen', function(evt) {
        _this.clipboard_event_checker(evt);
      });
      if (this.is_multiple) {
        return this.search_choices.bind('click.chosen', function(evt) {
          _this.choices_click(evt);
        });
      } else {
        return this.container.bind('click.chosen', function(evt) {
          evt.preventDefault();
        });
      }
    };

    Chosen.prototype.destroy = function() {
      $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
      if (this.search_field[0].tabIndex) {
        this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
      }
      this.container.remove();
      this.form_field_jq.removeData('chosen');
      return this.form_field_jq.show();
    };

    Chosen.prototype.search_field_disabled = function() {
      this.is_disabled = this.form_field_jq[0].disabled;
      if (this.is_disabled) {
        this.container.addClass('chosen-disabled');
        this.search_field[0].disabled = true;
        if (!this.is_multiple) {
          this.selected_item.unbind("focus.chosen", this.activate_action);
        }
        return this.close_field();
      } else {
        this.container.removeClass('chosen-disabled');
        this.search_field[0].disabled = false;
        if (!this.is_multiple) {
          return this.selected_item.bind("focus.chosen", this.activate_action);
        }
      }
    };

    Chosen.prototype.container_mousedown = function(evt) {
      if (!this.is_disabled) {
        if (evt && evt.type === "mousedown" && !this.results_showing) {
          evt.preventDefault();
        }
        if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
          if (!this.active_field) {
            if (this.is_multiple) {
              this.search_field.val("");
            }
            $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
            this.results_show();
          } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
            evt.preventDefault();
            this.results_toggle();
          }
          return this.activate_field();
        }
      }
    };

    Chosen.prototype.container_mouseup = function(evt) {
      if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
        return this.results_reset(evt);
      }
    };

    Chosen.prototype.search_results_mousewheel = function(evt) {
      var delta;
      if (evt.originalEvent) {
        delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
      }
      if (delta != null) {
        evt.preventDefault();
        if (evt.type === 'DOMMouseScroll') {
          delta = delta * 40;
        }
        return this.search_results.scrollTop(delta + this.search_results.scrollTop());
      }
    };

    Chosen.prototype.blur_test = function(evt) {
      if (!this.active_field && this.container.hasClass("chosen-container-active")) {
        return this.close_field();
      }
    };

    Chosen.prototype.close_field = function() {
      $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
      this.active_field = false;
      this.results_hide();
      this.container.removeClass("chosen-container-active");
      this.clear_backstroke();
      this.show_search_field_default();
      return this.search_field_scale();
    };

    Chosen.prototype.activate_field = function() {
      this.container.addClass("chosen-container-active");
      this.active_field = true;
      this.search_field.val(this.search_field.val());
      return this.search_field.focus();
    };

    Chosen.prototype.test_active_click = function(evt) {
      var active_container;
      active_container = $(evt.target).closest('.chosen-container');
      if (active_container.length && this.container[0] === active_container[0]) {
        return this.active_field = true;
      } else {
        return this.close_field();
      }
    };

    Chosen.prototype.results_build = function() {
      this.parsing = true;
      this.selected_option_count = null;
      this.results_data = SelectParser.select_to_array(this.form_field);
      if (this.is_multiple) {
        this.search_choices.find("li.search-choice").remove();
      } else if (!this.is_multiple) {
        this.single_set_selected_text();
        if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
          this.search_field[0].readOnly = true;
          this.container.addClass("chosen-container-single-nosearch");
        } else {
          this.search_field[0].readOnly = false;
          this.container.removeClass("chosen-container-single-nosearch");
        }
      }
      this.update_results_content(this.results_option_build({
        first: true
      }));
      this.search_field_disabled();
      this.show_search_field_default();
      this.search_field_scale();
      return this.parsing = false;
    };

    Chosen.prototype.result_do_highlight = function(el) {
      var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
      if (el.length) {
        this.result_clear_highlight();
        this.result_highlight = el;
        this.result_highlight.addClass("highlighted");
        maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
        visible_top = this.search_results.scrollTop();
        visible_bottom = maxHeight + visible_top;
        high_top = this.result_highlight.position().top + this.search_results.scrollTop();
        high_bottom = high_top + this.result_highlight.outerHeight();
        if (high_bottom >= visible_bottom) {
          return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
        } else if (high_top < visible_top) {
          return this.search_results.scrollTop(high_top);
        }
      }
    };

    Chosen.prototype.result_clear_highlight = function() {
      if (this.result_highlight) {
        this.result_highlight.removeClass("highlighted");
      }
      return this.result_highlight = null;
    };

    Chosen.prototype.results_show = function() {
      if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
        this.form_field_jq.trigger("chosen:maxselected", {
          chosen: this
        });
        return false;
      }
      this.container.addClass("chosen-with-drop");
      this.results_showing = true;
      this.search_field.focus();
      this.search_field.val(this.search_field.val());
      this.winnow_results();
      return this.form_field_jq.trigger("chosen:showing_dropdown", {
        chosen: this
      });
    };

    Chosen.prototype.update_results_content = function(content) {
      return this.search_results.html(content);
    };

    Chosen.prototype.results_hide = function() {
      if (this.results_showing) {
        this.result_clear_highlight();
        this.container.removeClass("chosen-with-drop");
        this.form_field_jq.trigger("chosen:hiding_dropdown", {
          chosen: this
        });
      }
      return this.results_showing = false;
    };

    Chosen.prototype.set_tab_index = function(el) {
      var ti;
      if (this.form_field.tabIndex) {
        ti = this.form_field.tabIndex;
        this.form_field.tabIndex = -1;
        return this.search_field[0].tabIndex = ti;
      }
    };

    Chosen.prototype.set_label_behavior = function() {
      var _this = this;
      this.form_field_label = this.form_field_jq.parents("label");
      if (!this.form_field_label.length && this.form_field.id.length) {
        this.form_field_label = $("label[for='" + this.form_field.id + "']");
      }
      if (this.form_field_label.length > 0) {
        return this.form_field_label.bind('click.chosen', function(evt) {
          if (_this.is_multiple) {
            return _this.container_mousedown(evt);
          } else {
            return _this.activate_field();
          }
        });
      }
    };

    Chosen.prototype.show_search_field_default = function() {
      if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
        this.search_field.val(this.default_text);
        return this.search_field.addClass("default");
      } else {
        this.search_field.val("");
        return this.search_field.removeClass("default");
      }
    };

    Chosen.prototype.search_results_mouseup = function(evt) {
      var target;
      target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
      if (target.length) {
        this.result_highlight = target;
        this.result_select(evt);
        return this.search_field.focus();
      }
    };

    Chosen.prototype.search_results_mouseover = function(evt) {
      var target;
      target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
      if (target) {
        return this.result_do_highlight(target);
      }
    };

    Chosen.prototype.search_results_mouseout = function(evt) {
      if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
        return this.result_clear_highlight();
      }
    };

    Chosen.prototype.choice_build = function(item) {
      var choice, close_link,
        _this = this;
      choice = $('<li />', {
        "class": "search-choice"
      }).html("<span>" + (this.choice_label(item)) + "</span>");
      if (item.disabled) {
        choice.addClass('search-choice-disabled');
      } else {
        close_link = $('<a />', {
          "class": 'search-choice-close',
          'data-option-array-index': item.array_index
        });
        close_link.bind('click.chosen', function(evt) {
          return _this.choice_destroy_link_click(evt);
        });
        choice.append(close_link);
      }
      return this.search_container.before(choice);
    };

    Chosen.prototype.choice_destroy_link_click = function(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      if (!this.is_disabled) {
        return this.choice_destroy($(evt.target));
      }
    };

    Chosen.prototype.choice_destroy = function(link) {
      if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
        this.show_search_field_default();
        if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
          this.results_hide();
        }
        link.parents('li').first().remove();
        return this.search_field_scale();
      }
    };

    Chosen.prototype.results_reset = function() {
      this.reset_single_select_options();
      this.form_field.options[0].selected = true;
      this.single_set_selected_text();
      this.show_search_field_default();
      this.results_reset_cleanup();
      this.form_field_jq.trigger("change");
      if (this.active_field) {
        return this.results_hide();
      }
    };

    Chosen.prototype.results_reset_cleanup = function() {
      this.current_selectedIndex = this.form_field.selectedIndex;
      return this.selected_item.find("abbr").remove();
    };

    Chosen.prototype.result_select = function(evt) {
      var high, item;
      if (this.result_highlight) {
        high = this.result_highlight;
        this.result_clear_highlight();
        if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
          this.form_field_jq.trigger("chosen:maxselected", {
            chosen: this
          });
          return false;
        }
        if (this.is_multiple) {
          high.removeClass("active-result");
        } else {
          this.reset_single_select_options();
        }
        high.addClass("result-selected");
        item = this.results_data[high[0].getAttribute("data-option-array-index")];
        item.selected = true;
        this.form_field.options[item.options_index].selected = true;
        this.selected_option_count = null;
        if (this.is_multiple) {
          this.choice_build(item);
        } else {
          this.single_set_selected_text(this.choice_label(item));
        }
        if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
          this.results_hide();
        }
        this.show_search_field_default();
        if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
          this.form_field_jq.trigger("change", {
            'selected': this.form_field.options[item.options_index].value
          });
        }
        this.current_selectedIndex = this.form_field.selectedIndex;
        evt.preventDefault();
        return this.search_field_scale();
      }
    };

    Chosen.prototype.single_set_selected_text = function(text) {
      if (text == null) {
        text = this.default_text;
      }
      if (text === this.default_text) {
        this.selected_item.addClass("chosen-default");
      } else {
        this.single_deselect_control_build();
        this.selected_item.removeClass("chosen-default");
      }
      return this.selected_item.find("span").html(text);
    };

    Chosen.prototype.result_deselect = function(pos) {
      var result_data;
      result_data = this.results_data[pos];
      if (!this.form_field.options[result_data.options_index].disabled) {
        result_data.selected = false;
        this.form_field.options[result_data.options_index].selected = false;
        this.selected_option_count = null;
        this.result_clear_highlight();
        if (this.results_showing) {
          this.winnow_results();
        }
        this.form_field_jq.trigger("change", {
          deselected: this.form_field.options[result_data.options_index].value
        });
        this.search_field_scale();
        return true;
      } else {
        return false;
      }
    };

    Chosen.prototype.single_deselect_control_build = function() {
      if (!this.allow_single_deselect) {
        return;
      }
      if (!this.selected_item.find("abbr").length) {
        this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
      }
      return this.selected_item.addClass("chosen-single-with-deselect");
    };

    Chosen.prototype.get_search_text = function() {
      return $('<div/>').text($.trim(this.search_field.val())).html();
    };

    Chosen.prototype.winnow_results_set_highlight = function() {
      var do_high, selected_results;
      selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
      do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
      if (do_high != null) {
        return this.result_do_highlight(do_high);
      }
    };

    Chosen.prototype.no_results = function(terms) {
      var no_results_html;
      no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
      no_results_html.find("span").first().html(terms);
      this.search_results.append(no_results_html);
      return this.form_field_jq.trigger("chosen:no_results", {
        chosen: this
      });
    };

    Chosen.prototype.no_results_clear = function() {
      return this.search_results.find(".no-results").remove();
    };

    Chosen.prototype.keydown_arrow = function() {
      var next_sib;
      if (this.results_showing && this.result_highlight) {
        next_sib = this.result_highlight.nextAll("li.active-result").first();
        if (next_sib) {
          return this.result_do_highlight(next_sib);
        }
      } else {
        return this.results_show();
      }
    };

    Chosen.prototype.keyup_arrow = function() {
      var prev_sibs;
      if (!this.results_showing && !this.is_multiple) {
        return this.results_show();
      } else if (this.result_highlight) {
        prev_sibs = this.result_highlight.prevAll("li.active-result");
        if (prev_sibs.length) {
          return this.result_do_highlight(prev_sibs.first());
        } else {
          if (this.choices_count() > 0) {
            this.results_hide();
          }
          return this.result_clear_highlight();
        }
      }
    };

    Chosen.prototype.keydown_backstroke = function() {
      var next_available_destroy;
      if (this.pending_backstroke) {
        this.choice_destroy(this.pending_backstroke.find("a").first());
        return this.clear_backstroke();
      } else {
        next_available_destroy = this.search_container.siblings("li.search-choice").last();
        if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
          this.pending_backstroke = next_available_destroy;
          if (this.single_backstroke_delete) {
            return this.keydown_backstroke();
          } else {
            return this.pending_backstroke.addClass("search-choice-focus");
          }
        }
      }
    };

    Chosen.prototype.clear_backstroke = function() {
      if (this.pending_backstroke) {
        this.pending_backstroke.removeClass("search-choice-focus");
      }
      return this.pending_backstroke = null;
    };

    Chosen.prototype.keydown_checker = function(evt) {
      var stroke, _ref1;
      stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
      this.search_field_scale();
      if (stroke !== 8 && this.pending_backstroke) {
        this.clear_backstroke();
      }
      switch (stroke) {
        case 8:
          this.backstroke_length = this.search_field.val().length;
          break;
        case 9:
          if (this.results_showing && !this.is_multiple) {
            this.result_select(evt);
          }
          this.mouse_on_container = false;
          break;
        case 13:
          if (this.results_showing) {
            evt.preventDefault();
          }
          break;
        case 32:
          if (this.disable_search) {
            evt.preventDefault();
          }
          break;
        case 38:
          evt.preventDefault();
          this.keyup_arrow();
          break;
        case 40:
          evt.preventDefault();
          this.keydown_arrow();
          break;
      }
    };

    Chosen.prototype.search_field_scale = function() {
      var div, f_width, h, style, style_block, styles, w, _i, _len;
      if (this.is_multiple) {
        h = 0;
        w = 0;
        style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
        styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
        for (_i = 0, _len = styles.length; _i < _len; _i++) {
          style = styles[_i];
          style_block += style + ":" + this.search_field.css(style) + ";";
        }
        div = $('<div />', {
          'style': style_block
        });
        div.text(this.search_field.val());
        $('body').append(div);
        w = div.width() + 25;
        div.remove();
        f_width = this.container.outerWidth();
        if (w > f_width - 10) {
          w = f_width - 10;
        }
        return this.search_field.css({
          'width': w + 'px'
        });
      }
    };

    return Chosen;

  })(AbstractChosen);

}).call(this);


  }).apply(root, arguments);
});
}(this));

(function(root) {
define("angular-chosen", ["angular","jquery-chosen"], function() {
  return (function() {
/**
 * angular-chosen-localytics - Angular Chosen directive is an AngularJS Directive that brings the Chosen jQuery in a Angular way
 * @version v1.3.0
 * @link http://github.com/leocaseiro/angular-chosen
 * @license MIT
 */
(function() {
  var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  angular.module('localytics.directives', []);

  angular.module('localytics.directives').directive('chosen', [
    '$timeout', function($timeout) {
      var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase;
      NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
      CHOSEN_OPTION_WHITELIST = ['persistentCreateOption', 'createOptionText', 'createOption', 'skipNoResults', 'noResultsText', 'allowSingleDeselect', 'disableSearchThreshold', 'disableSearch', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'singleBackstrokeDelete', 'displayDisabledOptions', 'displaySelectedOptions', 'width', 'includeGroupLabelInSelected', 'maxShownResults'];
      snakeCase = function(input) {
        return input.replace(/[A-Z]/g, function($1) {
          return "_" + ($1.toLowerCase());
        });
      };
      isEmpty = function(value) {
        var key;
        if (angular.isArray(value)) {
          return value.length === 0;
        } else if (angular.isObject(value)) {
          for (key in value) {
            if (value.hasOwnProperty(key)) {
              return false;
            }
          }
        }
        return true;
      };
      return {
        restrict: 'A',
        require: '?ngModel',
        priority: 1,
        link: function(scope, element, attr, ngModel) {
          var chosen, empty, initOrUpdate, match, options, origRender, startLoading, stopLoading, updateMessage, valuesExpr, viewWatch;
          scope.disabledValuesHistory = scope.disabledValuesHistory ? scope.disabledValuesHistory : [];
          element = $(element);
          element.addClass('localytics-chosen');
          options = scope.$eval(attr.chosen) || {};
          angular.forEach(attr, function(value, key) {
            if (indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) {
              return attr.$observe(key, function(value) {
                options[snakeCase(key)] = String(element.attr(attr.$attr[key])).slice(0, 2) === '{{' ? value : scope.$eval(value);
                return updateMessage();
              });
            }
          });
          startLoading = function() {
            return element.addClass('loading').attr('disabled', true).trigger('chosen:updated');
          };
          stopLoading = function() {
            element.removeClass('loading');
            if (angular.isDefined(attr.disabled)) {
              element.attr('disabled', attr.disabled);
            } else {
              element.attr('disabled', false);
            }
            return element.trigger('chosen:updated');
          };
          chosen = null;
          empty = false;
          initOrUpdate = function() {
            var defaultText;
            if (chosen) {
              return element.trigger('chosen:updated');
            } else {
              $timeout(function() {
                chosen = element.chosen(options).data('chosen');
              });
              if (angular.isObject(chosen)) {
                return defaultText = chosen.default_text;
              }
            }
          };
          updateMessage = function() {
            if (empty) {
              element.attr('data-placeholder', chosen.results_none_found).attr('disabled', true);
            } else {
              element.removeAttr('data-placeholder');
            }
            return element.trigger('chosen:updated');
          };
          if (ngModel) {
            origRender = ngModel.$render;
            ngModel.$render = function() {
              origRender();
              return initOrUpdate();
            };
            element.on('chosen:hiding_dropdown', function() {
              return scope.$apply(function() {
                return ngModel.$setTouched();
              });
            });
            if (attr.multiple) {
              viewWatch = function() {
                return ngModel.$viewValue;
              };
              scope.$watch(viewWatch, ngModel.$render, true);
            }
          } else {
            initOrUpdate();
          }
          attr.$observe('disabled', function() {
            return element.trigger('chosen:updated');
          });
          if (attr.ngOptions && ngModel) {
            match = attr.ngOptions.match(NG_OPTIONS_REGEXP);
            valuesExpr = match[7];
            scope.$watchCollection(valuesExpr, function(newVal, oldVal) {
              var timer;
              return timer = $timeout(function() {
                if (angular.isUndefined(newVal)) {
                  return startLoading();
                } else {
                  empty = isEmpty(newVal);
                  stopLoading();
                  return updateMessage();
                }
              });
            });
            return scope.$on('$destroy', function(event) {
              if (typeof timer !== "undefined" && timer !== null) {
                return $timeout.cancel(timer);
              }
            });
          }
        }
      };
    }
  ]);

}).call(this);


  }).apply(root, arguments);
});
}(this));

/*
 * versionControlService.js                           Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* global define: false, PAGE: false */
define(
    'app/services/versionControlService',[
        "angular",
        "cjt/util/locale",
        "cjt/util/parse",
        "cjt/io/uapi-request",
        "cjt/io/api",
        "cjt/io/uapi",
        "cjt/services/APIService",
        "cjt/filters/qaSafeIDFilter"
    ],
    function(angular, LOCALE, PARSE, UAPIREQUEST) {
        "use strict";

        var app = angular.module("cpanel.versionControl.service", []);
        app.value("PAGE", PAGE);

        app.factory("versionControlService", [
            "$q",
            "APIService",
            "$filter",
            "PAGE",
            "$timeout",
            "$rootScope",
            function($q, APIService, $filter, PAGE, $timeout, $rootScope) {

                var VersionControlService = function() {};
                var repos = [];

                var fileManagerPath = PAGE.fileManagerURL;
                var gitWebPath = PAGE.gitwebURL;

                /* Parse Head commit information
                * @method parseHeadCommitInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns head commit information
                */
                function parseHeadCommitInformation(repoInfo) {

                    // HEAD commit
                    var commitInfo = {};

                    commitInfo.lastUpdateSHA = LOCALE.maketext("Not available.");
                    commitInfo.lastUpdateDate = LOCALE.maketext("Not available.");
                    commitInfo.commitMessage = LOCALE.maketext("Not available.");
                    commitInfo.author = LOCALE.maketext("Not available.");
                    commitInfo.hasHeadInformation = false;

                    if (typeof repoInfo.last_update !== "undefined" && repoInfo.last_update) {

                        commitInfo.hasHeadInformation = true;

                        if (typeof repoInfo.last_update.identifier !== "undefined" &&
                            repoInfo.last_update.identifier) {
                            commitInfo.lastUpdateSHA = repoInfo.last_update.identifier;
                        }

                        if (typeof repoInfo.last_update.date !== "undefined" &&
                            repoInfo.last_update.date) {
                            commitInfo.lastUpdateDate = getHumanReadableTime(repoInfo.last_update.date);
                        }

                        if (typeof repoInfo.last_update.author !== "undefined" &&
                            repoInfo.last_update.author) {
                            commitInfo.author = repoInfo.last_update.author;
                        }

                        if (typeof repoInfo.last_update.message !== "undefined" &&
                            repoInfo.last_update.message) {
                            commitInfo.commitMessage = repoInfo.last_update.message;
                        }
                    }

                    return commitInfo;
                }

                /* Parse Last Deployed Information
                * @method parseLastDeployedInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns last deployed information
                */
                function parseLastDeployedInformation(repoInfo) {

                    var deployedInfo = {};

                    // last deployed information
                    deployedInfo.hasDeploymentInformation = false;
                    deployedInfo.lastDeployedSHA = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedDate = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedCommitDate = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedCommitMessage = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedAuthor = LOCALE.maketext("Not available.");

                    if (typeof repoInfo.last_deployment !== "undefined" && repoInfo.last_deployment) {

                        deployedInfo.hasDeploymentInformation = true;

                        if (typeof repoInfo.last_deployment.timestamps !== "undefined" &&
                            typeof repoInfo.last_deployment.timestamps.succeeded !== "undefined" &&
                            repoInfo.last_deployment.timestamps.succeeded) {
                            deployedInfo.lastDeployedDate = getHumanReadableTime(repoInfo.last_deployment.timestamps.succeeded);

                            if (typeof repoInfo.last_deployment.repository_state !== "undefined" &&
                                repoInfo.last_deployment.repository_state) {

                                if (typeof repoInfo.last_deployment.repository_state.identifier !== "undefined" &&
                                repoInfo.last_deployment.repository_state.identifier) {
                                    deployedInfo.lastDeployedSHA = repoInfo.last_deployment.repository_state.identifier;
                                }

                                if (typeof repoInfo.last_deployment.repository_state.date !== "undefined" &&
                                repoInfo.last_deployment.repository_state.date) {
                                    deployedInfo.lastDeployedCommitDate = getHumanReadableTime(repoInfo.last_deployment.repository_state.date);
                                }

                                if (typeof repoInfo.last_deployment.repository_state.message !== "undefined" &&
                                repoInfo.last_deployment.repository_state.message) {
                                    deployedInfo.lastDeployedCommitMessage = repoInfo.last_deployment.repository_state.message;
                                }

                                if (typeof repoInfo.last_deployment.repository_state.author !== "undefined" &&
                                repoInfo.last_deployment.repository_state.author) {
                                    deployedInfo.lastDeployedAuthor = repoInfo.last_deployment.repository_state.author;
                                }
                            }
                        }
                    }

                    return deployedInfo;
                }

                /* Render repository list
                * @method refineRepositoryInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns a single formatted repository data instance.
                */
                function refineRepositoryInformation(repoInfo) {
                    if (repoInfo && typeof repoInfo !== "undefined") {

                        // For unique ids
                        repoInfo.qaSafeSuffix = $filter("qaSafeID")(repoInfo.repository_root);

                        // File manager url
                        if (fileManagerPath) {
                            repoInfo.fileManagerRedirectURL =  fileManagerPath + encodeURIComponent(repoInfo.repository_root);
                        } else {
                            repoInfo.fileManagerRedirectURL = "";
                        }

                        // gitweb url
                        if (gitWebPath) {
                            var projectPath = repoInfo.repository_root.replace(/^\/+/g, "") + "/.git";
                            repoInfo.gitWebURL = gitWebPath + encodeURIComponent(projectPath);
                        } else {
                            repoInfo.gitWebURL = "";
                        }

                        // has remote
                        repoInfo.hasRemote = typeof repoInfo.source_repository !== "undefined" &&  repoInfo.source_repository;

                        // Clone URL
                        if (typeof repoInfo.clone_urls !== "undefined" && repoInfo.clone_urls) {
                            repoInfo.cloneURL = repoInfo.clone_urls.read_write[0] || repoInfo.clone_urls.read_only[0];
                        }

                        // Branch information
                        repoInfo.activeBranch = LOCALE.maketext("Not available.");
                        repoInfo.hasActiveBranch = false;

                        if (typeof repoInfo.branch !== "undefined" && repoInfo.branch) {
                            repoInfo.activeBranch = repoInfo.branch;
                            repoInfo.hasActiveBranch = true;
                        }

                        // Head Commit information
                        var commitInfo = parseHeadCommitInformation(repoInfo);
                        repoInfo.lastUpdateSHA = commitInfo.lastUpdateSHA;
                        repoInfo.lastUpdateDate = commitInfo.lastUpdateDate;
                        repoInfo.commitMessage = commitInfo.commitMessage;
                        repoInfo.author = commitInfo.author;
                        repoInfo.hasHeadInformation = commitInfo.hasHeadInformation;

                        // Last deployed information
                        var lastDeployedInfo = parseLastDeployedInformation(repoInfo);
                        repoInfo.hasDeploymentInformation = lastDeployedInfo.hasDeploymentInformation;
                        repoInfo.lastDeployedSHA = lastDeployedInfo.lastDeployedSHA;
                        repoInfo.lastDeployedDate = lastDeployedInfo.lastDeployedDate;
                        repoInfo.lastDeployedCommitDate = lastDeployedInfo.lastDeployedCommitDate;
                        repoInfo.lastDeployedCommitMessage = lastDeployedInfo.lastDeployedCommitMessage;
                        repoInfo.lastDeployedAuthor = lastDeployedInfo.lastDeployedAuthor;

                        // Is deployable
                        repoInfo.deployable = PARSE.parsePerlBoolean(repoInfo.deployable);

                        repoInfo.cloneInProgress = false;
                        repoInfo.deployInProgress = false;

                        // Tasks (clone and deploy)
                        if (typeof repoInfo.tasks !== "undefined" &&
                            repoInfo.tasks &&
                            repoInfo.tasks.length > 0) {

                            for ( var i = 0, len = repoInfo.tasks.length; i < len; i++) {
                                if (repoInfo.tasks[i].action === "create") {
                                    repoInfo.cloneInProgress = true;
                                    repoInfo.cloneTaskID = repoInfo.tasks[i].id;
                                }

                                if (repoInfo.tasks[i].action === "deploy") {
                                    repoInfo.deployInProgress = true;
                                }
                            }
                        }

                    }
                    return repoInfo;
                }

                /* Convert Epoch Time to Human readable
                * @method getHumanReadableTime
                * @params {Number} epochTime Represents the DateTime in epoch format.
                * @return {String} Returns a human readable DateTime string.
                */
                function getHumanReadableTime(epochTime) {
                    if (!epochTime) {
                        return LOCALE.maketext("Not available.");
                    } else if (typeof epochTime !== "number") {
                        epochTime = Number(epochTime);
                        if ( isNaN(epochTime) ) {
                            return LOCALE.maketext("Not available.");
                        }
                    }

                    epochTime = Math.floor(epochTime);
                    return LOCALE.local_datetime(epochTime, "datetime_format_medium");
                }

                /* Modify Repository List
                * @method prepareList
                * @params {Array} data List of repositories
                * @return {Array} List of Repositories
                */
                function prepareList(data) {
                    var list = [];

                    if (typeof data !== "undefined" && data !== null ) {
                        for (var i = 0, len = data.length; i < len; i++) {
                            var value = refineRepositoryInformation(data[i]);
                            list.push(value);
                        }
                    }
                    return list;
                }

                /* Gets the index of specific repository in the list
                * @method getRepositoryIndex
                * @params {Array} reposList List of Repositories
                * @params {String} path Repository path to look for
                * @return {Number} Returns the index of the repository
                */
                function getRepositoryIndex(reposList, path) {
                    var index = null;
                    if (typeof reposList !== undefined && reposList) {
                        for (var repoIndex = 0, repoLength = reposList.length; repoIndex < repoLength; repoIndex++) {
                            if (reposList[repoIndex].repository_root === path) {
                                index = repoIndex;
                                break;
                            }
                        }
                    }

                    return index;
                }

                /* Copies supplied string to the user's clipboard
                * @method copyTextToClipboard
                * @params {String} text String to be copied to the user's clipboard.
                */
                function copyTextToClipboard(text) {
                    var textArea = document.createElement("textarea");
                    textArea.value = text;
                    document.body.appendChild(textArea);
                    textArea.select();
                    try {
                        document.execCommand("copy");
                    } catch (e) {
                        throw new Error(LOCALE.maketext("You cannot copy the “[_1]” clone [output,acronym,URL,Uniform Resource Locator] to the clipboard.", text));
                    }
                    document.body.removeChild(textArea);
                }

                // get repository list from PAGE data
                repos = prepareList(PAGE.repos);

                VersionControlService.prototype = new APIService();

                angular.extend(VersionControlService.prototype, {

                    /**
                    * Gets the list of cached repositories.
                    * Used for testing purposes
                    *
                    * @method getRepositoryList
                    * @return {Array} Array of cached repositories
                    */
                    getRepositoryList: function() {
                        return repos;
                    },

                    /**
                    * List repositories
                    *
                    * @method listRepositories
                    * @param {Boolean} forceLoad Forces the API request to take place, rather than pulling the data from the JS vars.
                    * @param {String} attributeStr Comma seperated list of fields that UI needs
                    * @return {Promise} When fulfilled, will return the list of repositories.
                    */
                    listRepositories: function(forceLoad, attributeStr) {

                        if (forceLoad) {
                            var apiCall = new UAPIREQUEST.Class();
                            apiCall.initialize("VersionControl", "retrieve");

                            if (attributeStr) {
                                apiCall.addArgument("fields", attributeStr);
                            }

                            return this.deferred(apiCall).promise
                                .then(function(response) {
                                    try {
                                        repos = prepareList(response.data);
                                        return $q.resolve(repos);
                                    } catch (err) {
                                        return $q.reject(err);
                                    }
                                })
                                .catch(function(error) {
                                    return $q.reject(error);
                                });
                        } else {
                            return $q.resolve(repos);
                        }
                    },

                    /**
                    * Gets information of a single repository
                    *
                    * @method getRepositoryInformation
                    * @param {String} repositoryPath Repository root path
                    * @param {String} attributeStr Fields to request
                    * @return {Promise} When fulfilled, will return repository information.
                    */
                    getRepositoryInformation: function(repositoryPath, attributeStr) {

                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "retrieve");

                        if (repositoryPath) {
                            apiCall.addFilter("repository_root", "eq", repositoryPath);
                            if (attributeStr) {
                                apiCall.addArgument("fields", attributeStr);
                            }
                        } else {
                            throw new Error(LOCALE.maketext("Repository path cannot be empty."));
                        }

                        var repository = [];
                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                try {
                                    repository = prepareList(response.data);

                                    if (repository && repository.length > 0) {
                                        return $q.resolve(repository[0]);
                                    } else {
                                        return $q.reject(LOCALE.maketext("The system could not find the “[_1]” repository.", repositoryPath));
                                    }

                                } catch (err) {
                                    return $q.reject(err);
                                }
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    },

                    /**
                    * Clone to Clipboard
                    *
                    * @method cloneToClipboard
                    * @param {String} cloneUrl The URL to be used to clone repos.
                    * @return {Boolean} Returns true on success.
                    */
                    cloneToClipboard: function(cloneUrl) {
                        if (typeof cloneUrl === "string" && cloneUrl !== "") {

                        // set clipboard to the clone URL
                            copyTextToClipboard(cloneUrl);
                            return true;

                        } else {

                        // throw dev error
                            throw new Error(LOCALE.maketext("“[_1]” is not a valid clone [output,acronym,URL,Universal Resource Locator].", cloneUrl));
                        }
                    },

                    /**
                    * Creates or clones repository
                    *
                    * @method createRepository
                    * @return {Promise} When fulfilled, returns created repository information.
                    */
                    createRepository: function(name, fullPath, cloneURL) {

                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "create");

                        apiCall.addArgument("name", name);
                        apiCall.addArgument("repository_root", fullPath);

                        apiCall.addArgument("type", "git");

                        // Creates a new repository when cloneURL is not provided
                        if (typeof cloneURL !== "undefined" && cloneURL) {
                            apiCall.addArgument("source_repository", JSON.stringify({
                                "remote_name": "origin",
                                "url": cloneURL
                            }));
                        }

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                var processedData = {};

                                // update the repository list with data returned from create
                                if (response.data) {
                                    processedData = refineRepositoryInformation(response.data);
                                    repos.push(processedData);
                                }

                                return processedData;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    },

                    /**
                    * Delete repository
                    *
                    * @method deleteRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    deleteRepository: function(repositoryRoot) {
                        if (typeof repositoryRoot === "string" && repositoryRoot !== "") {
                            var apiCall = new UAPIREQUEST.Class();

                            apiCall.initialize("VersionControl", "delete");
                            apiCall.addArgument("repository_root", repositoryRoot);
                            return this.deferred(apiCall).promise
                                .then(function() {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos.splice(index, 1);
                                    }
                                    return $q.resolve();
                                })
                                .catch(function(error) {
                                    return $q.reject(error);
                                });
                        }
                    },

                    /**
                    * Updates repository
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @param {String} repositoryName name of the repository
                    * @param {String} branch active branch name
                    * @return {Promise} Returns a promise.
                    */
                    updateRepository: function(repositoryRoot, repositoryName, branch) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "update");

                        apiCall.addArgument("repository_root", repositoryRoot);

                        if (typeof repositoryName === "string" && repositoryName) {
                            apiCall.addArgument("name", repositoryName);
                        }

                        if (typeof branch === "string" && branch) {
                            apiCall.addArgument("branch", branch);
                        }

                        return this.deferred(apiCall).promise
                            .then(function(response) {

                                if (response.data) {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos[index] = refineRepositoryInformation(response.data);
                                    }
                                }
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });

                    },

                    /**
                    * Updates changes from remote
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    updateFromRemote: function(repositoryRoot, branch) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "update");

                        apiCall.addArgument("repository_root", repositoryRoot);
                        apiCall.addArgument("branch", branch || "");

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                if (response.data) {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos[index] = refineRepositoryInformation(response.data);
                                    }
                                }
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });

                    },

                    /**
                    * Deploy repository
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    deployRepository: function(repositoryRoot) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControlDeployment", "create");

                        apiCall.addArgument("repository_root", repositoryRoot);

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    }
                });

                return new VersionControlService();
            }
        ]);
    }
);

/*
 * services/sseCloneApi.js                            Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* global define: false */

define('app/services/sseAPIService',[
    "angular",
    "cjt/core",
    "lodash",
], function(
        angular,
        CJT,
        _
    ) {
    "use strict";

    var app = angular.module("cpanel.versionControl.sseAPIService", []);

    app.factory("sseAPIService", [
        "$rootScope",
        function(
            $rootScope
        ) {

            /**
             * Format the data based on the configuration requested.
             * Currently only has the option to convert from string to JSON if config.json is true.
             *
             * @param  {Object} config - SSE configuration
             * @param  {String} data
             * @return {Any}
             */
            var _formatData = function(config, data) {
                if (config && config.json && data) {
                    data = JSON.parse(data);
                }
                return data;
            };

            /**
             * Custom event sent when JSON parsing is requested but fails for some reason.
             *
             * @event sse:<eventName>:error
             * @property {Object} data
             *   @property {String} data.error - Error message from the JSON parser.
             *   @property {String} data.data  - Data passed with the message that could not be parsed.
             */

            /**
             * Custom event sent.
             *
             * @event sse:<eventName>
             * @property {Object} data
             */

            /**
             * Send a format error if the parsing fails
             *
             * @param {String} eventName
             * @param {String} exception
             * @param {String} data
             * @fires sse:<evenName>:error
             */
            var _sendFormatErrorEvent = function(eventName, exception, data) {
                $rootScope.$broadcast(
                    "sse:" + eventName + ":error",
                    {
                        data: data,
                        error: exception
                    }
                );
            };

            /**
             * Create a message event handler callback. The callback will annotate the event with
             * a eventName field and will generated an angularjs event with the following name:
             *
             *   sse:<eventName>
             *
             * If a data element is available it will send the data along with the event in the
             * data field.
             *
             *
             * @param  {String} eventName The name of the event.
             * @param  {Object} config configuration
             * @return {Function}
             */
            var makeHandler = function(eventName, config) {
                return function(e) {
                    e.eventName = eventName;
                    var data = e.data;
                    try {
                        data = _formatData(config, data);
                    } catch (exception) {
                        _sendFormatErrorEvent(eventName, exception, data);
                        return;
                    }

                    $rootScope.$broadcast("sse:" + eventName, data);
                };
            };

            /**
             * Fired when the sse process is done.
             *
             * @event sse:done
             */

            /**
             * Fired when sse generated an error.
             *
             * @event sse:error
             */

            /**
             * Fired when the sse is running and the page is being unloaded.
             *
             * @event sse:beforeunload
             */

            /**
             * Connect the specified SSE url fire an angular event for the requested events.
             *
             * @param {String} url - url to connect to the sse event source.
             * @param {Array[String]} [events]  - array of additional events to register.
             * @param {Object}        [config]  - optional configuration options
             *   @param {Boolean} [config.json] - if true, will parse the e.data as json. otherwise, just returns the data to the caller as is.
             * @fires sse:beforeunload, sse:error, sse:done, sse:*
             */
            var connect = function connect(url, events, config) {

                if (!events) {
                    events = [];
                }

                var sseConfig = config || {};

                var sse = new EventSource(url);

                // Setup known events
                if (events) {
                    events.forEach(function(event) {
                        sse.addEventListener(event, makeHandler(event, sseConfig));
                    });
                }

                // Setup the error event handler
                sse.onerror = function(e) {
                    $rootScope.$broadcast("sse:error", e);
                };

                // Setup the beforeunload event handler
                window.addEventListener("beforeunload", function(e) {
                    if (sse) {
                        sse.close();
                        sse = null;
                        $rootScope.$broadcast("sse:beforeunload", e);
                    }
                });

                return sse;
            };

            /**
             * Fired when the sse polyfill is loaded when needed. It will fire both
             * when the polyfill is needed and finished loading and when the polyfill
             * is not needed.
             *
             * @event sse:ready
             */

            /**
             * Initialize the SSE resources
             *
             * @param {Function} [ready] Optional callback to call when the sse is ready to run.
             * @fires sse:ready
             */
            var initialize = function initialize(ready) {

                // Microsoft browsers (including Edge)
                // don’t support SSE as of November 2017.
                // https://developer.microsoft.com/en-us/microsoft-edge/platform/status/serversenteventseventsource/
                if (!window.EventSource) {
                    var script = document.createElement("script");
                    script.src = CJT.buildFullPath("libraries/eventsource-polyfill/eventsource.js");
                    script.onload = function() {
                        if (ready) {
                            ready();
                        }
                        $rootScope.$broadcast("sse:ready");
                    };
                    document.body.appendChild(script);
                } else {
                    if (ready) {
                        ready();
                    }
                    $rootScope.$broadcast("sse:ready");
                }
            };

            /**
             * Close the sse connection and clean up the sseApi state.
             *
             * @param {Object} SSE object
             */
            var close = function(sse) {
                if (sse) {
                    sse.close();
                }
            };

            return {
                initialize: initialize,
                connect: connect,
                close: close
            };
        }
    ]);
});

/*
# version_control/views/listRepositoriesController.js      Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define, PAGE */

define(
    'app/views/listRepositoriesController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/util/table",
        "uiBootstrap",
        "cjt/filters/wrapFilter",
        "cjt/services/cpanel/nvDataService",
        "app/services/versionControlService",
        "app/services/sseAPIService",
        "cjt/services/alertService",
        "cjt/directives/alert",
        "cjt/directives/alertList",
        "cjt/directives/actionButtonDirective"
    ],
    function(angular, _, LOCALE, Table) {
        "use strict";

        var app = angular.module("cpanel.versionControl");
        app.value("PAGE", PAGE);

        var controller = app.controller(
            "ListRepositoriesController",
            ["$scope", "$window", "$location", "versionControlService", "sseAPIService", "PAGE", "nvDataService", "alertService", "$timeout",
                function($scope, $window, $location, versionControlService, sseAPIService, PAGE, nvDataService, alertService, $timeout) {

                    var repositories = this;
                    repositories.isLoading = false;
                    repositories.list = [];
                    repositories.cloningList = [];
                    var sseURL = PAGE.securityToken + "/sse/UserTasks";
                    var SSEObj;

                    // SSE events and config
                    var events = [ "task_processing", "task_complete", "task_failed" ];
                    var config = { json: true };

                    repositories.homeDirPath = PAGE.homeDir;
                    repositories.hasFilemanagerAccess = (PAGE.hasFileManagerAccess === "0") ? false : true;
                    repositories.hasShellAccess = (PAGE.hasShellAccess === "0") ? false : true;

                    var table = new Table();
                    table.setSort("name", "asc");

                    repositories.meta = table.getMetadata();
                    repositories.filteredList = table.getList();
                    repositories.paginationMessage = table.paginationMessage;
                    repositories.meta.pageSize = parseInt(PAGE.reposListPageSize, 10);

                    /**
                     * Search repository by name and path
                     * @method searchByNameOrFolder
                     * @param {Object} item Item in repositories list
                     * @param {String} searchText Text to search for
                     */
                    function searchByNameOrFolder(item, searchText) {
                        searchText = searchText.toLowerCase();
                        return item.name.toLowerCase().indexOf(searchText) !== -1 ||
                            item.repository_root.toLowerCase().indexOf(searchText) !== -1;
                    }

                    table.setSearchFunction(searchByNameOrFolder);

                    /**
                     * Render repository list
                     * @method render
                     */
                    repositories.render = function() {
                        repositories.filteredList = table.update();
                    };

                    /**
                     * Sort repository list
                     * @method sortList
                     */
                    repositories.sortList = function() {
                        repositories.render();
                    };

                    /**
                     * Select repository page
                     * @method selectPage
                     */
                    repositories.selectPage = function() {
                        repositories.render();
                    };

                    /**
                     * Selects page size
                     * @method selectPageSize
                     */
                    repositories.selectPageSize = function() {
                        repositories.render();

                        if (PAGE.reposListPageSize !== repositories.meta.pageSize) {
                            nvDataService.setObject({ repos_list_page_size: repositories.meta.pageSize })
                                .then(function() {
                                    PAGE.reposListPageSize = repositories.meta.pageSize;
                                })
                                .catch(function(error) {
                                    alertService.add({
                                        type: "danger",
                                        message: error.message,
                                        closeable: true,
                                        replace: false,
                                        group: "versionControl"
                                    });
                                });
                        }
                    };

                    /**
                     * Implements search for repository
                     * @method searchList
                     */
                    repositories.searchList = function() {
                        repositories.render();
                    };

                    /**
                     * Initialization of the List Repositories page.
                     * @method init
                     */
                    repositories.init = function() {
                        var pageErrors = PAGE.repoErrors;

                        if (pageErrors.length > 0) {

                            // Potential cache corruption, attempting to load via JS to display error messages.
                            pageErrors.forEach(function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: error,
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });
                            });
                        } else {

                            // Updates the list with the current API cache.
                            repositories.updateRepositoriesList(true);
                        }
                    };

                    /**
                     * Extend repository object to add few fields to manage state
                     * @method extendRepositoryObject
                     * @param {Object} repoObject repository
                     * @returns {Object} updated repository object
                     */
                    function extendRepositoryObject(repoObject) {
                        if (repoObject) {
                            repoObject.isExpanded = false;
                            repoObject.detailsLoading = false;
                            repoObject.delete_requested = false;
                            repoObject.cloneState = "";
                        }
                        return repoObject;
                    }

                    /**
                     * Resets clone state of repository
                     * @method clearCloning
                     * @param {Object} repoObject repository
                     * @returns {Object} updated repository object
                     */
                    function clearCloning(repoObject) {
                        if (repoObject) {
                            repoObject.cloneState = "";
                            repoObject.cloneInProgress = false;
                            delete repoObject.cloneTaskID;
                        }
                        return repoObject;
                    }

                    /**
                     * Calls the API to update the repositories list.
                     * @method updateRepositoriesList
                     * @param {Boolean} forceLoad Tells the method to pull the data from the API.
                     */
                    repositories.updateRepositoriesList = function(forceLoad) {
                        repositories.isLoading = true;

                        var attributeStr = (forceLoad) ? "name,tasks" : null;

                        return versionControlService.listRepositories(forceLoad, attributeStr)
                            .then(function(response) {

                                repositories.list = _.map(response, function(obj) {
                                    return extendRepositoryObject(obj);
                                });

                                for (var i = 0, len = repositories.list.length; i < len; i++) {
                                    if (repositories.list[i].cloneInProgress) {
                                        repositories.cloningList.push(
                                            repositories.list[i]
                                        );
                                    }
                                }

                                // Initiate SSE when atleast one repository is being cloned
                                if (repositories.cloningList && repositories.cloningList.length > 0) {
                                    sseAPIService.initialize();
                                }

                                table.load(repositories.list);
                                repositories.render();

                                repositories.isLoading = false;

                            }, function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: error.message,
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });

                                repositories.isLoading = false;
                            });
                    };

                    /**
                     * Handles task_processing.
                     *
                     * @method
                     * @param {sse:task_processing} event - Task processing event.
                     * @param {Object} data - Data
                     * @listens sse:task_processing
                     */
                    $scope.$on("sse:task_processing", function(event, data) {

                        var taskID = data.task_id;

                        var cloneItem = _.find(repositories.cloningList, function(o) {
                            return o.cloneTaskID === taskID;
                        });

                        if (cloneItem) {
                            var unfilteredIndex = _.indexOf(repositories.list, cloneItem);

                            if (unfilteredIndex !== -1) {
                                cloneItem.cloneState = "processing";
                                _.extend( repositories.list[unfilteredIndex], cloneItem );
                                $scope.$apply(repositories.render);
                            }
                        }

                    });

                    /**
                     * Handles task_complete.
                     *
                     * @method
                     * @param {sse:task_complete} event - Task complete event.
                     * @param {Object} data - Data
                     * @listens sse:task_complete
                     */
                    $scope.$on("sse:task_complete", function(event, data) {
                        var taskID = data.task_id;

                        var cloneItem = _.find(repositories.cloningList, function(o) {
                            return o.cloneTaskID === taskID;
                        });

                        if (cloneItem) {
                            var unfilteredIndex = _.indexOf(repositories.list, cloneItem);

                            if (unfilteredIndex !== -1) {
                                cloneItem.cloneState = "complete";
                                _.extend( repositories.list[unfilteredIndex], cloneItem );
                                $scope.$apply(repositories.render);

                                // removing object from clonning list because clone is complete
                                _.remove(repositories.cloningList, cloneItem);

                                if (repositories.cloningList.length === 0) {
                                    sseAPIService.close(SSEObj);
                                }

                                $timeout(function() {

                                    // Using timeout to visually display success on the row and later change it to a normal row.
                                    return versionControlService.getRepositoryInformation(cloneItem.repository_root, "name,tasks")
                                        .then(function(response) {
                                            var repoDetails = extendRepositoryObject(response);

                                            // updating the repository list with new information so that the row is active
                                            _.extend(repositories.list[unfilteredIndex], clearCloning(repoDetails));

                                            // Remove clone information message
                                            alertService.removeById(taskID, "versionControl");

                                            repositories.render();

                                        }, function(error) {

                                            // Remove clone information message
                                            alertService.removeById(taskID, "versionControl");

                                            // display error
                                            alertService.add({
                                                type: "danger",
                                                message: error.message,
                                                closeable: true,
                                                replace: false,
                                                group: "versionControl"
                                            });

                                        });
                                }, 5000);
                            }

                        }

                    });

                    /**
                     * Handles task_failed.
                     *
                     * @method
                     * @param {sse:task_failed} event - Task failed event.
                     * @param {Object} data - Data
                     * @listens sse:task_failed
                     */
                    $scope.$on("sse:task_failed", function(event, data) {

                        var taskID = data.task_id;

                        var cloneItem = _.find(repositories.cloningList, function(o) {
                            return o.cloneTaskID === taskID;
                        });

                        if (cloneItem) {
                            var unfilteredIndex = _.indexOf(repositories.list, cloneItem);

                            _.remove(repositories.cloningList, cloneItem);

                            if (repositories.cloningList.length === 0) {
                                sseAPIService.close(SSEObj);
                            }

                            if (unfilteredIndex !== -1) {
                                repositories.list.splice(unfilteredIndex, 1);
                                alertService.add({
                                    type: "danger",
                                    message: LOCALE.maketext("Error occurred while cloning repository “[_1]”.", cloneItem.name),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });

                                $scope.$apply(repositories.render);
                            }
                        }
                    });

                    /**
                     * Handles ready.
                     *
                     * @method
                     * @param {sse:ready} event - Task ready event.
                     * @listens sse:ready
                     */
                    $scope.$on("sse:ready", function(event) {
                        SSEObj = sseAPIService.connect(sseURL, events, config);
                    });

                    /**
                     * Handles destroy.
                     *
                     * @method
                     * @listens $destroy
                     */
                    $scope.$on("$destroy", function() {
                        if (SSEObj) {
                            sseAPIService.close(SSEObj);
                        }
                    });

                    /**
                     * Opens repository in gitWeb
                     * @method redirectToGitWeb
                     * @param {String} gitWebURL gitWebURL for the repository
                     * @param {String} repoName Repository name
                     */
                    repositories.redirectToGitWeb = function(gitWebURL, repoName) {

                        if (gitWebURL) {
                            $window.open(gitWebURL, repoName + "GitWeb");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("Unable to find repository web url"),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Opens repository path in file manager
                     * @method redirectToFileManager
                     * @param {String} fileManagerURL file Manager url for the repository path
                     * @param {String} repoName Repository name
                     */
                    repositories.redirectToFileManager = function(fileManagerURL, repoName) {

                        if (fileManagerURL) {
                            $window.open(fileManagerURL, repoName + "FileManager");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("Unable to redirect to File Manager interface"),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Copies the repo's clone link to you machine's clipboard
                     * @method cloneToClipboard
                     * @param {String} cloneUrl The URL to be used to clone repos.
                     */
                    repositories.cloneToClipboard = function(cloneUrl) {
                        try {
                            var result = versionControlService.cloneToClipboard(cloneUrl);
                            if (result) {
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("The system successfully copied the “[_1]” clone [output,acronym,URL,Uniform Resource Locator] to the clipboard.", cloneUrl),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });
                            }
                        } catch (error) {
                            alertService.add({
                                type: "danger",
                                message: error,
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Create Repository View
                     * @method createRepository
                     */
                    repositories.createRepository = function() {
                        alertService.clear("", "versionControl");
                        $location.path("/create");
                    };

                    /**
                     * Deletes a repository
                     * @method delete
                     * @param {String} repo The repository to delete.
                     * @return {Promise} Returns a promise from the VersionControlService.deleteRepository method for success/error handling when the user requests to delete a repository.
                     */
                    repositories.delete = function(repo) {
                        repo.removing = true;
                        var successMessage;
                        if (repositories.hasFilemanagerAccess) {
                            successMessage = LOCALE.maketext("The system successfully removed the “[_1]” repository from the list of [asis,cPanel]-managed repositories. You can use the [output,url,_2,File Manager,target,_3] interface to delete the repository contents.", repo.name, repo.fileManagerRedirectURL, "file-manager");
                        } else {
                            successMessage = LOCALE.maketext("The system successfully removed the “[_1]” repository from the list of [asis,cPanel]-managed repositories.", repo.name);
                        }
                        return versionControlService.deleteRepository(repo.repository_root)
                            .then(function() {
                                table.remove(repo);
                                repositories.render();
                                alertService.add({
                                    type: "success",
                                    message: successMessage,
                                    closeable: true,
                                    replace: false,
                                    autoClose: false,
                                    group: "versionControl"
                                });

                            }, function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: LOCALE.maketext("The system could not remove the “[_1]” repository in the “[_2]” directory.", repo.name, repo.repository_root),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });

                                repo.removing = false;
                                repo.delete_requested = false;
                            });
                    };

                    /**
                     * Delete repository confirmation message.
                     * @method deleteText
                     * @param {Object} repo The repository's data representation of the repository that should be deleted.
                     * @return {String} Returns a dynamic string as a message to the user, that cooresponds to the exact repository within the delete action-scope, to inform/ask the user if they are certain that they would like to permanatly delete a repository from their cPanel instance/account/system.
                     */
                    repositories.deleteText = function(repo) {
                        return LOCALE.maketext("Are you sure that you want to remove the “[_1]” repository from the list of [asis,cPanel]-managed repositories?", repo.name);
                    };

                    /**
                     * Redirects user to the version control manage page for a specified repo.
                     * @method manageRepository
                     * @param {String} repoPath Represents the repository_root of the repo object in the repo list.
                     */
                    repositories.manageRepository = function(repoPath) {
                        $location.path("/manage/" + encodeURIComponent(repoPath) + "/basic-info");
                    };

                    /**
                     * Get the repository index.
                     * @method getRepositoryIndex
                     * @param {String} repositoryRoot Repository root
                     * @return {Number} return the index
                     */
                    function getRepositoryIndex(repositoryRoot) {
                        return _.findIndex(repositories.filteredList, function(o) {
                            return o.repository_root === repositoryRoot;
                        });
                    }

                    /* Gets repository details
                     * @method repositories.getRepositoryDetails
                     * @param {Object} repo Repository
                     * @param {Boolean} expandState Current Expanded state
                     * @return {Promise} return promise
                     */
                    repositories.getRepositoryDetails = function(repo, expandState) {

                        repo.detailsLoading = true;
                        var index;

                        // When changing from collapse to expand
                        if (expandState) {

                            return versionControlService.getRepositoryInformation(repo.repository_root, "name,clone_urls,branch,last_update,source_repository")
                                .then(function(response) {
                                    index = getRepositoryIndex(repo.repository_root);
                                    if (index !== -1) {
                                        response.detailsLoading = false;
                                        response.isExpanded = true;

                                        // Retrieving back the state of delete
                                        response.delete_requested = repo.delete_requested;

                                        _.assign(repo, response);
                                    }

                                    // Use assign to update the source object in-place
                                    _.assign(repo, response);
                                }, function(error) {

                                    alertService.add({
                                        type: "danger",
                                        message: error.message,
                                        closeable: true,
                                        replace: false,
                                        group: "versionControl"
                                    });

                                });
                        } else {
                            repo.detailsLoading = false;
                            repo.isExpanded = false;
                        }

                    };

                    repositories.init();
                }
            ]
        );

        return controller;
    }
);

/*
 * version_control/utils/cloneUrlParser.js         Copyright(c) 2020 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* eslint-env amd */

define('app/utils/cloneUrlParser',[],function() {
    "use strict";

    function parseCloneUrl(cloneUrl) {
        var parts = {};

        if (!cloneUrl) {
            return parts;
        }

        // Correct IPv6 URLs of the form http(s)://[<ipv6addr]/... are handled properly.
        // However, incorrect IPv6 URLs - http(s)://<ipv6addr>/... are not handled well
        // because the parser assumes that if there is a : then that represents a port
        // number and thus authority is incorrectly parsed. Here we just parse out the
        // authority portion and check for more than 2 :'s. If found then the URL is not
        // valid
        var authority = cloneUrl.match(/^(https?:\/\/)?(?!\[)([^/]+)\/*(?!\])/);

        if (authority !== null && authority[2].match(/.*:.*:.*/)) {
            return parts;
        }

        parts.scheme = parseUrlParts(cloneUrl.match(/^\S+:\/\//i));
        parts.userInfo = parseUrlParts(cloneUrl.match(/^\S+@/i));
        parts.ipv6Authority = parseUrlParts(cloneUrl.match(/^\[\S+\]/i));
        if (parts.ipv6Authority) {
            parts.ipv6Authority = parts.ipv6Authority.replace(/(\[|\])/gi, "");
        }

        parts.authority =
            parts.ipv6Authority === null
                ? parseUrlParts(cloneUrl.split(/((:\d+\/)|(\/|:))/i))
                : null;

        // Parse out the port if it exists.
        parts.port = (cloneUrl.match(/^:\d+\//i)) ? parseUrlParts(cloneUrl.match(/^:(\d+)/i), 1) : null;
        parts.path = parseUrlParts(cloneUrl.match(/^\S+/i));
        parts.unparsed = cloneUrl;

        function parseUrlParts(matches, returnIndex) {
            returnIndex = returnIndex || 0;
            if (matches !== null && matches.length > 0) {
                cloneUrl = cloneUrl.replace(matches[0], "");
                return matches[returnIndex];
            }
            return null;
        }

        return parts;
    }

    return {
        parse: parseCloneUrl,
    };
});

/*
 * version_control/services/knownHostsService.js      Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* eslint-env amd */
/* global PAGE: false */
define(
    'app/services/knownHostsService',[
        "angular",
        "cjt/util/locale",
        "cjt/util/parse",
        "cjt/io/uapi-request",
        "cjt/io/uapi",
        "cjt/services/APIService",
    ],
    function(angular, LOCALE, PARSE, UAPIREQUEST) {
        "use strict";

        var app = angular.module("cpanel.versionControl.knownHostsService", ["cjt2.services.api"]);
        app.value("PAGE", PAGE); // TODO: Consolidate this higher up

        app.factory("knownHostsService", [
            "$q",
            "APIService",
            "$filter",
            "PAGE",
            "$timeout",
            "$rootScope",
            function($q, APIService, $filter, PAGE, $timeout, $rootScope) {

                var KnownHostsService = function() {};
                KnownHostsService.prototype = new APIService();

                angular.extend(KnownHostsService.prototype, {

                    /**
                     * Checks whether the current keys for a given hostname/port combination
                     * are in the known_hosts file. This is helpful to use before cloning or
                     * pulling, since the errors provided by those APIs are not very clear
                     * when keys are missing or mismatched.
                     *
                     * The known_hosts file differentiates by port, so that is why we need to
                     * provide the port.
                     *
                     * @param  {String} hostname      The hostname to check
                     * @param  {Number|String} port   The port to check
                     * @return {Promise}              If resolved, then the keys already exists in
                     *                                the known_hosts file. If rejected, then the keys
                     *                                don't exist in the known_hosts file. In most cases
                     *                                the object provided to the promise callback will
                     *                                contain a 'keys' property containing the current
                     *                                keys for the server.
                     */
                    verify: function(hostname, port) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("KnownHosts", "verify");

                        apiCall.addArgument("host_name", hostname);
                        if (port) {
                            apiCall.addArgument("port", port);
                        }

                        return this.deferred(apiCall).promise.then(
                            function success(res) {
                                var data = res && res.data || {};

                                if (data.status) {
                                    return {
                                        status: "recognized",
                                    };
                                } else if (data.host.length && data.failure_type) {
                                    return $q.reject({
                                        status: "unrecognized-" + data.failure_type,
                                        keys: data.host,
                                    });
                                }

                                return $q.reject({
                                    status: "unrecognized-unknown",
                                });
                            },
                            function failure(error) {
                                return $q.reject({
                                    status: "unrecognized-unknown",
                                    error: error,
                                });
                            }
                        );
                    },

                    /**
                     * Adds the current keys for an SSH server listening on a given
                     * hostname/port combination to the user's known_hosts file.
                     *
                     * @param  {String} hostname      The server's hostname
                     * @param  {Number|String} port   The server's port
                     * @return {Promise}              If resolved, then the operation was successful.
                     *                                Rejections are not caught or manipulated because
                     *                                we cannot be sure what the status was before we
                     *                                tried to add the host keys, so we just pass the
                     *                                API error through as normal.
                     */
                    create: function(hostname, port) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("KnownHosts", "create");

                        apiCall.addArgument("host_name", hostname);
                        if (port) {
                            apiCall.addArgument("port", port);
                        }

                        return this.deferred(apiCall).promise.then(function(res) {
                            return {
                                status: "recognized",
                            };
                        });
                    }

                });

                return new KnownHostsService();
            }
        ]);
    }
);

/*
 * version_control/services/sshKeyVerification.js     Copyright 2022 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* eslint-env amd */

define('app/services/sshKeyVerification',[
    "angular",
    "lodash",
    "cjt/util/locale",
    "app/utils/cloneUrlParser",
    "ngSanitize",
    "uiBootstrap",
    "app/services/knownHostsService",
    "cjt/directives/actionButtonDirective",
], function(
        angular,
        _,
        LOCALE,
        cloneUrlParser
    ) {

    "use strict";

    angular
        .module("cpanel.versionControl.sshKeyVerificationService", ["cpanel.versionControl.knownHostsService", "ui.bootstrap", "ngSanitize", "cjt2.directives.actionButton", ])
        .factory("sshKeyVerification", [
            "knownHostsService",
            "$uibModal",
            function(
                knownHostsService,
                $uibModal
            ) {

                var _memoizedVerify = _initMemoizedVerify();

                var service = {

                    // A memoized version of knownHostsService.verify
                    verify: _memoizedVerify,

                    /**
                     * Get the hostname and port of an SSH-based clone URL.
                     *
                     * @param  {String} cloneUrl    The URL to parse
                     * @return {Object|Undefined}   An object with hostname and port properties, unless
                     *                              it is not an SSH-based URL
                     */
                    getHostnameAndPort: function getHostnameAndPort(cloneUrl) {
                        var parts = cloneUrlParser.parse(cloneUrl);

                        if (parts.scheme === "ssh://" || (!parts.scheme && parts.userInfo)) {
                            return {
                                hostname: parts.authority || parts.ipv6Authority,
                                port: parts.port,
                            };
                        }
                    },

                    /**
                     * Opens a modal for key verification so that the user can choose
                     * whether to accept and save the key or not.
                     *
                     * @param {Object}         props             An object of properties that the modal will use.
                     * @param {String}         props.hostname    The server's hostname.
                     * @param {String|Number} [props.port]       The server's port (optional).
                     * @param {String}         props.type        The key's failure type (unrecognized-new, unrecognized-changed)
                     * @param {Function}      [props.onAccept]   A callback function that is called when someone chooses to accept
                     *                                           the new/changed key and save it to known_hosts. This function is
                     *                                           called with one argument - a promise that resolves when the changes
                     *                                           are successfully saved and rejects when it's unsuccessful.
                     * @return {Modal}   An angular-ui-bootstrap modal instance
                     */
                    openModal: function openModal(props) {
                        var self = this;
                        self.modal = $uibModal.open({
                            templateUrl: "views/sshKeyVerification.ptt",
                            controllerAs: "modal",
                            controller: KeyVerificationController,
                            resolve: {
                                props: function() {
                                    return props;
                                },
                            },
                        });

                        self.modal.result.finally(function() {
                            delete self.modal;
                        });

                        return self.modal;
                    },

                };


                /**
                 * The constructor for the SSH key verification modal controller.
                 *
                 * See service.openModal for documentation on the props argument.
                 */
                function KeyVerificationController(props) {
                    _.assign(this, props);
                }

                KeyVerificationController.$inject = ["props"];

                _.assign(KeyVerificationController.prototype, {

                    /**
                     * Attempt to add the host to the known_hosts file and continue with
                     * creation.
                     *
                     * @return {Promise}   When resolved, we successfully added the host.
                     *                     When rejected, something went wrong.
                     */
                    acceptIdentity: function acceptIdentity() {
                        var self = this;

                        var acceptPromise = knownHostsService.create( self.hostname, self.port ).then(
                            function success(data) {
                                return data.status;
                            }
                        ).finally(function() {

                            // We attempted a change to the known_hosts file, so our cached verification results are probably stale
                            var cacheKey = _memoizedVerifyResolver(self.hostname, self.port);
                            _memoizedVerify.cache.delete(cacheKey);
                            _memoizedVerify(self.hostname, self.port);
                        });

                        return self.onAccept ? self.onAccept(acceptPromise) : acceptPromise;
                    },

                    rejectIdentity: function rejectIdentity() {
                        service.modal.dismiss();
                    },

                    newKeyIntro: function newKeyIntro() {
                        return LOCALE.maketext("You have not connected this [asis,cPanel] account to the SSH server for “[output,strong,_1].” The system cannot verify the server’s identity.", this.hostname);
                    },

                    changedKeyIntro: function changedKeyIntro() {
                        return LOCALE.maketext("The current identity of the SSH server at “[output,strong,_1]” does not match its identity in your account’s [asis,known_hosts] file.", this.hostname);
                    },

                    keyIsNew: function keyIsNew() {
                        return this.type === "unrecognized-new";
                    },

                    keyIsChanged: function keyIsChanged() {
                        return this.type === "unrecognized-changed";
                    },

                });

                /**
                 * These are some small caching optimizations for the knownHostsService.verify
                 * method so that we don't make repeated requests for identical hostname/port
                 * combinations unless we know something has changed.
                 */

                function _memoizedVerifyResolver(hostname, port) {
                    port = port ? ":" + port : "";
                    return hostname + port;
                }

                function _initMemoizedVerify() {
                    var memoizedVerify = _.memoize(knownHostsService.verify, _memoizedVerifyResolver);
                    var memoizedVerifyCache = memoizedVerify.cache;

                    var boundMemoizedVerify = memoizedVerify.bind(knownHostsService);
                    boundMemoizedVerify.cache = memoizedVerifyCache;

                    return boundMemoizedVerify;
                }

                return service;
            }
        ]);
});

/*
 * cpanel - base/frontend/jupiter/_assets/services/directoryLookupService.js
 *                                                 Copyright(c) 2020 cPanel, L.L.C.
 *                                                           All rights reserved.
 * copyright@cpanel.net                                         http://cpanel.net
 * This code is subject to the cPanel license. Unauthorized copying is prohibited
 */

/* global define: false */

define(
    'app/services/directoryLookupService',[
        "angular",
        "lodash",

        "cjt/core",
        "cjt/util/locale",

        "cjt/io/api",
        "cjt/io/uapi-request",
        "cjt/io/uapi",
        "cjt/util/parse",
    ],
    function(angular, _, CJT, LOCALE, API, APIREQUEST, APIDRIVER, PARSER) {
        "use strict";

        var app = angular.module("cpanel.services.directoryLookup", []);
        var lastRequestJQXHR = null;
        app.factory("directoryLookupService", [
            "$q",
            "APIService",
            function($q, APIService) {
                var DirectoryLookupService = function() {};
                DirectoryLookupService.prototype = new APIService();
                angular.extend(DirectoryLookupService.prototype, {

                    /**
                     * Query the directory completion API. Given a path prefix, which may
                     * include a partial directory name, returns an array of matching
                     * directories.
                     * @param  {String}  match  The prefix to match.
                     * @return {Promise} When fulfilled, will have either provided the list of matching directories or failed.
                     */
                    complete: function(match) {

                        /* Only allow one promise at a time for this service, and cancel any existing request, since
                         * the latest request will always supersede the existing one when typing into a text box. */
                        if (lastRequestJQXHR) {
                            lastRequestJQXHR.abort();
                        }

                        var apiCall = new APIREQUEST.Class();
                        apiCall.initialize("Fileman", "autocompletedir");
                        apiCall.addArgument("path", match);
                        apiCall.addArgument("dirsonly", true);
                        apiCall.addArgument("skipreserved", true);
                        apiCall.addArgument("html", 0);

                        /* If the last character of the path to match is a slash, then the user is probably hoping to see
                         * a list of all files underneath that directory. The API doesn't understand this unless you
                         * specify list_all mode, so we need to add that argument. */
                        if ( "/" === match.charAt(match.length - 1) ) {
                            apiCall.addArgument("list_all", true);
                        }

                        var deferred = this.deferred(apiCall, {
                            transformAPISuccess: function(response) {
                                var flattenedResponse = [];
                                for (var i = 0, l = response.data.length; i < l; i++) {
                                    flattenedResponse.push(response.data[i].file);
                                }
                                return flattenedResponse;
                            },
                        });

                        return deferred.promise;
                    },

                    /* override sendRequest from APIService to also save our last jqXHR object */
                    sendRequest: function(apiCall, handlers, deferred) {
                        apiCall = new APIService.AngularAPICall(apiCall, handlers, deferred);

                        lastRequestJQXHR = apiCall.jqXHR;

                        return apiCall.deferred;
                    },
                });
                return new DirectoryLookupService();
            },
        ]);
    }
);

/*
# version_control/directives/cloneURLValidator.js  Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

define('app/directives/cloneURLValidator',[
    "angular",
    "app/utils/cloneUrlParser",
    "cjt/util/locale",
    "cjt/validator/validator-utils",
    "cjt/validator/ip-validators",
    "cjt/validator/domain-validators",
    "cjt/validator/validateDirectiveFactory",
], function(
    angular,
    cloneUrlParser,
    LOCALE,
    validationUtils,
    IP_VALIDATOR,
    DOMAIN_VALIDATOR
) {
    "use strict";

    var cloneURLValidator = {

        /**
         * Validate a git-based clone url.
         *
         * Does not validate local paths like: file:///path/to/repo.git/ and /path/to/repo.git/
         *
         * @method validCloneUrl
         * @param {String} cloneUrl - Check if this is a valid clone url.
         * @return {object} result - Validator-Utils result.
         */
        validCloneUrl: function(cloneUrl) {
            var result = validationUtils.initializeValidationResult();

            // Check for blank string.
            if (typeof cloneUrl === "undefined" || cloneUrl === null) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext("You must specify a valid clone URL.")
                );
                return result;
            }

            var parts = cloneUrlParser.parse(cloneUrl);
            var scheme = parts.scheme;
            var userInfo = parts.userInfo;
            var ipv6Authority = parts.ipv6Authority;
            var authority = parts.authority;
            var path = parts.path;
            var unparsed = parts.unparsed;

            // Check for valid protocols (http:// | https:// | ssh:// | git://)
            var protocolPattern = /^(?:git|ssh|https?)(?::\/\/)$/i;
            var hasValidProtocol = protocolPattern.test(scheme);

            // Check for invalid username and password (user:pass@)
            var userAndPassPattern = /^\S+:\S+@/i;
            if (userAndPassPattern.test(userInfo)) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "The clone URL [output,strong,cannot] include a password."
                    )
                );
                return result;
            }

            // Check for valid username (username@)
            var emailPattern =
                /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@/i;
            var hasValidUser = emailPattern.test(userInfo);

            // Assess if provided scheme and user@ is valid
            var preDomainValid = false;
            if (hasValidProtocol && hasValidUser) {

                // has both
                preDomainValid = true;
            }
            if (hasValidProtocol ^ hasValidUser) {

                // has one or the other

                // Prevents invalid non-required protocol.
                if (!hasValidProtocol && scheme !== null) {
                    result.isValid = false;
                    result.add(
                        "cloneURLValidator",
                        LOCALE.maketext(
                            "The provided clone URL [output,strong,must] include a valid protocol."
                        )
                    );
                    return result;
                }

                preDomainValid = true;
            }
            if (!preDomainValid) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "Clone URLs [output,strong,must] include a valid protocol or username."
                    )
                );
                return result;
            }

            // Check for valid ipv6
            if (ipv6Authority !== null) {
                var validIPV6 = IP_VALIDATOR.methods.ipv6(ipv6Authority);

                if (!validIPV6.isValid) {
                    var errorMsg = combineErrorMessages(validIPV6.messages);

                    result.isValid = false;
                    result.add("cloneURLValidator", errorMsg);
                    return result;
                }
            }

            // Check for valid ipv4 or domain name
            if (authority !== null && authority !== "") {
                var validIPV4 = IP_VALIDATOR.methods.ipv4(authority);
                var validFQDN = DOMAIN_VALIDATOR.methods.fqdn(authority);

                if (!validIPV4.isValid && !validFQDN.isValid) {
                    result.isValid = false;
                    result.add(
                        "cloneURLValidator",
                        LOCALE.maketext(
                            "The clone URL [output,strong,must] include a valid IP address or a fully-qualified domain name."
                        )
                    );
                    return result;
                }

                // If there is no valid ipv4, ipv6, or domain name
            } else if (ipv6Authority === null) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "The clone URL [output,strong,must] include a valid IP address or a fully-qualified domain name."
                    )
                );
                return result;
            }

            // Check for ip/domain -> path delimiter: if there is a protocol and path starts with : then throw error
            var scpSyntaxPattern = /^:/i;
            var hasSCPsyntaxPathStart = scpSyntaxPattern.test(path);
            if (hasValidProtocol && hasValidUser && hasSCPsyntaxPathStart) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "The repository path should [output,strong,not] begin with “:” if it includes the protocol."
                    )
                );
                return result;
            }

            // Check for valid path with .git extension (:|/ + path + .git? + /? )
            // This could also indicate a problem with the port number format.
            // This ignores any query string vars or page anchors.
            var pathAndExtensionPattern = /^(:|\/)\S*(\.git)?\/?/i;
            var hasValidPath = pathAndExtensionPattern.test(path);
            if (!hasValidPath) {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "The path or port number is [output,strong,not] valid."
                    )
                );
                return result;
            }

            // Check for any left over cloneUrl parts that indicates spaces were used in the URL construction.
            if (unparsed !== "") {
                result.isValid = false;
                result.add(
                    "cloneURLValidator",
                    LOCALE.maketext(
                        "The clone URL [output,strong,cannot] include whitespace characters."
                    )
                );
                return result;
            }

            return result;

            /**
             * Private method to combine all validation error messages from other validators.
             * This method is used to reduce the number of LOCALE.maketext calls via re-useability.
             *
             * @method combineErrorMessages
             * @param {array} msgArr - An array of messages from the validation-utils :: ValidationResult object.
             * @return {string} msg - All messages combined into one string.
             */
            function combineErrorMessages(msgArr) {
                var msg = "";

                for (var a = 0, len = msgArr.length; a < len; a++) {
                    msg +=
                        a !== len - 1
                            ? msgArr[a].message + " "
                            : msgArr[a].message;
                }

                return msg;
            }
        },
    };

    var validatorModule = angular.module("cjt2.validate");

    validatorModule.run([
        "validatorFactory",
        function(validatorFactory) {
            validatorFactory.generate(cloneURLValidator);
        },
    ]);

    return {
        methods: cloneURLValidator,
        name: "clone-url-validator",
        description: "Validation on git-based clone URLs.",
        version: 1.0,
    };
});

/*
# version_control/views/CreateRepositoriesController.js      Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define, PAGE */

define(
    'app/views/createRepositoriesController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "uiBootstrap",
        "app/services/versionControlService",
        "app/services/sshKeyVerification",
        "cjt/services/alertService",
        "cjt/directives/alert",
        "cjt/directives/alertList",
        "cjt/directives/actionButtonDirective",
        "cjt/directives/toggleSwitchDirective",
        "cjt/directives/toggleLabelInfoDirective",
        "app/services/versionControlService",
        "cjt/directives/validationContainerDirective",
        "cjt/directives/validationItemDirective",
        "cjt/validator/ascii-data-validators",
        "cjt/validator/path-validators",
        "app/services/directoryLookupService",
        "app/directives/cloneURLValidator",
        "cjt/filters/htmlFilter",
        "cjt/decorators/uibTypeaheadDecorator",
    ],
    function(angular, _, LOCALE) {
        "use strict";

        var app = angular.module("cpanel.versionControl");
        app.value("PAGE", PAGE);

        var controller = app.controller(
            "CreateRepositoriesController",
            ["$scope", "$location", "versionControlService", "sshKeyVerification", "PAGE", "alertService", "directoryLookupService",
                function($scope, $location, versionControlService, sshKeyVerification, PAGE, alertService, directoryLookupService) {

                    var repository = this;

                    // home directory path
                    repository.homeDirPath = PAGE.homeDir + "/";

                    repository.displaySuccessSummary = false;

                    // initialize form data
                    repository.formData = {
                        repoName: "",
                        repoPath: "",
                        clone: true,
                        cloneURL: "",
                        createAnother: false
                    };

                    repository.ssh = {};

                    repository.pathExcludeList = "[^'\":\\\\*?<>|@&=%#`$(){};\\[\\]\\s]+";// This is for angular input validation.
                    var directoryLookupFilter = /[%*{}()=?`$@:|[\]'"<>&#;\s\\]+/g;// This is the same regex for directory lookup service filter.

                    // Utility function
                    function _bothAreSameServer(obj1, obj2) {
                        return obj1
                            && obj2
                            && obj1.hostname === obj2.hostname
                            && obj2.port     === obj2.port;
                    }

                    /**
                     * When a user inputs a Clone URL, this method is fired to perform
                     * an API check against the user's knowns_hosts file.
                     *
                     * While some state is updated after the API check, the data mostly
                     * lays dormant until the createRepository method is called.
                     *
                     * All of the relevant state is stored on the repository.ssh object.
                     */
                    repository.checkKnownHosts = function() {

                        var cloneUrl = repository.formData.cloneURL;
                        var newServer = cloneUrl && sshKeyVerification.getHostnameAndPort(cloneUrl);

                        if (!newServer) {
                            repository.ssh = {};
                            return;
                        }

                        if ( _bothAreSameServer(repository.ssh, newServer) ) {
                            return;
                        }

                        repository.ssh.hostname = newServer.hostname;
                        repository.ssh.port = newServer.port;
                        repository.ssh.status = "verifying";
                        repository.ssh.keys = [];

                        function _updateScope(data) {

                            /**
                             * It's possible to have a race if there are multiple checks in flight
                             * simultaneously. This check ensures that we only update the scope if
                             * the finished request was initiated using the current input value.
                             */
                            if ( !_bothAreSameServer(repository.ssh, newServer) ) {
                                return;
                            }

                            repository.ssh.status = data.status;
                            repository.ssh.keys = data.keys;
                            return data.status;
                        }

                        return repository.ssh.promise = sshKeyVerification.verify(newServer.hostname, newServer.port).then(_updateScope, _updateScope);
                    };

                    /**
                     * Back to List View
                     * @method backToListView
                     */
                    repository.backToListView = function() {
                        $location.path("/list");
                    };

                    /**
                     * Reset Form Data
                     * @method resetFormData
                     * @param {Object} opts   An object of optional values.
                     * @param {Boolean} opts.isCreateAnother   If true, it will only be a partial reset for Create Another.
                     */
                    repository.resetFormData = function(opts) {
                        repository.formData = {
                            repoName: "",
                            repoPath: "",
                            clone: opts.isCreateAnother ? repository.formData.clone : true,
                            cloneURL: "",
                            createAnother: Boolean(opts.isCreateAnother),
                        };
                        repository.createRepoForm.$setPristine();
                    };

                    /**
                     * Create Repository
                     * @method createRepository
                     * @return {Promise} returns promise.
                     */
                    repository.createRepository = function() {

                        if (!repository.formData.repoName || !repository.formData.repoPath) {
                            return;
                        }

                        alertService.clear(null, "versionControl");

                        if (!repository.formData.clone) {
                            repository.formData.cloneURL = null;
                        }


                        if (repository.formData.cloneURL && (repository.ssh.status === "unrecognized-new" || repository.ssh.status === "unrecognized-changed")) {
                            _showKeyVerificationModal();
                            return;
                        } else if (repository.formData.cloneURL && repository.ssh.promise) {
                            return repository.ssh.promise.then(function(status) {
                                delete repository.ssh.promise;
                                return repository.createRepository();
                            });
                        } else {
                            return _createRepository();
                        }
                    };

                    /**
                     * Opens up the modal for SSH key verification.
                     */
                    function _showKeyVerificationModal() {
                        alertService.clear(null, "versionControl");

                        repository.ssh.modal = sshKeyVerification.openModal({
                            hostname: repository.ssh.hostname,
                            port: repository.ssh.port,
                            type: repository.ssh.status,
                            keys: repository.ssh.keys,
                            onAccept: _onAcceptKey,
                        });

                        /**
                         * Handle the case where the modal is dismissed, rather than closed.
                         * Dismissal is when the user clicks the cancel button or if they click
                         * outside of the modal, causing it to disappear.
                         */
                        repository.ssh.modal.result.catch(function() {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The system [output,strong,cannot] clone this repository if you do not trust the host key for “[output,strong,_1]”. To create your repository, select one of the following options:", repository.ssh.hostname),
                                list: [
                                    LOCALE.maketext("Enter a clone URL that uses the HTTPS or Git protocols instead of SSH."),
                                    LOCALE.maketext("Enter a clone URL for a different, previously-trusted host."),
                                    LOCALE.maketext("Click [output,em,Create] again and choose to trust the remote server."),
                                ],
                                closeable: true,
                                replace: true,
                                group: "versionControl",
                                id: "known-hosts-verification-cancelled",
                            });
                        });
                    }

                    function _onAcceptKey(promise) {
                        return promise.then(
                            function success(newStatus) {
                                repository.ssh.status = newStatus;
                                return _createRepository();
                            },
                            function failure(error) {
                                alertService.add({
                                    type: "danger",
                                    message: LOCALE.maketext("The system failed to add the fingerprints from “[_1]” to the [asis,known_hosts] file: [_2]", repository.ssh.hostname, error),
                                    closeable: true,
                                    replace: true,
                                    group: "versionControl",
                                    id: "known-hosts-verification-failure",
                                });
                            }
                        ).finally(function() {
                            repository.ssh.modal.close();
                            delete repository.ssh.modal;
                        });
                    }

                    /**
                     * The common bits for actually creating the repo.
                     */
                    function _createRepository() {

                        var repositoryPath = repository.homeDirPath + repository.formData.repoPath;

                        return versionControlService.createRepository(
                            repository.formData.repoName,
                            repositoryPath,
                            repository.formData.cloneURL).then(function(response) {

                            // Clone Repository Success
                            if (repository.formData.cloneURL) {
                                alertService.add({
                                    type: "info",
                                    message: LOCALE.maketext("The system successfully initiated the clone process for the “[_1]” repository.", repository.formData.repoName) + " " + LOCALE.maketext("The system may require more time to clone large remote repositories."),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl",
                                    id: response.cloneTaskID,
                                    counter: false
                                });

                                if (!repository.formData.createAnother) {
                                    repository.backToListView();
                                } else {
                                    repository.resetFormData({ isCreateAnother: true });
                                }

                            } else {

                                // Create repository Success
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("The system successfully created the “[_1]” repository.", repository.formData.repoName),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });

                                if (!repository.formData.createAnother) {
                                    var repoSummary = response;
                                    var cloneURL = repoSummary.cloneURL;

                                    if (typeof cloneURL !== "undefined" && cloneURL) {
                                        repository.displaySuccessSummary = true;

                                        repository.summary = {};
                                        repository.summary.remoteURL = cloneURL;
                                        var parts = repository.summary.remoteURL.split("/");

                                        if (parts && parts.length > 0) {
                                            repository.summary.directoryName = parts[parts.length - 1];
                                        } else {
                                            repository.summary.directoryName = "";
                                        }

                                        repository.summary.readOnly = repoSummary.clone_urls.read_write.length === 0 ? true : false;
                                    } else {
                                        repository.backToListView();
                                    }
                                } else {
                                    repository.resetFormData({ isCreateAnother: true });
                                }
                            }

                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: error,
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    }

                    /**
                     * Directory lookup
                     * @method completeDirectory
                     * @return {Promise} Returns an array of directory paths.
                     */
                    repository.completeDirectory = function(prefix) {
                        var directoryLookupPromise = directoryLookupService.complete(prefix);
                        var outputDirectories = [];

                        return directoryLookupPromise.then(function(directories) {

                            for ( var i = 0, len = directories.length; i < len; i++ ) {

                                var directoryName = directories[i];

                                if ( directoryName.search(directoryLookupFilter) === -1 ) {
                                    outputDirectories.push(directoryName);
                                }
                            }

                            return outputDirectories;
                        });
                    };

                    /**
                     * Toggle Status
                     * @method toggleStatus
                     * @return {Boolean} Returns true.
                     */
                    repository.toggleStatus = function() {
                        repository.formData.clone = !repository.formData.clone;
                        return true;
                    };

                    /**
                     * Autofill Repository path and name based on clone url
                     * @method autoFillPathAndName
                     */
                    repository.autoFillPathAndName = function() {
                        if (!repository.formData.repoName && !repository.formData.repoPath) {
                            if (repository.createRepoForm.repoCloneURL.$valid && repository.formData.cloneURL) {
                                var cloneUrl = repository.formData.cloneURL;
                                var repoPathPrefix = "repositories/";

                                // Removing training slash
                                cloneUrl = cloneUrl.replace(/\/+$/, "");

                                // finding last part of the url and replacing .git if present
                                var repoDirectory = cloneUrl.substr(cloneUrl.lastIndexOf("/") + 1).replace(".git", "");
                                repoDirectory = repoDirectory.replace(directoryLookupFilter, "_");

                                var repositoryPath = repoPathPrefix + repoDirectory;
                                repository.formData.repoPath = repositoryPath;
                                repository.formData.repoName = repoDirectory;
                            }
                        }
                    };
                }
            ]
        );

        return controller;
    }
);

/*
# version_control/views/manageRepositoriesController.js      Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define, PAGE */

define(
    'app/views/manageRepositoriesController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "uiBootstrap",
        "app/services/versionControlService",
        "app/services/sseAPIService",
        "cjt/services/alertService",
        "cjt/directives/alert",
        "cjt/directives/alertList",
        "cjt/directives/actionButtonDirective",
        "jquery-chosen",
        "angular-chosen",
        "cjt/decorators/angularChosenDecorator",
    ],
    function(angular, _, LOCALE) {
        "use strict";

        var app = angular.module("cpanel.versionControl");
        app.value("PAGE", PAGE);

        var controller = app.controller(
            "ManageRepositoriesController",
            ["$scope", "$window", "$location", "$timeout", "versionControlService", "sseAPIService", "PAGE", "$routeParams", "alertService", "sshKeyVerification", "$q",
                function($scope, $window, $location, $timeout, versionControlService, sseAPIService, PAGE, $routeParams, alertService, sshKeyVerification, $q) {

                    var repository = this;

                    // RTL check for chosen
                    repository.isRTL = false;
                    var html = document.querySelector("html");
                    if (html) {
                        repository.isRTL = html.getAttribute("dir") === "rtl";
                    }

                    // Page defaults
                    repository.isLoading = true;

                    repository.deployInProgress = false;
                    repository.deployState = "";
                    repository.deployedTaskInformation = null;
                    repository.deployCalloutType = "info";

                    // SSE events and config
                    var deploySSEURL = "";
                    var sseObj;
                    var events = [ "log_update", "task_complete", "task_failed" ];
                    var config = { json: true };

                    var tabs = [
                        "basic-info",
                        "deploy"
                    ];

                    var tabToSelect = 0;

                    // Get the variables from the URL
                    var requestedRepoPath = decodeURIComponent($routeParams.repoPath);
                    var tabName = decodeURIComponent($routeParams.tabname);

                    selectActiveTab(tabName);

                    /**
                     * Selects Active Tab
                     * @method selectActiveTab
                     * @param {String} tabName Tab Name
                     */
                    function selectActiveTab(tabName) {

                        // Selecting tab based on route parameter
                        if (tabName) {
                            tabToSelect = tabs.indexOf(tabName);
                            if ( tabToSelect !== -1) {
                                $scope.activeTabIndex = tabToSelect;
                            } else {
                                $location.path("/list/");
                            }
                        } else {
                            $location.path("/list/");
                        }
                    }

                    retrieveRepositoryInfo(requestedRepoPath);

                    /**
                    * Changes active tab
                    *
                    * @method changeActiveTab
                    * @param {String} name name of the tab.
                    */
                    $scope.changeActiveTab = function(name) {
                        var url = $location.url();
                        var lastPart = url.split( "/" ).pop().toLowerCase();

                        if (name) {
                            $scope.activeTabIndex = tabs.indexOf(name);

                            // lastpart other than name
                            if (lastPart !== name) {
                                $location.path("/manage/" + encodeURIComponent(requestedRepoPath) + "/" + name);
                            }
                        }
                    };

                    /**
                    * Checks to see if the user came from the VersionControl List View
                    *
                    * @method retrieveRepositoryInfo
                    * @param {String} requestedRepoPath Represents the path of the repository to be loaded on the page.
                    */
                    function retrieveRepositoryInfo(requestedRepoPath) {

                        var repoInfo;
                        return versionControlService.getRepositoryInformation(requestedRepoPath, "name,tasks,clone_urls,branch,last_update,source_repository,last_deployment,deployable")
                            .then(function(response) {
                                repoInfo = response;
                                var branchPromise = _retrieveAvailableBranches(requestedRepoPath);

                                /**
                                 * If we fail to retrieve the available branches, we will let the UI
                                 * go ahead and display in its mostly broken state, while performing
                                 * this SSH key check in the background for later use with the Try
                                 * Again button.
                                 */
                                branchPromise.catch(function() {
                                    var sshServer = sshKeyVerification.getHostnameAndPort(response && response.source_repository && response.source_repository.url);
                                    if (sshServer) {
                                        repository.ssh = {};
                                        repository.ssh.hostname = sshServer.hostname;
                                        repository.ssh.port = sshServer.port;
                                        repository.ssh.promise = sshKeyVerification.verify(sshServer.hostname, sshServer.port);
                                    }
                                });

                                return branchPromise;
                            }, function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(error),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });
                                $location.path("/list/");

                            })
                            .finally(function() {
                                setFormData(repoInfo);
                                repository.isLoading = false;
                            });
                    }

                    function _retrieveAvailableBranches(requestedRepoPath) {
                        return versionControlService.getRepositoryInformation(requestedRepoPath, "available_branches")
                            .then(function(response) {
                                repository.branchList = response && response.available_branches || [];
                            }, function(error) {
                                repository.unableToRetrieveAvailableBranches = true;
                                return $q.reject(error);
                            });
                    }

                    /**
                     * We don't want to show an alert if there are issues fetching the branches during the
                     * initial page load, but if it's in response to a user action it's good to have feedback.
                     */
                    function _retrieveAvailableBranchesTryAgain() {
                        return _retrieveAvailableBranches( repository.repoPath ).catch(function() {
                            alertService.add({
                                type: "danger",
                                id: "retrieve-branches-again-error",
                                message: _.escape(LOCALE.maketext("The system cannot update information for the repository at “[_1]” because it cannot access the remote repository.", repository.repoPath)),
                                closeable: true,
                                replace: true,
                                group: "versionControl"
                            });
                        });
                    }

                    /**
                     * Changes the text for the callout that's used when unableToRetrieveAvailableBranches = true.
                     *
                     * @param  {Boolean} hasRemote   True when the repository was cloned from a remote repo.
                     */
                    $scope.$watch("repository.hasRemote", function(hasRemote) {
                        if (hasRemote) {
                            repository._noConnectionText = LOCALE.maketext("The system could not contact the remote repository.");
                            repository._tryAgainTooltipText = LOCALE.maketext("Attempt to contact the remote repository again.");
                        } else {
                            repository._noConnectionText = LOCALE.maketext("The system could not read from the repository.");
                            repository._tryAgainTooltipText = LOCALE.maketext("Attempt to read from the repository again.");
                        }
                    });

                    /**
                     * Try to fetch the list of available_branches again.
                     *
                     * @return {Promise}                    Resolves if it successfully retrieves the available branches.
                     *                                      Rejects otherwise.
                     */
                    repository.tryAgain = function tryAgain() {
                        alertService.removeById("retrieve-branches-again-error", "versionControl");
                        if (!repository.ssh.promise) {
                            return _retrieveAvailableBranchesTryAgain();
                        }

                        return repository.ssh.promise.then(
                            function success() {

                                // SSH host key verification is not the problem, so just try again
                                return _retrieveAvailableBranchesTryAgain();
                            },
                            function failure(data) {
                                repository.ssh.status = data.status;
                                repository.ssh.keys = data.keys;

                                _showKeyVerificationModal();
                            }
                        );
                    };

                    /**
                     * Opens up the modal for SSH key verification.
                     */
                    function _showKeyVerificationModal() {
                        alertService.clear(null, "versionControl");

                        repository.ssh.modal = sshKeyVerification.openModal({
                            hostname: repository.ssh.hostname,
                            port: repository.ssh.port,
                            type: repository.ssh.status,
                            keys: repository.ssh.keys,
                            onAccept: _onAcceptKey,
                        });

                        /**
                         * Handle the case where the modal is dismissed, rather than closed.
                         * Dismissal is when the user clicks the cancel button or if they click
                         * outside of the modal, causing it to disappear.
                         */
                        repository.ssh.modal.result.catch(function() {
                            alertService.add({
                                type: "danger",
                                message: _.escape(LOCALE.maketext("The system [output,strong,cannot] connect to the remote repository if you do not accept the host key for “[output,strong,_1].”", repository.ssh.hostname)),
                                closeable: true,
                                replace: true,
                                group: "versionControl",
                                id: "known-hosts-verification-cancelled",
                            });
                        });

                        return repository.ssh.modal;
                    }

                    function _onAcceptKey(promise) {
                        return promise.then(
                            function success(newStatus) {
                                repository.ssh.status = newStatus;
                                return _retrieveAvailableBranchesTryAgain();
                            },
                            function failure(error) {
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(LOCALE.maketext("The system failed to add the fingerprints from “[_1]” to the [asis,known_hosts] file: [_2]", repository.ssh.hostname, error)),
                                    closeable: true,
                                    replace: true,
                                    group: "versionControl",
                                    id: "known-hosts-verification-failure",
                                });
                            }
                        ).finally(function() {
                            repository.ssh.modal.close();
                            delete repository.ssh.modal;
                        });
                    }

                    /**
                    * Set Manage Form Data
                    *
                    * @method setFormData
                    * @param {object} data Represents the single repository data object.
                    */
                    function setFormData(data) {
                        repository.name = data.name;
                        repository.repoPath = data.repository_root;
                        repository.cloneURL = data.clone_urls.read_write[0];

                        repository.branch = data.branch;
                        repository.checkedoutBranch = data.branch;

                        repository.hasActiveBranch = data.hasActiveBranch;

                        repository.hasHeadInformation = data.hasHeadInformation;
                        repository.lastUpdateSHA = data.lastUpdateSHA;
                        repository.lastUpdateDate = data.lastUpdateDate;
                        repository.commitMessage = data.commitMessage;
                        repository.author = data.author;

                        if (data.available_branches) {
                            repository.branchList = data.available_branches;
                        }

                        repository.hasRemote = data.hasRemote;
                        repository.remoteInformation = data.source_repository;

                        repository.gitWebURL = data.gitWebURL;
                        repository.fileManagerRedirectURL = data.fileManagerRedirectURL;

                        repository.fullRepoPath = repository.repoPath;

                        repository.qaSafeSuffix = data.qaSafeSuffix;

                        repository.deployInProgress = data.deployInProgress;

                        repository.deployable = data.deployable;
                        repository.hasDeploymentInformation = data.hasDeploymentInformation;
                        repository.lastDeployedDate = data.lastDeployedDate;
                        repository.lastDeployedSHA = data.lastDeployedSHA;
                        repository.lastDeployedAuthor = data.lastDeployedAuthor;
                        repository.lastDeployedCommitDate = data.lastDeployedCommitDate;
                        repository.lastDeployedCommitMessage = data.lastDeployedCommitMessage;

                        repository.changesAvailableToDeploy = data.lastDeployedSHA !== data.lastUpdateSHA;

                        repository.deployTasks = getDeployTasks(data.tasks);

                        if (typeof sseObj === "undefined" && repository.deployInProgress) {
                            initializeSSE();
                        }
                    }

                    function getDeployTasks(tasks) {
                        var deployTasks =  _.map(tasks, function(task) {
                            if (task.action === "deploy") {
                                var timestampInfo = getDeployTimestamp(task.args.log_file);
                                return {
                                    task_id: task.task_id,
                                    log_file: task.args.log_file,
                                    sse_url: task.sse_url,
                                    timeStamp: timestampInfo,
                                    humanReadableDate: LOCALE.local_datetime(timestampInfo, "datetime_format_medium")
                                };
                            }
                        });

                        return _.sortBy(deployTasks, [function(o) {
                            return o.log_file;
                        }]);
                    }

                    /**
                     * @method getQueuedTaskString
                     * @param {Number} taskCount TaskCount
                     * @returns {String} Additional tasks display string
                     */
                    function getQueuedTaskString(taskCount) {
                        return LOCALE.maketext("[quant,_1,additional task,additional tasks] queued", taskCount);
                    }

                    /**
                     * Gets Deployment timestamp
                     * @method getDeployTimestamp
                     * @param {String} logFilePath LogFile path
                     * @returns {String} deployment timestamp
                     */
                    function getDeployTimestamp(logFilePath) {
                        var timeStamp;
                        if (logFilePath) {
                            var logFileName = logFilePath.split("/").pop();
                            timeStamp = logFileName.match(/\d+(\.\d+)/g);
                        }
                        return timeStamp[0];
                    }

                    /**
                     * Update Repository
                     * @method updateRepository
                     * @return {Promise} Returns a promise from the VersionControlService.updateRepository method for success/error handling when the user requests to update a repository.
                     */
                    repository.updateRepository = function() {

                        var branch = repository.branch === repository.checkedoutBranch ? "" : repository.branch;

                        return versionControlService.updateRepository(
                            repository.repoPath,
                            repository.name,
                            branch
                        ).then(function(response) {

                            alertService.add({
                                type: "success",
                                message: _.escape(LOCALE.maketext("The system successfully updated the “[_1]” repository.", repository.name)),
                                closeable: true,
                                replace: true,
                                autoClose: 10000,
                                group: "versionControl"
                            });

                            setFormData(response.data);

                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };

                    /**
                     * Pull from remote repository
                     * @method pullFromRemote
                     * @return {Promise} Returns a promise from the VersionControlService.updateRepository method for success/error handling when the user requests to pull from remote repository.
                     */
                    repository.pullFromRemote = function() {

                        return versionControlService.updateFromRemote(
                            repository.repoPath,
                            repository.branch
                        ).then(function(response) {

                            var data = response.data;

                            if (repository.lastUpdateSHA === data.lastUpdateSHA) {
                                alertService.add({
                                    type: "info",
                                    message: _.escape(LOCALE.maketext("The “[_1]” repository is up-to-date.", repository.name)),
                                    closeable: true,
                                    replace: true,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });
                            } else {
                                alertService.add({
                                    type: "success",
                                    message: _.escape(LOCALE.maketext("The system successfully updated the “[_1]” repository.", repository.name)),
                                    closeable: true,
                                    replace: true,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });

                                repository.hasHeadInformation = data.hasHeadInformation;
                                repository.lastUpdateSHA = data.lastUpdateSHA;
                                repository.lastUpdateDate = data.lastUpdateDate;
                                repository.commitMessage = data.commitMessage;
                                repository.author = data.author;

                                repository.newCommits = true;

                                $timeout( function() {
                                    repository.newCommits = false;
                                }, 10000 );
                            }
                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };

                    /**
                     * Reset deployment and sse flags
                     * @method resetSSEState
                     */
                    function resetSSEState() {
                        repository.deployState = "";
                        repository.deployCalloutType = "info";
                        repository.deployedTaskInformation = null;

                        sseObj = null;
                    }

                    /**
                     * Initialize SSE
                     * @method initializeSSE
                     */
                    function initializeSSE() {

                        repository.queuedDeployTasksCount = repository.deployTasks.length - 1;

                        if (repository.queuedDeployTasksCount) {
                            repository.queuedTaskString = getQueuedTaskString(repository.queuedDeployTasksCount);
                        }

                        repository.firstDeployTask = repository.deployTasks[0];
                        deploySSEURL = repository.firstDeployTask.sse_url;

                        repository.deployProgress = LOCALE.maketext("The deployment that you triggered on [_1] is in progress …", repository.firstDeployTask.humanReadableDate);
                        repository.deployComplete = LOCALE.maketext("The deployment that you triggered on [_1] is complete. Updating last deployment information …", repository.firstDeployTask.humanReadableDate);
                        repository.deployQueued =  LOCALE.maketext("The deployment that you triggered on [_1] is queued …", repository.firstDeployTask.humanReadableDate);
                        sseAPIService.initialize();
                    }

                    /**
                     * Handles ready.
                     *
                     * @method
                     * @param {sse:ready} event - ready event.
                     * @listens sse:ready
                     */
                    $scope.$on("sse:ready", function(event) {
                        deploySSEURL = PAGE.securityToken + deploySSEURL;
                        sseObj = sseAPIService.connect(deploySSEURL, events, config);
                    });

                    /**
                     * Handles destroy event.
                     *
                     * @method
                     * @listens $destroy
                     */
                    $scope.$on("$destroy", function() {
                        if (sseObj) {
                            sseAPIService.close(sseObj);
                        }
                    });

                    /**
                     * Handles log_update.
                     *
                     * @method
                     * @param {sse:log_update} event - Task log update event.
                     * @param {String} data - log data
                     * @listens sse:log_update
                     */
                    $scope.$on("sse:log_update", function(event, data) {
                        repository.deployState = "processing";
                        $scope.$apply();
                    });

                    /**
                     * Handles task_complete.
                     *
                     * @method
                     * @param {sse:task_complete} event - Task complete event.
                     * @param {Object} data - Data
                     * @listens sse:task_complete
                     */
                    $scope.$on("sse:task_complete", function(event, data) {
                        var taskData = data;
                        sseAPIService.close(sseObj);
                        repository.deployCalloutType = "success";
                        repository.deployState = "complete";

                        $scope.$apply();

                        $timeout(function() {
                            return versionControlService.getRepositoryInformation(repository.repoPath, "last_deployment,tasks")
                                .then(function(data) {
                                    repository.lastDeployedDate = data.lastDeployedDate;
                                    repository.lastDeployedSHA = data.lastDeployedSHA;
                                    repository.lastDeployedAuthor = data.lastDeployedAuthor;
                                    repository.lastDeployedCommitDate = data.lastDeployedCommitDate;
                                    repository.lastDeployedCommitMessage = data.lastDeployedCommitMessage;

                                    repository.hasDeploymentInformation = true;

                                    repository.changesAvailableToDeploy = data.lastDeployedSHA !== repository.lastUpdateSHA;

                                    repository.deployTasks = getDeployTasks(data.tasks);

                                    resetSSEState();

                                    if (repository.deployTasks && repository.deployTasks.length > 0) {
                                        repository.deployInProgress = true;
                                        initializeSSE();
                                    } else {
                                        repository.deployInProgress = false;
                                    }

                                    repository.newDeployCommit = true;

                                    $timeout( function() {
                                        repository.newDeployCommit = false;
                                    }, 5000 );
                                }, function(error) {

                                    // display error
                                    alertService.add({
                                        type: "danger",
                                        message: _.escape(error.message),
                                        closeable: true,
                                        replace: false,
                                        group: "versionControl"
                                    });
                                });
                        }, 5000);
                    });

                    /**
                     * Handles task_failed.
                     *
                     * @method
                     * @param {sse:task_failed} event - Task failed event.
                     * @param {Object} data - Data
                     * @listens sse:task_failed
                     */
                    $scope.$on("sse:task_failed", function(event, data) {
                        sseAPIService.close(sseObj);
                        var deployedTaskInfo = repository.deployedTaskInformation;
                        var logFileInfo = getLogFileDetails(deployedTaskInfo.log_path);

                        alertService.add({
                            type: "danger",
                            message: LOCALE.maketext("Error occurred while deploying.") +
                                    " " +
                                    LOCALE.maketext("You can view the log file: [output,url,_1,_2,target,_3]", logFileInfo.fileManagerURL, logFileInfo.fileName, "_blank"),
                            closeable: true,
                            replace: false,
                            group: "versionControl"
                        });

                        $scope.$apply();

                        return versionControlService.getRepositoryInformation(repository.repoPath, "tasks")
                            .then(function(data) {
                                repository.deployTasks = getDeployTasks(data.tasks);

                                resetSSEState();

                                if (repository.deployTasks && repository.deployTasks.length > 0) {
                                    repository.deployInProgress = true;
                                    initializeSSE();
                                } else {
                                    repository.deployInProgress = false;
                                }

                            }, function(error) {

                            // display error
                                alertService.add({
                                    type: "danger",
                                    message: _.escape(error.message),
                                    closeable: true,
                                    replace: false,
                                    group: "versionControl"
                                });
                            });
                    });

                    /**
                     * Get log file details
                     * @method getLogFileDetails
                     *
                     * @param {Object} logFilePath logfile path
                     * @return {Object} Log file details
                     */
                    function getLogFileDetails(logFilePath) {
                        var logFileInfo = {};

                        if (logFilePath) {

                            // construct the file manager url for log file
                            var fileName = logFilePath.split( "/" ).pop();
                            var dirPath = PAGE.homeDir + "/.cpanel/logs";
                            var fileManangerURL = PAGE.deprefix + "filemanager/showfile.html?file=" + encodeURIComponent(fileName) + "&dir=" + encodeURIComponent(dirPath);

                            logFileInfo.fileName = fileName;
                            logFileInfo.fileManagerURL =  fileManangerURL;
                        }

                        return logFileInfo;
                    }

                    /**
                     * Deploy repository
                     * @method deployRepository
                     * @return {Promise} Returns a promise from the VersionControlService.deployRepository method for success/error handling when the user requests to deploy their repository.
                     */
                    repository.deployRepository = function() {
                        return versionControlService.deployRepository(
                            repository.repoPath
                        ).then(function(response) {
                            var data = response.data || {};

                            /**
                             * We have to fake the task object, since the task data returned from the
                             * VersionControlDeployment::create and VersionControlDeployment::retrieveAPI calls don't match.
                             */
                            repository.deployTasks = getDeployTasks([
                                {
                                    action: "deploy",
                                    task_id: data.task_id,
                                    sse_url: data.sse_url,
                                    args: {
                                        log_file: data.log_path,
                                    },
                                }
                            ]);

                            if (repository.deployTasks && repository.deployTasks.length > 0) {
                                repository.deployInProgress = true;
                                initializeSSE();
                            } else {
                                repository.deployInProgress = false;
                            }

                        }, function(error) {
                            repository.deployInProgress = false;
                            alertService.add({
                                type: "danger",
                                message: _.escape(error) +
                                         " " +
                                         LOCALE.maketext("For more information, read our [output,url,_1,documentation,target,_2].", "https://go.cpanel.net/GitDeployment", "_blank"),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        });
                    };


                    /**
                     * Back to List View
                     * @method backToListView
                     */
                    repository.backToListView = function() {
                        $location.path("/list");
                    };

                    /**
                     * Opens repository in gitWeb
                     * @method redirectToGitWeb
                     * @param {String} gitWebURL gitWebURL for the repository
                     * @param {String} repoName Repository name
                     */
                    repository.redirectToGitWeb = function(gitWebURL, repoName) {

                        if (gitWebURL) {
                            $window.open(gitWebURL, repoName + "GitWeb");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The system could not find the repository’s [asis,Gitweb] [output,acronym,URL,Universal Resource Locator]."),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Opens repository path in file manager
                     * @method redirectToFileManager
                     * @param {String} fileManagerURL file Manager url for the repository path
                     * @param {String} repoName Repository name
                     */
                    repository.redirectToFileManager = function(fileManagerURL, repoName) {

                        if (fileManagerURL) {
                            $window.open(fileManagerURL, repoName + "FileManager");
                        } else {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The system could not redirect you to the File Manager interface."),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     * Copies the repo's clone link to your machine's clipboard
                     * @method cloneToClipboard
                     * @param {String} cloneUrl The URL to be used to clone repos.
                     */
                    repository.cloneToClipboard = function(cloneUrl) {
                        try {
                            var result = versionControlService.cloneToClipboard(cloneUrl);
                            if (result) {
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("The system successfully copied the “[_1]” clone [output,acronym,URL,Uniform Resource Locator] to the clipboard.", cloneUrl),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "versionControl"
                                });
                            }
                        } catch (error) {
                            alertService.add({
                                type: "danger",
                                message: _.escape(error),
                                closeable: true,
                                replace: false,
                                group: "versionControl"
                            });
                        }
                    };

                    /**
                     *  Checks if there are available branches or not.
                     *  @method hasAvailableBranches
                     *  @return {Boolean} Returns if there are any branches in the branchList.
                     */
                    repository.hasAvailableBranches = function() {
                        return Boolean(repository.branchList && repository.branchList.length !== 0);
                    };


                }
            ]
        );

        return controller;
    }
);

/*
# version_control/index.js                        Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global require: false, define: false, PAGE: false */

define(
    'app/index',[
        "angular",
        "cjt/core",
        "cjt/modules",
        "ngRoute",
        "uiBootstrap",
        "cjt/services/alertService",
        "cjt/directives/alert",
        "cjt/directives/alertList",
        "cjt/directives/callout",
        "jquery-chosen",
        "angular-chosen"
    ],
    function(angular) {

        "use strict";

        return function() {

            // First create the application
            angular.module("cpanel.versionControl", [
                "ngRoute",
                "ui.bootstrap",
                "cjt2.cpanel",
                "cpanel.versionControl.service",
                "cpanel.versionControl.sshKeyVerificationService",
                "cpanel.services.directoryLookup",
                "cpanel.versionControl.sseAPIService",
                "localytics.directives",
            ]);

            // Then load the application dependencies
            var app = require(
                [
                    "cjt/bootstrap",
                    "app/views/listRepositoriesController",
                    "app/views/createRepositoriesController",
                    "app/views/manageRepositoriesController",
                ], function(BOOTSTRAP) {

                    var app = angular.module("cpanel.versionControl");
                    app.value("PAGE", PAGE);

                    app.config([
                        "$routeProvider",
                        function($routeProvider) {
                            $routeProvider.when("/list/", {
                                controller: "ListRepositoriesController",
                                controllerAs: "repositories",
                                templateUrl: "views/listRepositoriesView.ptt",
                            });

                            $routeProvider.when("/create/", {
                                controller: "CreateRepositoriesController",
                                controllerAs: "repository",
                                templateUrl: "views/createRepositoriesView.ptt",
                            });

                            $routeProvider.when("/manage/:repoPath/:tabname?", {
                                controller: "ManageRepositoriesController",
                                controllerAs: "repository",
                                templateUrl: "views/manageRepositoriesView.ptt",
                            });

                            $routeProvider.otherwise({
                                "redirectTo": "/list"
                            });
                        }
                    ]);

                    BOOTSTRAP("#content", "cpanel.versionControl");

                });

            return app;
        };
    }
);

Back to Directory File Manager