File manager - Edit - /usr/local/cpanel/base/frontend/jupiter/version_control/index.cmb.js
Back
(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 = { "<": "<", ">": ">", '"': """, "'": "'", "`": "`" }; unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g; return text.replace(unsafe_chars, function(chr) { return map[chr] || "&"; }); }; 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; }; } );
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.06 |
proxy
|
phpinfo
|
Settings