Viewing File: /usr/local/cpanel/base/frontend/jupiter/site-monitor/index.cmb.js

/*
# site-monitor/views/ROUTES.js                     Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.views.ROUTES */

define(
    'app/views/ROUTES',[
        "cjt/util/locale"
    ],
    function(LOCALE) {

        "use strict";

        var ROUTES = [
            {
                "id": "listSiteMonitors",
                "path": "/",
                "hideTitle": false,
                "controller": "listSiteMonitors",
                "templateUrl": "views/listSiteMonitors.phtml",
                "title": LOCALE.maketext("Site Monitors"),
                "breadcrumb": {
                    "id": "listSiteMonitors",
                    "name": LOCALE.maketext("Site Monitors"),
                    "path": "/"
                },
                "resolve": {
                    "initialSites": ["sites", function(sitesAPI) {
                        return sitesAPI.get().then(
                            function(payload) {
                                return payload;
                            },
                            function(e) {
                                return new Error(e.error);
                            }
                        );
                    }]
                }
            },
        ];

        return ROUTES;
    }
);

/*
 * site-monitor/models/enumHelper.js                Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enumHelper */

define('app/models/enum-helper',[],
    function() {

        "use strict";

        return {
            /**
             * Return the key from the value if it exists in the enum.
             *
             * @param {object} theEnum
             * @param {string} value
             * @returns string
             */
            fromString: function(theEnum, value) {
                if (!theEnum || typeof theEnum !== 'object') {
                    throw new Error('You must pass an object as the enum.');
                }

                if (!value || !( typeof value === 'string' || typeof value === 'number' ) ) {
                    throw new Error('The value must be either a string or number');
                }

                var key = Object.keys(theEnum).find(function(key) { return theEnum[key] === value });

                if (typeof key === 'undefined') {
                    throw new Error('The enum does not contain a property with the value ' + value);
                }

                return key;
            }
        }
    }
);
/*
 * site-monitor/models/domain-type.enum.js          Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enums.DomainType */

define('app/models/domain-type.enum',[
        'app/models/enum-helper'
    ],
    function(EnumHelper) {

        "use strict";

        /**
         * @typedef {string} Type
         */

        /**
         * The various domain types supported by the monitoring tools.
         * @name DomainType
         * @enum {Type}
         */
        var DomainType = {
            SUBDOMAIN: "subdomain",
            ADDON: "addon",
            ALIAS: "alias",
            MAIN: "main_domain",
            EXTERNAL: "external",
        };

        /**
         * Return the key from the value if it exists in the enum.
         *
         * @param {string} value
         * @returns string
         */
        DomainType.fromString = function(value) {
            return EnumHelper.fromString(DomainType, value);
        };

        return DomainType;
    }
);
/*
 * site-monitor/models/monitor-status.enum.js       Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enums.MonitorStatus */

define('app/models/monitor-status.enum',[
        'app/models/enum-helper'
    ],
    function(EnumHelper) {

        "use strict";

        /**
         * @typedef {string} Status
         */

        /**
         * @name MonitorStatus
         * @enum {Status}
         */
        var MonitorStatus = {
            Down: 'down',
            Up: 'up',
        };

        /**
         * Return the key from the value if it exists in the enum.
         *
         * @param {string} value
         * @returns string
         */
        MonitorStatus.fromString = function(value) {
            return EnumHelper.fromString(MonitorStatus, value);
        };

        return MonitorStatus;
    }
);

/*
 * site-monitor/models/monitor-type.enum.js         Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enums.MonitorType */

define('app/models/monitor-type.enum',[
        'app/models/enum-helper'
    ],
    function(EnumHelper) {

        "use strict";

        /**
         * @typedef {string} Type
         */

        /**
         * @name MonitorType
         * @enum {Type}
         */
         var MonitorType = {
            HTTP: 'http',
            HTTPS: 'https',
            ICMP: 'icmp',
            TCP: 'telnet',
        };


        /**
         * Return the key from the value if it exists in the enum.
         *
         * @param {string} value
         * @returns string
         */
         MonitorType.fromString = function(value) {
            return EnumHelper.fromString(MonitorType, value);
        };

        return MonitorType;
    }
);

/*
 * site-monitor/models/uptime-window.enum.js        Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enums.UptimeWindow */

define('app/models/uptime-window.enum',[
        'app/models/enum-helper'
    ],
    function(EnumHelper) {

        "use strict";

        /**
         * @typedef {string} UptimeWindowType
         */

        /**
         * @name UptimeWindow
         * @enum {UptimeWindowType}
         */
         var UptimeWindow = {
            Last24Hours: 'last 24 hours',
            Last7Days: 'last 7 days',
            Last30Days: 'last 30 days',
            SinceStart: 'since start',
        };

        /**
         * Return the key from the value if it exists in the enum.
         *
         * @param {string} value
         * @returns string
         */
        UptimeWindow.fromString = function(value) {
            return EnumHelper.fromString(UptimeWindow, value);
        }

        return UptimeWindow;
    }
);

/*
 * site-monitor/models/monitor-state.enum.js        Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.enums.MonitorState */

define('app/models/monitor-state.enum',[
        'app/models/enum-helper'
    ],
    function(EnumHelper) {

        "use strict";

        /**
         * @typedef {string} State
         */

        /**
         * @name MonitorState
         * @enum {Status}
         */
        var MonitorState = {
            Creating: 'creating',
            Removing: 'removing',
            Ready:    'ready',
            Error:    'error',
            None:     'none',
        };

        /**
         * Return the key from the value if it exists in the enum.
         *
         * @param {string} value
         * @returns string
         */
        MonitorState.fromString = function(value) {
            return EnumHelper.fromString(MonitorState, value);
        };

        return MonitorState;
    }
);

/*
 * site-monitor/models/monitor.js                   Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.monitor */

define('app/models/monitor',[
        "app/models/monitor-status.enum",
        "app/models/monitor-type.enum",
        "app/models/uptime-window.enum",
        "app/models/monitor-state.enum",
    ],
    function(MonitorStatus, MonitorType, UptimeWindow, MonitorState) {

        "use strict";

        var DEFAULT_CHECK_INTERVAL = 1800;  // 30 min

        /**
         * @typedef Monitor
         * @property {string} id - The unique id for the monitor provided by the backend.
         * @property {string} name - The user given name for the monitor.
         * @property {MonitorType} protocol - The protocal to access the site with.
         * @property {string} domain - The domain to monitor.
         * @property {number} port - Port the site is hosted on if any.
         * @property {string} path - Path under the domain if any.
         * @property {string} search  - The querystring associated with the site url.
         * @property {boolean} enabled - Weather the monitor is enabled or not, NOTE: not supported yet by nixstats
         * @property {ISiteCheck2[]} data - average response time by hour for last 24 hours.
         * @property {Contact} contact - The contact information for the monitor.
         * @property {Date} firstUpdate - The date/time of the first check.
         * @property {Date} lastUpdate -  The date/time of the most recent check.
         * @property {string} fromLocation - The location monitors check from.
         * @property {number} interval - The interval that the site is check in seconds. NOTE: We don't know how to retrive this right now.
         * @property {Date} sslExpiration - When HTTPS, the expiration date for the SSL certificate. Only present when retrieving the list of monitors.
         * @property {MonitorState} [state] - optional, default to MonitorState.Ready.
         */

        /**
         *
         * @param {Monitor} monitor
         */
        function Monitor(monitor) {
            if (monitor) {
                this.id = monitor.id;
                this.name = monitor.name;

                this.protocol = monitor.protocol || MonitorType.HTTPS;
                this.domain = monitor.domain;
                this.port = monitor.port || _getDefaultPort(this.protocol);
                this.path = monitor.path || '';
                this.search = monitor.search || '';

                this.enabled  = monitor.enabled || true;

                this.data = monitor.data || [];

                this.contact = monitor.contact;

                this.firstUpdate = monitor.firstUpdate;
                this.lastUpdate  = monitor.lastUpdate;
                this.fromLocation = monitor.fromLocation;

                this.interval = monitor.interval || DEFAULT_CHECK_INTERVAL;
                this.sslExpiration = monitor.sslExpiration;
                this.state = monitor.state || MonitorState.Ready;
            }
        }

        /**
         * Getter for the full URL for the monitor.
         */
        Monitor.prototype.url = function() {
            return (/^http/.test(this.protocol) ? this.protocol + "://" : "") +
                   this.domain +
                   (this.port !== _getDefaultPort(this.protocol) ? ":" + this.port : "") +
                   "/" + this.path +
                   (this.search !== '' ? "?" + this.search : "");
        }

        /**
         * Clone the monitor.
         *
         * @returns {Monitor}
         */
        Monitor.prototype.clone = function() {
            return new Monitor(_.cloneDeep(this));
        }

        /**
         * Calculate the default port number to be used with the given protocol.
         *
         * @param {MonitorType} protocol
         * @returns {Number} the default port number.
         */
        function _getDefaultPort(protocol) {
            switch(protocol) {
                case MonitorType.ICMP:
                    return; // ICMP does not use ports as it is neither TCP or UDP, it is a lower level IP protocol.
                case MonitorType.TCP:
                    return 23; // Described as telnet, so uses the default telnet port.
                case MonitorType.HTTP:
                    return 80;
                case MonitorType.HTTPS:
                default:
                    return 443;
            }
        }

        /**
         * @typedef {number} Timestamp
         */

        /**
         * @typedef ISiteCheck
         * @property {number} t - Total time
         * @property {numbrer} c - Time to connect to the server
         * @property {string} status - The status message returned by the site check
         * @property {Timestamp} time - The time the last site check was processed.
         * @property {number} code - Status code returned by the site check
         * @property {number} dns - Time for dns resolution
         * @property {number} ttfb - Time to first byte
         */

        /**
         * @typedef ICheckFrom
         * @property {string} name - The location the site was checked from.
         * @property {string} ip_address_v6 - optional the locations ipv6 address.
         * @property {string} ip_address - the locations ipv4 address.
         */

        /**
         * @typedef IMonitorItem
         * @property {number} downtime_seconds - number of seconds the site was down.
         * @property {Timestamp} first_update - the first time the monitore was updated.
         * @property {string} id - unique idenfier for the monitor
         * @property {string|null} ip_address - ???
         * @property {ISiteCheck} last_check - statistics from the last site check.
         * @property {Timestamp} last_update - the last time the monitor was updated.
         * @property {ICheckFrom} monitor - the location the site was checked from.
         * @property {string} name - the human readable name for the monitor.
         * @property {Timestamp} ssl_expiration_timestamp - expiration date for the certificate. Only pressent on https requests.
         * @property {MonitorStatus} status - the last status for the site.
         * @property {string} status_message - the status message from the last check.
         * @property {MonitorType} type - the monitor type.
         * @property {number} uptime_percentage - the percentage of uptime
         * @property {string} url - the full url without the protocol.
         */

        /**
         * Parse the raw monitor data from the apis into a Monitor object.
         *
         * @param {IMonitorItem} item
         * @returns {Monitor}
         */
        Monitor.parseListItem = function parseListItem(item) {
            var fullUrl = "https://" + item.url;
            var url = new URL(fullUrl);

            return new Monitor({
                id: item.id,
                name: item.name,

                protocol: item.type,
                domain: url.hostname,
                port: Number(url.port),
                path: url.pathname.replace(/^\//, ""),
                search: url.search.replace(/^\?/, ""),

                type: item.type,

                // UNIX timestamps are in sec, javascript timestamps are in ms
                firstUpdate: new Date(item.first_update * 1000),
                lastUpdate: new Date(item.last_update * 1000),
                sslExpiration: new Date(item.ssl_expiration_timestamp * 1000), // UNIX timestamps are in sec, javascript timestamps are in ms

                fromLocation: item.monitor.name,

                // TODO: parse more data
            });
        }

        /**
         * @typedef {object} Contact
         * @property {object} email - the contact is an email address
         *   @property {number} delay - number of seconds to delay before sending the alert.
         */

        /**
         * @typedef ISiteCheck2
         * @property {number} time_total - Total time
         * @property {number} time_connect - Time to connect to the server
         * @property {string} status - The status message returned by the site check
         * @property {number} time_dns - Time for dns resolution
         * @property {number} time_to_first_byte - Time to first byte
         */

        /**
         * @typedef IUptimeWindow
         * @property {UptimeWindow} date - the kind of uptime window.
         * @property {number} events - number of events seen during the window.
         * @property {number} downtime_seconds - number of seconds the site was down.
         * @property {number} uptime_percentage - the percentage of time the site was down.
         * @property {Timestamp} start - the start date/time for the window
         * @property {Timestamp} end - the start date/time for the window
         */

        /**
         * @typedef {object} IMonitorDetails
         * @property {Object.<string, Contact>} contacts - list of contact to alert when the site is down
         * @property {number} code
         * @property {Timestamp} first_update - the date/time of the first update
         * @property {string} id - uniquie id for the monitor.
         * @property {string|null} ip_address - ???
         * @property {boolean} password - ???
         * @property {Timestamp} last_update - the date/time of the last update
         * @property {ISiteCheck2} last_check - the times for the most recent check.
         * @property {ICheckFrom} monitor - the location the monitor was checked from.
         * @property {string} name - the user friendly name of the monitor
         * @property {MonitorType} type - the protocol for the monitor.
         * @property {MonitorStatus} status - the current status for the monitor.
         * @property {string} url - the full url including the protocol.
         * @property {IUptimeWindow[]} uptimes - summaries of various uptime windows.
         * @property {boolean} username - ???
         */

        /**
         * Parser for data returned by get_domain_monitor
         *
         * @param {IMonitorDetails} item
         * @returns {Monitor}
         */
        Monitor.parseItem = function parseItem(item) {
            var url = new URL(item.url);

            return new Monitor({
                id: angular.isDefined(item.id) ? item.id: null,
                name: angular.isDefined(item.name) ? item.name: null,

                protocol: item.type,
                domain: url.hostname,
                port: url.port,
                path: url.pathname.replace(/^\//, ""),
                search: url.search.replace(/^\?/, ""),

                firstUpdate: angular.isDefined(item.first_update) ? new Date(item.first_update * 1000) : null,
                lastUpdate: angular.isDefined(item.last_update) ? new Date(item.last_update * 1000) : null,
                fromLocation: angular.isDefined(item.monitor) ? item.monitor.name : null,

                interval: angular.isDefined(item.interval) ? item.interval: null,

                state: angular.isDefined(item.state) ? item.state: null,

                // TODO: parse more data
            });
        }

        /**
         * The default interval that monitors check the site/page being monitored in ms.
         *
         * @type Number
         */
        Monitor.DEFAULT_CHECK_INTERVAL = DEFAULT_CHECK_INTERVAL;

        return Monitor;
    }
);
/*
 * site-monitor/models/site.js                      Copyright 2021 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 */

/** @namespace cpanel.siteMoniter.models.site */

define('app/models/site',[
        "lodash",
        "app/models/monitor",
        "app/models/domain-type.enum",
    ],
    function(_, Monitor, DomainType) {

        "use strict";

        /**
         * @typedef Site
         * @property {string} documentRoot - the file system path to where the sites files are stored, if any.
         * @property {string} domain - the domain the site is hosted under.
         * @property {boolean} hasValidHTTPSAliases - ???
         * @property {string} homedir - the homedir for the user that owns the site, if any.
         * @property {boolean} isHttpsRedirecting - true when the domain is always redirect to HTTPS.
         * @property {Monitor[]} monitors - list of monitors attached to the site.
         * @property {boolean} fetchFailed - true if the fetch failed, false otherwise.
         * @property {boolean} nonHTTPS - false if the site is HTTPS, true otherwise.
         * @property {string} protocol - one of: http, https
         * @property {string} realRootDomain - the main domain for the account.
         * @property {string} redirectsTo - the url the site redirects to, if any.
         * @property {string} rootDomain - the root domain the site is hosted on.
         * @property {DomainType} type - the type of the domain.
         */


        function Site(site) {
            var self = this;
            self.monitors = site.monitors || [];
            self.fetchFailed = site.fetchFailed || false;

            Object.keys(site).forEach(function(key) {
                self[key] = site[key];
            });

            self.type = DomainType[DomainType.fromString(site.type)];
            self.protocol = self.isHttpsRedirecting ? "https" : "http";
        }

        /**
         * Generate a default match function if one is not provided. By default we match by name.
         *
         * @param {Partial<Monitor>} value - The monitor to find. Only needs the fields consumed by the match function
         * @param {Function} match - The match function, will match by name if not provided.
         * @returns
         */
        Site.prototype._makeMatcher = function _makeMatcher(partial, match) {
            if (!match || typeof match !== 'function') {
                match = function(m) {
                    return m.name === partial.name;
                };
            }
            else {
                // Generate the matcher from the factory function
                match = match(partial);
            }
            return match;
        }

        /**
         * Find matching monitors in the collection.
         *
         * @param {Partial<Monitor>} partial - The monitor to find. Only needs the fields consumed by the match function
         * @param {Function} match - The match function, will match by name if not provided.
         * @returns {Monitor[]} The list of matching monitors.
         */
        Site.prototype.findMonitor = function findMonitor(partial, match) {
            match = this._makeMatcher(partial, match);
            return this.monitors.filter(match);
        }

        /**
         * Find first matching monitor in the collection.
         *
         * @param {Partial<Monitor>} partial - The monitor to find. Only needs the fields consumed by the match function
         * @param {Function} match - The match function, will match by name if not provided.
         * @returns {Monitor} The first matching monitor in the list
         */
         Site.prototype.findFirstMonitor = function findFirstMonitor(partial, match) {
            match = this._makeMatcher(partial, match);
            return this.monitors.find(match);
        }

        /**
         * Add a monitor to the list of monitors. It will overwrite any montiors that match.
         * If match is a string, its the name of the monitor. If its a function, its a predicate
         * used to compare monitors for equality
         *
         * @method addMonitor
         * @param {Monitor} monitor - The monitor to add.
         * @param {Function} match - The match function, will match by name field if not provided.
         */
         Site.prototype.addMonitor = function addMonitor(monitor, match) {
            match = this._makeMatcher(monitor, match);
            var index = _.findIndex(this.monitors, match);
            if (index !== -1) {
                // Replace existing monitor
                this.monitors[index] = monitor;
            } else {
                this.monitors.push(monitor);
            }
        };

        /**
         * Remove a monitor from the list of monitors.
         *
         * @method removeMonitor
         * @param {Monitor} monitor - The monitor to remove
         * @param {Function} match - The match function, will match by name field if not provided.
         */
        Site.prototype.removeMonitor = function removeMonitor(monitor, match) {
            match = this._makeMatcher(monitor, match);
            _.remove(this.monitors, match);
        }

        /**
         * Check to see if there are any monitors for a giving site.
         *
         * @returns true when there are enabled monitors and false when there are no enabled monitors.
         */
        Site.prototype.anyEnabled = function anyEnabled() {
            return this.monitors.length > 0;
        }

        return Site;
    }
);
/*
 * site-monitor/services/monitoring.js              Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.monitoring */

define(
    'app/services/monitoring',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/io/uapi-request",
        "cjt/io/uapi",
        "cjt/modules",
        "cjt/services/APICatcher",

    ],
    function(angular, _, LOCALE, UAPIRequest) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.monitoringService", [
            "cjt2.services.apicatcher",
        ]);

        app.factory("monitoring", [
            "$q",
            "APICatcher",
            "PAGE",
            function($q, APICatcher, PAGE) {

                var MonitorAPI = function() {};
                MonitorAPI.prototype = Object.create(APICatcher);

                /**
                 * Fake list method to make the build work.
                 *
                 * @async
                 * @method get
                 * @returns {IMonitorItem[]} - list of all the monitors available from the nixstats engine for this user.
                 */
                MonitorAPI.prototype.list = function list() {
                    var self = this;

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "get_all_site_monitors");
                    return self.promise(apiCall).then(function(result) {
                        return result.data.response.monitors;
                    });
                }

                /**
                 * Get the monitor by its uniqe id
                 *
                 * @async
                 * @method get
                 * @params {String} id - the id of the monitor
                 * @returns {IMonitorDetails} The details of the requested monitor.
                 */
                MonitorAPI.prototype.get = function get(id) {
                    var self = this;

                    if (typeof(id) !== 'string' || id === '') {
                        throw new Error('The `id` argument is required.')
                    }

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "get_site_monitor");
                    apiCall.addArgument("id", id);
                    return self.promise(apiCall).then(function(result) {
                        return result.data.response;
                    });
                };

                /**
                 * Enable monitoring for the site
                 *
                 * @async
                 * @method enable
                 * @params {String} site - the url to monitor. Example: domain.com/api/list?p=1
                 * @params {String} [ name ] - optional name of the monitor, will default to the domain from the url if not provided
                 * @returns {boolean} returns true if successful, false otherwise.
                 */
                MonitorAPI.prototype.enable = function enable(site, name, emails) {

                    var self = this;

                    if (typeof(site) !== 'string' || site === '') {
                        throw new Error('The `domain` argument is required.')
                    }
                    if (emails && !Array.isArray(emails)) {
                        throw new Error('The `emails` argument must be an array of email addresses.');
                    }

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "create_site_monitor");
                    apiCall.addArgument("site", site);
                    apiCall.addArgument("name", name || site);
                    apiCall.addArgument("email", emails);
                    return self.promise(apiCall).then(function(result) {
                        if (result.data.http_code === "200") {
                            return result.data.response.id;
                        } else {
                            throw new Error(LOCALE.maketext("The system could not create the monitor for [_1] with the error: [_2]", site, result.data.response.error))
                        }
                    });
                };

                /**
                 * Disable an existing site monitor
                 *
                 * @async
                 * @method disable
                 * @params {String} id - the unique id of the monitor
                 * @params {String} url - the url for the monitor. Only used in messages.
                 * @returns {boolean} returns true if successful, false otherwise.
                 */
                MonitorAPI.prototype.disable = function disable(id, url) {
                    var self = this;

                    if (typeof(id) !== 'string' || id === '') {
                        throw new Error('The `id` argument is required.')
                    }

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "remove_site_monitor");
                    apiCall.addArgument("id", id);
                    return self.promise(apiCall).then(function(result) {
                            if (result.data.http_code === "204") {
                            return true;
                        } else {
                            throw new Error(LOCALE.maketext("The system could not remove the monitor for [_1] with the error: [_2]", url, result.data.response.error))
                        }
                    });
                };

                return new MonitorAPI();
            }
        ]);
    }
);

/*
 * site-monitor/services/sitesCache.js              Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.sitesCache */

define(
    'app/services/sitesCache',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/io/uapi-request",
        "app/models/site",
        "app/models/monitor",
        "cjt/util/parse",
        "cjt/io/uapi",
        "cjt/io/api2",
        "cjt/modules",
        "cjt/services/APICatcher",

    ],
    function(angular, _, LOCALE, UAPIRequest, Site, Monitor, PARSE) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.sitesCacheService", []);

        var CAN_EDIT_DOCROOT = {
            documentRoot: true,
        };

        app.factory("sitesCache", [
            function() {

            /**
             * Caching service for sites.
             *
             * @module sites
             * @example
             * SiteCache.add(new Site({domain: 'a.tld', docroot: 'public_html/a'}));
             * SiteCache.add(new Site({domain: 'b.tld', docroot: 'public_html/b'}));
             *
             * SiteCache.get().for((site) => {
             *   console.log(site);
             * });
             *
             * my siteA = SiteCache.findByDomainName('a.tld');
             * if (siteA) {
             *  console.log('found');
             * } else {
             *  console.log('not found');
             * }
             */

            var _flattenedSites = [];
            var _siteLookupMap = {};

            var SitesCache = function() {};

            /**
             * Cache a site.
             *
             * @private
             * @method add
             * @param {Site} site - the domain information about the site.
             * @returns Site - a Site object instantiated from the site param,
             *     regardless of whether the site was pre-existing or added to
             *     the cache with this call
             */
            SitesCache.prototype.add = function _add(site) {

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

                var site = new Site(site);
                if (_siteLookupMap[site.domain]) {
                    this.remove(site.domain);
                }

                _siteLookupMap[site.domain] = site;
                _flattenedSites.push(site);

                return site;
            };

            /**
             * Clear the cache completly.
             *
             * @method clear
             */
            SitesCache.prototype.clear = function _clear() {
                _flattenedSites = [];
                _siteLookupMap = {};
            };

            /**
             * Remove a previously cached Site object by the domain name.
             *
             * @private
             * @method remove
             * @param {string} domainName
             * @returns
             */
            SitesCache.prototype.remove = function _remove(domainName) {
                var self = this;

                var siteObject = self.findByDomainName(domainName);
                if (!siteObject) {
                    return false;
                }
                for (var i = _flattenedSites.length - 1; i >= 0; i--) {
                    if (_flattenedSites[i].domain === siteObject.domain) {
                        _flattenedSites.splice(i, 1);
                        delete _siteLookupMap[domainName];
                        return true;
                    }
                }

                return false;
            };

            /**
             * Returns the cached sites list.
             *
             * @public
             * @method get
             * @returns {Site[]}
             */
            SitesCache.prototype.get = function get() {
                return _flattenedSites;
            };

            /**
             * Find a site object by the domain name
             *
             * @method findByDomainName
             * @public
             * @param  {String} domainName - domain name for the site as a whole
             * @return {Site} returns the site object if found
             */
            SitesCache.prototype.findByDomainName = function _findByDomainName(domainName) {
                return _siteLookupMap[domainName];
            };

            /**
             * Lookup an existing monitor on a site if it exists.
             *
             * @param {String} domainName - domain name for the site as a whole
             * @param {String} url - the url for the monitor
             * @returns {Monitor?} returns the monitor if one is found matching the url in the site with the domainName.
             */
            SitesCache.prototype.findMonitor = function _findMonitor(domainName, url) {
                var site = this.findByDomainName(domainName);
                if (!site) {
                    return;
                }
                return site.monitors.find(function(monitor) {
                    return monitor.url() === url;
                });
            };

            return new SitesCache();
        }]);
    }
);

/*
 * site-monitor/services/sites.js                      Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.sites */

define(
    'app/services/sites',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/io/uapi-request",
        "app/models/domain-type.enum",
        "app/models/site",
        "app/models/monitor",
        "cjt/util/parse",
        "app/services/monitoring",
        "cjt/io/uapi",
        "cjt/io/api2",
        "cjt/modules",
        "cjt/services/APICatcher",
        "app/services/sitesCache"
    ],
    function(angular, _, LOCALE, UAPIRequest, DomainType, Site, Monitor, PARSE) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.sitesService", [
            "cjt2.services.apicatcher",
            "cpanel.siteMonitor.sitesCacheService",
            "cpanel.siteMonitor.monitoringService"
        ]);

        app.value("PAGE", PAGE);

        app.factory("sites", [
            "$q",
            "APICatcher",
            "PAGE",
            "sitesCache",
            "monitoring",
            function($q, APICatcher, PAGE, SitesCache, MonitoringAPI) {

            /**
             * service wrapper for domain related functions
             *
             * @module sites
             *
             * @param  {Object} $q angular $q object
             * @param  {Object} APICatcher cjt2 APICatcher service
             * @param  {Object} DOMIAIN_TYPE_CONSTANTS constants objects for use on domain types
             * @param  {Object} PAGE window.PAGE object
             *
             * @example
             *  let sites = siteService.get();
             *  if(!sites) return;
             *  for(site in sites) {
             *      ...
             *  }
             */

            var _mainDomain;
            var _parkDomains;
            var _addOnDomains;
            var _subDomains;

            var Sites = function() {};
            Sites.prototype = APICatcher;

            /**
             * Lookup the sub-domain information by the subdomain name. This automatically
             * added the main domain suffix so all you pass is the subdomain segment.
             *
             * @private
             * @method _getSubDomainObject
             * @param {string} subdomainName
             * @returns {DomainInfo|undefined}
             */
             Sites.prototype._getSubDomainObject = function _getSubDomainObject(subdomainName) {
                var self = this;
                if (typeof subdomainName === "string") {
                    return SitesCache.findByDomainName(subdomainName + "." + self.getMainDomain().domain);
                }

                return;
            };

            /**
             * Updates the subdomains that host addon domains so the associations are identified.
             *
             * @private
             * @method _associateAddonDomains
             */
            Sites.prototype._associateAddonDomains = function _associateAddonDomains() {
                var self = this;

                angular.forEach(_addOnDomains, function(addonDomain) {
                    var subdomainObject = self._getSubDomainObject(addonDomain.subdomain);
                    if (subdomainObject) {
                        subdomainObject.associatedAddonDomain = addonDomain.domain;
                    }
                });
            };

            /**
             * Get the currently stored main domain
             *
             * @method getMainDomain
             * @public
             * @return {DomainInfo} returns the current main domain object
             */

            Sites.prototype.getMainDomain = function _getMainDomain() {
                return _mainDomain;
            };

            /**
             * Find a domain object by the domain name
             *
             * @method findByDomainName
             * @public
             * @param  {String} domainName domain name (bob.com)
             * @return {Object} returns the domain object if found
             */
            Sites.prototype.findByDomainName = function _findByDomainName(domainName) {
                return SitesCache.findByDomainName(domainName);
            };

            /**
             * Fetch the sites and caches them. This also returns the main domain information.
             *
             * @public
             * @async
             * @method fetchSites
             * @return {Promise<Site[]>} returns a promise, then the sites for the account.
             */
            Sites.prototype.fetchSites = function fetchSites() {

                var self = this;

                var apiCall = new UAPIRequest.Class();
                apiCall.initialize("DomainInfo", "domains_data");
                apiCall.addArgument("return_https_redirect_status", 1);

                return self.promise(apiCall).then(function(result) {

                    var mainDomain = self.parseDomain(result.data.main_domain);
                    mainDomain.type = DomainType.MAIN;
                    _mainDomain = mainDomain;
                    SitesCache.add(mainDomain);

                    // Cache (most of) the rest of the domains to speed this up
                    _subDomains = [];
                    var domains = result.data.sub_domains || [];
                    domains.forEach(function(rawDomain) {
                        var parsedDomain = self.parseDomain(rawDomain, DomainType.SUBDOMAIN);
                        _subDomains.push(parsedDomain);
                        SitesCache.add(parsedDomain);
                    });

                    _addOnDomains = [];
                    domains = result.data.addon_domains || [];
                    domains.forEach(function(rawDomain) {
                        var parsedDomain = self.parseDomain(rawDomain, DomainType.ADDON);
                        _addOnDomains.push(parsedDomain);
                        SitesCache.add(parsedDomain);

                        // Also add in this thing's backing subdomain
                        var subdomain = self.parseDomain(rawDomain, DomainType.ADDON);
                        subdomain.domain = subdomain.rootDomain;
                        subdomain.type   = DomainType.SUBDOMAIN;
                        _subDomains.push(subdomain);
                        SitesCache.add(subdomain);
                    });

                    _parkDomains = [];
                    result.data.parked_domains.forEach(function(rawDomain) {
                        var parsedDomain    = self.parseDomain(result.data.main_domain, DomainType.ALIAS);
                        parsedDomain.domain = rawDomain;
                        _parkDomains.push(parsedDomain);
                        SitesCache.add(parsedDomain);
                    });

                    return mainDomain;
                });
            };

            /**
             * Helper to check if any required keys are missing
             *
             * @param {Object} object - object to check.
             * @returns {Function} - reducer build the list of missing keys.
             */
            function makeMissingKeysReducer(object) {
                return function(missing, key) {
                    if(!object[key]) {
                        missing.push(key);
                    }
                    return missing;
                }
            }
            /**
             * Parse the domain information returned by the api to build
             * a Site model.
             *
             * @method parseDomain
             * @param {DomainInfo} rawDomain
             * @param {DomainType} actualType
             * @returns {Site}
             */
            Sites.prototype.parseDomain = function parseDomain(rawDomain, actualType) {
                if (!rawDomain) {
                    throw new Error("Invalid domain information object");
                }
                var keys = Object.keys(rawDomain);
                if (!keys || !keys.length) {
                    throw new Error("Invalid domain information object");
                }

                var missingRequired = [
                        "domain",
                        "homedir",
                    ].reduce(makeMissingKeysReducer(rawDomain), []);
                if (missingRequired.length) {
                    throw new Error("The domain information object is missing the keys: " + missingRequired.join(","));
                }

                if (!rawDomain.documentroot && !rawDomain.dir) {
                    throw new Error("The domain information object must have one of the keys: " + [ "documentroot" , "dir" ].join(","));
                }

                var self = this;
                var domain = {
                    domain: rawDomain.domain,
                    homedir: rawDomain.homedir,
                    documentRoot: rawDomain.documentroot || rawDomain.dir,
                    rootDomain: rawDomain.servername,
                    isHttpsRedirecting: PARSE.parseBoolean(rawDomain.is_https_redirecting),
                    hasValidHTTPSAliases: PARSE.parseBoolean(rawDomain.all_aliases_valid),
                    nonHTTPS: !PARSE.parseBoolean(rawDomain.can_https_redirect),
                    redirectsTo: rawDomain.status === "not redirected" ? null : rawDomain.status,
                    type: rawDomain.type,
                    realRootDomain: PAGE.mainDomain,
                };

                if (actualType) {
                    domain.type    = actualType;
                    domain.homedir = self.getMainDomain().homedir;
                    var altRootDomain = self.getMainDomain().domain;
                    var altSubDomain = domain.rootDomain ? domain.rootDomain.substr(0, domain.rootDomain.lastIndexOf("." + altRootDomain)) : null;

                    if (actualType !== DomainType.ALIAS) {
                        domain.subdomain = altSubDomain;
                    }

                    if (actualType === DomainType.SUBDOMAIN) {
                        domain.rootDomain = altRootDomain;
                    } else if ( actualType === DomainType.ALIAS ) {
                        domain.rootDomain = PAGE.mainDomain;
                    }
                }
                return new Site(domain);
            };


            var sitesLoadingPromise = null;

            /**
             * Helper to update the state of the monitors when the fetch failed.
             */
            Sites.prototype._monitorFetchFailed = function failed() {
                var self = this;

                var sites = SitesCache.get();
                if (sites && sites.length > 0) {
                    sites.forEach(function(site) {
                        site.fetchFailed = true;
                    });
                }
            };

            /**
             * Fetch the sites and their monitors all domains (main, addon, subdomain, alias).
             * on a server. If there is a cached list already, the returned promise resolves to
             * the cached versions saving api requests. The Cache is only updated if the cache is
             * empty. You can use SitesCache.clear() to flush the cache before calling this method
             * to for a new load from the server.
             *
             * @public
             * @async
             * @method get
             * @param {Object} args
             *   @param {boolean} args.refresh - bypass and clear the cache before reloading
             * @return {Promise<Site[]>} returns a promise, then the array of all domain objects
             */
            Sites.prototype.get = function getSites(args) {

                var self = this;
                if (!args)  { args = {}; }
                var refresh = args.refresh || false;

                // Prevent additional fetches when an existing fetch
                // is already in progress. Just return the same promise.
                if (sitesLoadingPromise) {
                    return sitesLoadingPromise;
                }

                if (!refresh) {
                    var sites = SitesCache.get();
                    if (sites.length > 0) {
                        return $q.resolve(sites);
                    }
                }

                // Clear the cache
                SitesCache.clear();
                self.error = null;

                // Load a new dataset.
                sitesLoadingPromise = $q
                    .all( [
                        self.fetchSites().then(function(response) {
                            if (self.error) {
                                // Call here and in catch for all so we handle the race
                                // between the two api calls.
                                self._monitorFetchFailed();
                            }
                            return response;
                        }),
                        MonitoringAPI.list()
                    ] )
                    .then(function(results) {
                        self._associateAddonDomains();

                        var monitors = (typeof results[1] !== 'undefined') ? results[1] : undefined;
                        if (monitors) {
                            monitors.forEach(function(item) {
                                var monitor = Monitor.parseListItem(item);
                                var forDomain = monitor.domain.replace(/^www./, "");

                                var site = SitesCache.findByDomainName(forDomain);
                                if (site) {
                                    site.addMonitor(monitor);
                                } else {
                                    var newSite = new Site({ domain: forDomain, type: DomainType.EXTERNAL });
                                    newSite.addMonitor(monitor);
                                    SitesCache.add(newSite);
                                }
                            });
                        }

                        sitesLoadingPromise = null;
                        return SitesCache.get();
                    })
                    .catch(function(error) {
                        self.error = error;
                        // Call here and in then for domain so we handle the race
                        // between the two api calls.
                        self._monitorFetchFailed();
                        throw error;
                    });
                return sitesLoadingPromise;
            };

            return new Sites();
        }]);
    }
);

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

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

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

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

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

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

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

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

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

    return SelectParser;

  })();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    AbstractChosen.default_multiple_text = "Select Some Options";

    AbstractChosen.default_single_text = "Select an Option";

    AbstractChosen.default_no_result_text = "No results match";

    return AbstractChosen;

  })();

  $ = jQuery;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return Chosen;

  })(AbstractChosen);

}).call(this);


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

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

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

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

}).call(this);


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

/*
 * site-monitor/services/contacts.js                Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.contacts */

define(
    'app/services/contacts',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/io/uapi-request",
        "cjt/io/uapi",
        "cjt/modules",
        "cjt/services/APICatcher",

    ],
    function(angular, _, LOCALE, UAPIRequest) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.contactsService", [
            "cjt2.services.apicatcher",
        ]);

        app.factory("contacts",   [
            "$q",
            "APICatcher",
            "PAGE",
            function($q, APICatcher, PAGE) {

                var ContactsAPI = function() {};
                ContactsAPI.prototype = Object.create(APICatcher);

                /**
                 * Get the store email address saved on the cPanel Account.
                 *
                 * @async
                 * @returns {string} the store email address
                 */
                ContactsAPI.prototype.getStoreEmailAddress = function list() {
                    var self = this;

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "get_store_email_address");
                    return self.promise(apiCall).then(function(result) {
                        return result.data;
                    });
                }


                /**
                 * Get all contacts configured in Nixstats
                 *
                 * @async
                 * @method get
                 * @returns {IMonitorItem[]} - list of all the nixstats contacts already configured.
                 */
                ContactsAPI.prototype.list = function list() {
                    var self = this;

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "list_contacts");
                    return self.promise(apiCall).then(function(result) {
                        return result.data.response.contacts || [];
                    });
                }

                /**
                 * Get the contact by its uniqe id
                 *
                 * @async
                 * @method get
                 * @params {String} id - the id of the monitor
                 * @returns {IMonitorDetails} The details of the requested monitor.
                 */
                ContactsAPI.prototype.get = function get(id) {
                    var self = this;

                    if (typeof(id) !== 'string' || id === '') {
                        throw new Error('The `id` argument is required.')
                    }

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "get_contact");
                    apiCall.addArgument("id", id);
                    return self.promise(apiCall).then(function(result) {
                        return result.data.response;
                    });
                };

                /**
                 * Create a contact
                 *
                 * @async
                 * @method enable
                 * @params {String} name - The
                 * @params {String} email - optional name of the monitor, will default to the domain from the url if not provided
                 * @returns {boolean} returns true if successful, false otherwise.
                 */
                ContactsAPI.prototype.create = function create(name, email) {

                    var self = this;

                    if (typeof(name) !== 'string' || name === '') {
                        throw new Error('The `name` argument is required.')
                    }

                    if (typeof(email) !== 'string' || email === '') {
                        throw new Error('The `email` argument is required.')
                    }

                    var apiCall = new UAPIRequest.Class();
                    apiCall.initialize("Monitoring", "create_contact");
                    apiCall.addArgument("name", name);
                    apiCall.addArgument("email", email);
                    return self.promise(apiCall).then(function(result) {
                        if (result.error) {
                            throw new Error(result.error);
                        } else if(result.id) {
                            return result.id;
                        } else {
                            throw new Error("Unknown response");
                        }
                    });
                };

                return new ContactsAPI();
            }
        ]);
    }
);
/*
 * site-monitor/services/batch.js                   Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.batch */

define(
    'app/services/batch',[
        "angular"
    ],
    function(angular) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.batchService", []);

        app.factory("batch", [
            "$q",
            function($q) {

                return {
                    /**
                     * Perform the `op` on each item in the list in groups of `batchSize`. No more than `batchSize`
                     * Promises are running at once so the queue is not flooded.
                     *
                     * @async
                     * @template A
                     * @template B
                     * @param {A[]} list - list of items to operate on
                     * @param {number} batchSize - the number of items to process in parallel
                     * @param {Function(A) => Promise<B>} op - operation to perform on each item in the list
                     * @param {Function(A) => Identity<A>} gitId - a function that returns a string that uniquely identifies the item.
                     * @param {Function(A, string?) => void} doneItem - a function that is called after each op(item) call resolves(A) or rejects(A, error).
                     * @returns {Object.<string, B>} - a dictionary of the unique ids and the responses for that unique id.
                     */
                    uniform: function uniform(list, batchSize, op, getId, doneItem) {

                        var length = list.length;
                        var responses = {};
                        var i = 0;

                        return new $q(function(resolve, reject) {
                            (function loop() {
                                if (i < length) {
                                    // Create a list of promises
                                    var requests = list
                                            .slice(i, i + batchSize)
                                            .map(function(item) {
                                                return op(item)
                                                        .then(function(response) {
                                                            responses[ getId(item) ] = response;
                                                            if (doneItem) {
                                                                doneItem(item);
                                                            }
                                                        })
                                                        .catch(function(error) {
                                                            if (doneItem) {
                                                                doneItem(item, error);
                                                            }
                                                        });
                                            });

                                    // Wait for the current batch of ops to run
                                    return $q.all(requests)
                                             .finally(function() {
                                                 i += batchSize;
                                                 // Recurse into the next batch
                                                 loop();
                                             });
                                } else {
                                    resolve(responses); // Resolve the outer promise
                                }
                            })();
                        });
                    }
                }
            }
        ]);
    }
);
/*
# site-monitor/directives/whatsShowingDirective.js                   Copyright(c) 2021 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 */

/** @namespace cpanel.siteMonitor.directive.whatsShowing */

define(
    'app/directives/whatsShowingDirective',[
        "angular",
        "cjt/util/locale",
        "cjt/core"
    ],
    function(angular, LOCALE, CJT) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.whatsShowingDirective", []);

        module.directive("whatsShowing", function factory() {

            /**
             * Directive to render the "Showing 1 - 4 of 10"
             *
             * @module whats-showing
             *
             * @param  {Number} start first number in range ([1]-4)
             * @param  {Number} limit second number in range (1-[4])
             * @param  {Number} total total number of items (10)
             *
             * @example
             * <whats-showing start="1" limit="4" total="10"></whats-showing>
             *
             */

            var TEMPLATE_PATH = "directives/whatsShowingDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "EA",
                scope: {
                    start: "=",
                    limit: "=",
                    total: "="
                },
                transclude: true,
                controller: ["$scope", function($scope) {
                    $scope.LOCALE = LOCALE;
                }]
            };

        });
    }
);

/*
# site-monitor/directives/itemListerDirective.js
#                                               Copyright(c) 2021 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 */

/** @namespace cpanel.siteMonitor.directive.itemLister */

define(
    'app/directives/itemListerDirective',[
        "angular",
        "cjt/util/locale",
        "cjt/core",
        "ngRoute",
        "ngSanitize",
        "cjt/modules",
        "cjt/services/cpanel/componentSettingSaverService",
        "app/services/sites",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/searchDirective",
        "cjt/directives/pageSizeDirective",
        "app/directives/whatsShowingDirective",
        "cjt/filters/startFromFilter",
        "cjt/decorators/paginationDecorator",
    ],
    function(angular, LOCALE, CJT) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.itemListerDirective", [
            "cpanel.siteMonitor.whatsShowingDirective",
            "ngRoute",
            "ngSanitize",
            "cjt2.filters.startFrom",
        ]);

        module.directive("itemLister", [
            "$window",
            "$log",
            "componentSettingSaverService",
            function itemListerFactory($window, $log, $CSSS) {

            /**
             * Item Lister combines the typical table functions, pageSize,
             * showing, paginator, search, and allows you to plug in multiple
             * views.
             *
             * @module item-lister
             * @restrict EA
             *
             * @param  {String} id disseminated to other objects
             * @param  {Array} items Items that will be paginated, array of objs
             * @param  {Array} configuration Items that will be in the configuration
             * @param  {Array} headers represents the columns of the table
             *
             * @example
             * <item-lister
             *      id="MyItemLister"
             *      items="[a,b,c,d,e]"
             *      configuration="configuration"
             *      headers="[ { field:"blah", label:"Blah", sortable:false } ]">
             *   <my-item-view></my-item-view>
             * </item-lister>
             */

            var COMPONENT_NAME = "itemLister";
            var TEMPLATE_PATH = "directives/itemListerDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "EA",
                scope: {
                    parentID: "@id",
                    items: "=",
                    headers: "=",
                    configuration: "="
                },
                transclude: true,
                replace: true,
                link: function(scope, element, attrs, transclude) {

                    var controlsBlock;
                    var contentBlock;

                    /**
                     * Replace the control block with the actual controls.
                     *
                     * @param {Element} elem
                     */
                    function _transcludeListerControls(elem) {
                        controlsBlock.append(elem);
                    }

                    /**
                     * Replace the content block with the actual content.
                     *
                     * @param {Element} elem
                     */
                    function _transcludeContent(elem) {
                        elem.setAttribute("id", scope.parentID + "_transcludePoint");
                        elem.setAttribute("ng-if", "filteredItems.length");
                        contentBlock.replaceWith(elem);
                    }

                    /**
                     * Transclude the desired parts into the insertion point
                     *
                     * @param {Element} elem
                     */
                    function _transclude(elem) {
                        if (angular.element(elem).hasClass("lister-controls")) {
                            _transcludeListerControls(elem);
                        } else {
                            _transcludeContent(elem);
                        }
                    }

                    /**
                     * Transclude all the insertion points
                     */
                    function _processTranscludes() {
                        controlsBlock = element.find("#" + scope.parentID + "_transcludedControls");
                        contentBlock = element.find("#" + scope.parentID + "_transcludePoint");
                        var transcludedBlock = element.find("div.transcluded");
                        var transcludedItems = transcludedBlock.children();
                        angular.forEach(transcludedItems, _transclude);
                        transcludedBlock.remove();
                    }

                    /* There is a race condition here so we have to
                       delay to get the content transcluded */
                    setTimeout(_processTranscludes, 2);
                },
                controller: [
                    "$routeParams",
                    "$scope",
                    "$filter",
                    "ITEM_LISTER_CONSTANTS",
                    "sites",
                    "PAGE",
                    "alertService",
                    function itemListerController($routeParams, $scope, $filter, ITEM_LISTER_CONSTANTS, sitesService, alertService, PAGE) {
                    $scope.PAGE = PAGE;
                    $scope.LOCALE = LOCALE;
                    $scope.viewCallbacks = [];
                    $scope.disableBtn = true;

                    var filters = {
                        filter: $filter("filter"),
                        orderBy: $filter("orderBy"),
                        startFrom: $filter("startFrom"),
                        limitTo: $filter("limitTo")
                    };

                    /**
                     * Filter the items so only ones matching the requested string remain.
                     *
                     * @param {Site[]} items
                     * @returns {Site[]} only items matching the filter.
                     */
                    function _filter(items) {

                        // filter list based on search text
                        if ($scope.filterValue !== "") {
                            return filters.filter(items, $scope.filterValue, false);
                        }

                        return items;
                    }

                    /**
                     * Sort the items by the selected field and direction.
                     *
                     * @param {Site[]} items
                     * @returns {Site[]}
                     */
                    function _sort(items) {

                        // sort the filtered list
                        if ($scope.sort.sortDirection !== "" && $scope.sort.sortBy !== "") {

                            // Normally we would sort by the 'domains' field by virtue it was first.  It's not anymore, so correct that.
                            if ($scope.sort.sortBy === "batchSelect") {
                                $scope.sort.sortBy = "domain";
                            }
                            return filters.orderBy(items, $scope.sort.sortBy, $scope.sort.sortDirection !== "asc");
                        }

                        return items;
                    }

                    /**
                     * Apply the pagination filter to the data set.
                     *
                     * @param {Sites[]} items
                     * @returns {Sites[]} sites on the visible page only.
                     */
                    function _paginate(items) {

                        // filter list based on page size and pagination
                        if ($scope.totalItems > _.min($scope.pageSizes)) {
                            var start = ($scope.currentPage - 1) * $scope.pageSize;
                            var limit = $scope.pageSize;

                            items = filters.startFrom(items, start);
                            items = filters.limitTo(items, limit);
                            $scope.showPager = true;

                            // table statistics
                            $scope.start = start + 1;
                            $scope.limit = start + items.length;

                        } else {

                            // hide pager and pagination
                            $scope.showPager = false;

                            if (items.length === 0) {
                                $scope.start = 0;
                            } else {

                                // table statistics
                                $scope.start = 1;
                            }

                            $scope.limit = items.length;
                        }

                        return items;
                    }

                    /**
                     * Preserve the current lister state.
                     *
                     * @param {*} lastInteractedItem
                     * @returns
                     */
                    function _updatedListerState(lastInteractedItem) {

                        if ($scope.loadingInitialState) {
                            return;
                        }

                        var settings = {
                            totalItems: $scope.totalItems,
                            currentPage: $scope.currentPage,
                            pageSize: $scope.pageSize,
                            start: $scope.start,
                            limit: $scope.limit,
                            lastInteractedItem: lastInteractedItem,
                            filterValue: $scope.filterValue,
                            sort: {
                                sortDirection: $scope.sort.sortDirection,
                                sortBy: $scope.sort.sortBy
                            }
                        };

                        $CSSS.set(COMPONENT_NAME, settings);
                    }

                    /**
                     *
                     * @param {*} event
                     * @param {*} parameters
                     */
                    function _itemInteracted(event, parameters) {
                        if (parameters.interactionID) {
                            _updatedListerState(parameters.interactionID);
                        }
                    }

                    /**
                     * Update the all selected checkbox based on the view based on what
                     * sites are selected.
                     *
                     * @param {Event} event
                     * @param {Array} parameters
                     */
                    function _itemSelected(event, parameters) {

                        // Go over the sites until you find one that is not selected
                        var allSelected = true;
                        sitesService.get().then(function(sites) {
                            for (var i = 0; i < sites.length; i++) {
                                if (!sites[i].selected) {
                                    allSelected = false;
                                    break;
                                }
                            }

                            // Update Select All checkbox
                            $scope.selectAll = allSelected;

                            // Disable or enable buttons depending on if sites are selected or not
                            _handleButtons(sites);
                        });
                    }

                    /**
                     *
                     * @param {*} sites
                     */
                    function _handleButtons(sites) {
                        var selected = sites.filter(function(site) {
                            return site.selected;
                        });
                        $scope.disableBtn = selected.length ? false : true;
                    }

                    /**
                     * dispatches a ITEM_CLICKED_EVENT event
                     *
                     * @method itemClicked
                     *
                     * @param  {String} type type of action taken.
                     * @param  {String} site the site on which the action occurred.
                     * @return {Boolean} returns the result of the $scope.$emit function
                     *
                     */
                     $scope.itemClicked = function itemClicked(type, selectedSites) {
                        $scope.disableBtn = true;
                        $scope.$emit(ITEM_LISTER_CONSTANTS.ITEM_CLICKED_EVENT, { actionType: type, items: selectedSites });
                    };

                    /**
                     * Get the header items
                     *
                     * @method getHeaders
                     *
                     * @return {Array} returns array of objects containing labels
                     *
                     */
                    this.getHeaders = function getHeaders() {
                        return $scope.headers;
                    };

                    /**
                     * Register a callback to call on the update of the lister
                     *
                     * @method registerViewCallback
                     * @param  {Function} callback function to callback to
                     *
                     */

                    this.registerViewCallback = function registerViewCallback(callback) {
                        $scope.viewCallbacks.push(callback);
                        callback($scope.filteredItems);
                    };


                    /**
                     * Deregister a callback (useful for view changes)
                     *
                     * @method deregisterViewCallback
                     * @param  {Function} callback callback to deregister
                     *
                     */
                    this.deregisterViewCallback = function deregisterViewCallback(callback) {
                        for (var i = $scope.viewCallbacks.length - 1; i >= 0; i--) {
                            if ($scope.viewCallbacks[i] === callback) {
                                $scope.viewCallbacks.splice(i, 1);
                            }
                        }
                    };

                    /**
                     * Rebuild the view after various user interactions.
                     *
                     * @method updateView
                     */
                    $scope.updateView = function updateView() {

                        var filteredItems = [];

                        filteredItems = _filter($scope.items);

                        // update the total items after search
                        $scope.totalItems = filteredItems.length;

                        filteredItems = _sort(filteredItems);
                        filteredItems = _paginate(filteredItems);

                        $scope.filteredItems = filteredItems;

                        _updatedListerState();

                        angular.forEach($scope.viewCallbacks, function updateCallback(viewCallback) {
                            viewCallback($scope.filteredItems);
                        });

                        $scope.$emit(ITEM_LISTER_CONSTANTS.ITEM_LISTER_UPDATED_EVENT, {
                            meta: {
                                filterValue: $scope.filterValue
                            }
                        });
                    };

                    /**
                     * Return the focus of the page to the search at the top and scroll to it
                     *
                     * @method focusSearch
                     */
                    $scope.focusSearch = function focusSearch() {
                        angular.element(document).find("#" + $scope.parentID + "_search_input").focus();
                        $window.scrollTop = 0;
                    };

                    /**
                     * Click handler for the configuration button.
                     *
                     * @param {Event} event
                     */
                    $scope.configurationClicked = function(event) {
                        $scope.$emit(event);
                    };

                    /**
                     * Click handler for the monitor button.
                     *
                     * @param {Event} event
                     */
                    $scope.monitorClicked = function(event) {
                        $scope.$emit(event);
                    };

                    /**
                     * Toggles the selection state of all sites in the form.
                     *
                     * @method toggleSelect
                     */
                     $scope.toggleSelect = function() {
                        sitesService.get().then(function(sites) {
                            for (var i = 0; i < sites.length; i++) {
                                var site = sites[i];
                                site.selected = $scope.selectAll;
                            }

                            $scope.disableBtn = $scope.selectAll ? false : true;
                        });
                    };

                    /**
                     * Checks to see if any sites are currently selected
                     *
                     * @method  hasSelected
                     * @returns {boolean} TRUE if one or more options are selected
                     *
                     */
                    $scope.hasSelected = function() {
                        sitesService.get().then(function(sites) {
                            for (var i = 0; i < sites.length; i++) {
                                if (sites[i].selected) {
                                    return true;
                                }
                            }
                            return false;
                        });
                    };

                    /**
                     * Checks to see if any sites are currently selected and returns them
                     *
                     * @method  getSelected
                     * @returns {Array} Array of all sites selected
                     *
                     */
                     $scope.getSelected = function() {
                        var sites = $scope.items;

                        var selected = sites.filter(function(site) {
                            return site.selected;
                        });

                        return selected;
                    }

                    $scope.$on(ITEM_LISTER_CONSTANTS.ITEM_CLICKED_EVENT, _itemInteracted);
                    $scope.$on(ITEM_LISTER_CONSTANTS.ITEM_SELECT_EVENT, _itemSelected);
                    $scope.$on(ITEM_LISTER_CONSTANTS.MONITOR_CHANGE_EVENT, function (event, sites) {
                        $scope.disableBtn = false;
                        _itemSelected();
                        $scope.focusSearch();
                    });

                    angular.extend($scope, {
                        maxPages: 5,
                        totalItems: $scope.items.length,
                        filteredItems: [],
                        currentPage: 1,
                        pageSize: 20,
                        pageSizes: [20, 50, 100, 500],

                        start: 0,
                        limit: 20,

                        filterValue: "",
                        sort: {
                            sortDirection: "asc",
                            sortBy: $scope.headers && $scope.headers.length ? $scope.headers[0].field : ""
                        }
                    }, {
                        filterValue: $routeParams["q"]
                    });

                    /**
                     * Add any filters to the view state.
                     *
                     * @param {*} initialState
                     */
                    function _savedStateLoaded(initialState) {
                        angular.extend($scope, initialState, {
                            filterValue: $routeParams["q"]
                        });
                    }

                    $scope.loadingInitialState = true;
                    $CSSS.register(COMPONENT_NAME)
                        .then(_savedStateLoaded, $log.error)
                        .finally(function() {
                            $scope.loadingInitialState = false;
                            $scope.updateView();
                        });

                    this.showConfigColumn = $scope.showConfigColumn = $scope.configuration && $scope.configuration.length;

                    $scope.$on("$destroy", function() {
                        $CSSS.unregister(COMPONENT_NAME);
                    });

                    $scope.updateView();
                    $scope.$watch("items", $scope.updateView);
                }]
            };

        }]);
    }
);

/*
# site-monitor/directives/docrootDirective.js                    Copyright(c) 2021 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 */

/** @namespace cpanel.siteMonitor.directive.docroot */

define(
    'app/directives/docrootDirective',[
        "angular",
        "lodash",
        "cjt/core",
    ],
    function(angular, _, CJT) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.docrootDirective", []);
        module.value("PAGE", PAGE);


        module.directive("docroot", function factory() {

            /**
             * Generates a docroot link automatically shortening the home dir to
             * to an icon and additional title text
             *
             * @module docroot
             * @restrict E
             *
             * @param  {String} docroot full path of the docroot
             * @param  {String} homedir path of the homedir (will be first part of docroot)
             *
             * @example
             * <docroot homedir="/home/baldr" docroot="/home/baldr/a/docroot" />
             *
             */

            var TEMPLATE_PATH = "directives/docrootDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,

                restrict: "E",
                scope: {
                    parentID: "@id",
                    rawDocroot: "@docroot",
                    homedir: "@"
                },
                controller: ["$scope", "PAGE", function($scope, PAGE) {

                    /**
                     * Converts a full document root into a shortened one and updates $scope.docroot
                     *
                     * @private
                     * @method updateDocroot
                     * @param  {String} newFullDocroot full document root, including the homedir to parse
                     * @return {String} returns the parsed document root
                     *
                     */
                    function updateDocroot(newFullDocroot) {
                        $scope.fullDocroot = encodeURIComponent(newFullDocroot);
                        var regexp = new RegExp("^" + _.escapeRegExp($scope.homedir) + "/?");
                        $scope.docroot = newFullDocroot.replace(regexp, "");
                        $scope.docroot = $scope.docroot === "/" ? "" : $scope.docroot;
                        return $scope.docroot;
                    }

                    $scope.fileManager = PAGE.fileManager;

                    $scope.$watch("rawDocroot", updateDocroot);
                    updateDocroot($scope.rawDocroot);
                }]

            };

        });
    }
);

/*
# site-monitor/directives/monitorDirective.js                    Copyright(c) 2021 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 */

/** @namespace cpanel.siteMonitor.directive.monitor */

define(
    'app/directives/monitorDirective',[
        "angular",
        "lodash",
        "cjt/core",
        "cjt/util/locale",
        "app/models/monitor-state.enum",
        "cjt/directives/spinnerDirective",
    ],
    function(angular, _, CJT, LOCALE, MonitorState) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.monitorDirective", [
            "cjt2.directives.spinner",
        ]);

        module.directive("monitor", ["spinnerAPI", function factory(spinnerAPI) {

            /**
             * Generates a monitor line in the table
             *
             * @module docroot
             * @restrict E
             *
             * @param  {string} id an id attribute for the element.
             * @param  {Monitor} data reference to the monitor to render.
             *
             * @example
             * <li ng-repeat="monitor in ::site.monitors trackby monitor.url()"
             *    id="{{ ::monitor.name}}" >
             *    <monitor data="monitor" />
             * </li>
             *
             */

            var TEMPLATE_PATH = "directives/monitorDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "E",
                require: "^monitors",
                scope: {
                    monitor: "=data",
                    parentID: "@parentID",
                },
                link: function(scope, element, attrs) {
                    // Watch the state property to update the spinners
                    var unwatch = scope.$watch("monitor.state", function(newState, oldState, scope) {
                        var spinnerId = scope.makeSpinnerId(scope.monitor);
                        switch(newState) {
                            case MonitorState.Creating:
                            case MonitorState.Removing:
                                spinnerAPI.start(spinnerId);
                                break;
                            default:
                                spinnerAPI.stop(spinnerId);
                                break;
                        }
                    }, true);
                },
                controller: ["$scope", "spinnerAPI", function($scope, spinnerAPI) {

                    $scope.LOCALE = LOCALE;
                    $scope.MonitorState = MonitorState;

                    $scope.standardPorts = {
                        http: 80,
                        https: 443,
                    };

                    /**
                     * Check if the port is one of the standard ones.
                     *
                     * @param {string} protocol - the protocol
                     * @param {number} port - the port number on the monitor
                     * @returns
                     */
                    $scope.hasStandardPort = function(protocol, port) {
                        return $scope.standardPorts[protocol] === port;
                    };

                    /**
                     * Generate the monitors spinner id
                     *
                     * @param {Monitor} monitor
                     */
                    $scope.makeSpinnerId = function(monitor) {
                        return $scope.parentID + "_spinner_" + monitor.url();
                    };

                    /**
                     * Build the correct text string for the monitor state.
                     *
                     * @param {MonitorState} state - the state of the current monitor.
                     * @returns {string} Message to print
                     */
                    $scope.monitorStateMessage = function monitorStateMessage(state) {
                        switch(state) {
                            case MonitorState.Creating:
                                return LOCALE.maketext("Configuring…")
                            case MonitorState.Removing:
                                return LOCALE.maketext("Removing…")
                            case MonitorState.Ready:
                                return LOCALE.maketext("Active");
                            case MonitorState.Error:
                                return LOCALE.maketext("Failed");
                            case MonitorState.None:
                            default:
                                return '';
                        }
                    }
                }]
            };

        }]);
    }
);

/*
# site-monitor/directives/monitorsDirective.js                    Copyright(c) 2021 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 */

/** @namespace cpanel.siteMonitor.directive.monitors */

define(
    'app/directives/monitorsDirective',[
        "angular",
        "lodash",
        "cjt/core",
        "cjt/util/locale",
        "app/directives/monitorDirective",
    ],
    function(angular, _, CJT, LOCALE) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.monitorsDirective", [
            "cpanel.siteMonitor.monitorDirective",
        ]);


        module.directive("monitors", function factory() {

            /**
             * Generates a list of monitors from a site.monitors array.
             *
             * @module monitors
             * @restrict E
             *
             * @param  {string} id an id attribute for the element.
             * @param  {Site} data reference to the site to render. Each site should have a monitors collection that
             * indicates information about the monitors for this site.
             *
             * @example
             * <monitors id="monitors" data="site" />
             *
             */

            var TEMPLATE_PATH = "directives/monitorsDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "E",
                scope: {
                    parentID: "@id",
                    site: "=data"
                },
                controller: ["$scope", function($scope) {
                    $scope.LOCALE = LOCALE;
                }]
            };
        });
    }
);

/*
# site-monitor/directives/siteItemListDirective.js
#                                                  Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.directives.siteItemList */

define(
    'app/directives/siteItemListDirective',[
        "lodash",
        "angular",
        "cjt/core",
        "cjt/util/locale",
        "app/models/domain-type.enum",
        "app/directives/docrootDirective",
        "app/directives/monitorsDirective"
    ],
    function(_, angular, CJT, LOCALE, DomainType) {

        "use strict";

        var module = angular.module("cpanel.siteMonitor.siteItemListDirective", [
            "cpanel.siteMonitor.docrootDirective",
            "cpanel.siteMonitor.monitorsDirective",
        ]);

        module.directive("siteItemList", [ function siteItemList() {

            /**
             * Site Item is a directive that pairs with the item lister to display domains,
             * docroots, monitors and the actions you can take with them. It must be nested
             * within an item lister.
             *
             * @module site-item
             * @restrict EA
             *
             * @example
             * <item-lister>
             *     <site-item-list></site-item-list>
             * </item-lister>
             *
             */

            var TEMPLATE_PATH = "directives/siteItemListDirective.phtml";
            var RELATIVE_PATH = "site-monitor/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "EA",
                replace: true,
                require: "^itemLister",
                scope: true,
                link: function($scope, $element, $attrs, $parentCtrl) {
                    $scope.LOCALE = LOCALE;
                    $scope.showConfigColumn = $parentCtrl.showConfigColumn;
                    $scope.headers = $parentCtrl.getHeaders();

                    /**
                     * Update the view with sites.
                     *
                     * @param {Site[]} sites
                     */
                    $scope.updateView = function updateView(sites) {
                        $scope.sites = sites;
                    };

                    $parentCtrl.registerViewCallback($scope.updateView.bind($scope));

                    $scope.$on("$destroy", function() {
                        $parentCtrl.deregisterViewCallback($scope.updateView);
                    });
                },
                controller: [
                    "$scope", "ITEM_LISTER_CONSTANTS", "PAGE", "alertService", "$timeout",
                    function($scope, ITEM_LISTER_CONSTANTS, PAGE, alertService, $timeout) {

                    $scope.isRTL = PAGE.isRTL;
                    $scope.DomainType = DomainType;
                    $scope.hasWebServerRole = PAGE.hasWebServerRole;

                    /**
                     * Get the sites for the view.
                     *
                     * @method getSites
                     * @returns {Site[]} list of sites to render.
                     */
                    $scope.getSites = function getSites() {
                        return $scope.sites;
                    };

                    /**
                     * dispatches a ITEM_CLICKED_EVENT event
                     *
                     * @method itemClicked
                     *
                     * @param  {String} type type of action taken.
                     * @param  {String} site the site on which the action occurred.
                     * @return {Boolean} returns the result of the $scope.$emit function
                     *
                     */
                    $scope.itemClicked = function itemClicked(type, site) {
                        $scope.$emit(ITEM_LISTER_CONSTANTS.ITEM_CLICKED_EVENT, { actionType: type, item: site, interactionID: site.domain });
                    };

                    /**
                     * dispatches a ITEM_SELECT_EVENT for the site.
                     *
                     * @param {String} site
                     */
                    $scope.itemSelected = function itemSelected(site) {
                        $scope.$emit(ITEM_LISTER_CONSTANTS.ITEM_SELECT_EVENT, site);
                    };

                    // render again when a monitor change is detected
                    $scope.$on(ITEM_LISTER_CONSTANTS.MONITOR_CHANGE_EVENT, function (event, sites) {
                        $scope.updateView(sites);
                    });
                }]
            };
        } ]);
    }
);

/*
 * site-monitor/services/dataStore.js                   Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.services.dataStore */

define(
    'app/services/dataStore',[
        "angular"
    ],
    function(angular) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.dataStoreService", []);

        app.factory("dataStore", [
            "$window",
            function($window) {

                return {
                    save: function(action, data) {
                        $window.localStorage.setItem(action, angular.toJson(data));
                    },
                    get: function(action) {
                        var data = $window.localStorage.getItem(action);
                        return angular.fromJson(data);
                    },
                    remove: function(action) {
                        $window.localStorage.removeItem(action);
                    },
                    clear: function() {
                        $window.localStorage.clear();
                    }
                };
            }
        ]);
    }
);

/*
# site-monitor/views/listSiteMonitors.js           Copyright 2021 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 */

/** @namespace cpanel.siteMonitor.views.listSiteMonitors */

define(
    'app/views/listSiteMonitors',[
        "angular",
        "cjt/util/locale",
        "app/models/monitor-state.enum",
        "app/models/monitor-type.enum",
        "app/models/monitor",
        "app/services/sites",
        "app/services/monitoring",
        "app/services/contacts",
        "app/services/sitesCache",
        "app/services/batch",
        "cjt/services/alertService",
        "app/directives/itemListerDirective",
        "app/directives/siteItemListDirective",
        "cjt/services/cpanel/componentSettingSaverService",
        "cjt/services/viewNavigationApi",
        "app/services/dataStore",
    ],
    function(angular, LOCALE, MonitorState, MonitorType, Monitor) {

        "use strict";

        var app = angular.module("cpanel.siteMonitor.listSiteMonitors", [
            "cpanel.siteMonitor.siteItemListDirective",
            "cpanel.siteMonitor.itemListerDirective",
            "cpanel.siteMonitor.monitoringService",
            "cpanel.siteMonitor.batchService",
            "cpanel.siteMonitor.contactsService",
            "cjt2.services.viewNavigationApi",
            "cjt2.services.alert",
            "cpanel.siteMonitor.dataStoreService",
            "cpanel.siteMonitor.sitesCacheService",
        ]);

        /**
         * View Controller for Domain Listing
         *
         * @module listSites
         *
         * @param  {Object} $scope angular scope
         * @param  {Object} $locale angular location Object
         * @param  {Array}  sitesAPI api to fetch sites.
         * @param  {Object} ITEM_LISTER_CONSTANTS event constants for actions
         *
         */

        var COMPONENT_NAME = "siteMonitor.listView";

        var controller = app.controller(
            "listSiteMonitors",
            [
                "$scope",
                "$location",
                "$filter",
                "$timeout",
                "componentSettingSaverService",
                "initialSites",
                "viewNavigationApi",
                "dataStore",
                "sitesCache",
                "sites",
                "batch",
                "monitoring",
                "alertService",
                "contacts",
                "ITEM_LISTER_CONSTANTS",
                "PAGE",
                function(
                    $scope,
                    $location,
                    $filter,
                    $timeout,
                    $CSSS,
                    initialSites,
                    viewNavigationApi,
                    dataStore,
                    sitesCache,
                    sitesService,
                    batch,
                    monitoringAPI,
                    alertService,
                    contacts,
                    ITEM_LISTER_CONSTANTS,
                    PAGE) {

                    var LIST_DOMAIN_EVENTS = {
                        HIDE_ASSOCIATED: "hideAssociatedSubdomains",
                        SHOW_ASSOCIATED: "showAssociatedSubdomains"
                    };

                    var associatedDomainsExist;

                    $scope.LOCALE = LOCALE;
                    $scope.disableBtn = false;

                    /**
                     * Handles item buttons clicks.
                     *
                     * @method _itemChangeRequested
                     * @param {Event} event
                     * @param {*} parameters
                     */
                    function _itemChangeRequested(event, parameters) {
                        switch(parameters.actionType) {
                            case 'enable_multiple':
                                $scope.disableBtn = true;

                                var sites = parameters.items;

                                var queue = sites.map(function(site) {
                                    return { url: site.domain, fullUrl: "https://" + site.domain + "/", domain: site.domain };
                                });

                                dataStore.save("post-subscribe", queue);

                                // TODO: Move this to the store callback this once the subscribe flow is in place
                                _batchEnable(queue)
                                    .then(function(results) {
                                        _fetchAndRefresh()
                                    });

                                break;

                            case 'disable_multiple':
                                $scope.disableBtn = true;

                                var sites = parameters.items;

                                // filter out calls to remove monitors that don't exist
                                var queue = sites.filter(function(site) {
                                    if (site.monitors.length) { return true }
                                }).map(function(site) {
                                    var monitor = site.monitors[0];
                                    return { url: monitor.url(), id: monitor.id, domain: site.domain };
                                });

                                dataStore.save("post-unsubscribe", queue);

                                // TODO: Move this to the store callback this once the unsubscribe flow is in place
                                _batchDisable(queue)
                                    .then(function() {
                                        _fetchAndRefresh()
                                    });

                                break;

                            case 'enable':
                                $scope.disableBtn = true;

                                var site = parameters.item;

                                var queue = [ site ].map(function(site) {
                                    return { url : site.domain, fullUrl: "https://" + site.domain + "/" , domain: site.domain};
                                });

                                dataStore.save("post-subscribe", queue);

                                // TODO: Move this to the store callback this once the subscribe flow is in place
                                _batchEnable(queue)
                                    .then(function() {
                                        _fetchAndRefresh()
                                    });

                                break;

                            case 'disable':
                                $scope.disableBtn = true;
                                var site = parameters.item;
                                var monitor = site.monitors[0];

                                var queue = [
                                    { url: monitor.url(), id: monitor.id, domain: site.domain },
                                ];

                                dataStore.save("post-unsubscribe", queue);

                                // TODO: Move this to the store callback this once the unsubscribe flow is in place
                                _batchDisable(queue)
                                    .then(function() {
                                        _fetchAndRefresh();
                                    });

                                break;
                            case 'manage':
                                // TODO
                                break;
                        }
                    }

                    /**
                     * Fetches updated sites and broadcasts event to refresh UI
                     *
                     * @method _fetchAndRefresh
                     */
                    function _fetchAndRefresh() {
                        sitesService.get({refresh: true}).then(function(sites) {
                            $scope.disableBtn = false;
                            _updateFiltered();
                            //_updateAssociated(sites);
                            $scope.$broadcast(ITEM_LISTER_CONSTANTS.MONITOR_CHANGE_EVENT, sites );
                        });
                    }

                    /**
                     * Disable the monitors a few at a time so we don't flood the server or
                     * consume too much browser memory.
                     * @param { { url: string, id: string }[] } orders
                     * @returns
                     */
                    function _batchDisable(orders) {
                        var status = {
                            error: [],
                            success: [],
                        };

                        return batch
                            .uniform(
                                orders, 2,
                                function removeMonitor(order) {
                                    var monitor = sitesCache.findMonitor(order.domain, order.url);
                                    if (monitor) {
                                        monitor.state = MonitorState.Removing;
                                        $scope.$applyAsync();
                                    }
                                    return monitoringAPI.disable(order.id, order.url);
                                },
                                function identity(order) {
                                    return order.url;
                                },
                                function removeMonitorDone(order, error) {
                                    var monitor = sitesCache.findMonitor(order.domain, order.url);
                                    if (error)  {
                                        if (monitor) {
                                            monitor.state = MonitorState.Error;
                                        }
                                        alertService.add({
                                            type: "danger",
                                            message: LOCALE.maketext("Failed to remove the monitor [_1] with the error: [_2]", monitor.url(), error),
                                            closeable: true,
                                            replace: false,
                                        });
                                        status.error.push(monitor);
                                    } else {
                                        if (monitor) {
                                            monitor.state = MonitorState.Ready;
                                            $scope.$applyAsync();
                                        }
                                        status.success.push(monitor);
                                    }
                                }
                            )
                            .then(function(responses) {
                                if (status.success.length > 0 && status.error.length > 0) {
                                    alertService.success({
                                        message: LOCALE.maketext("The system successfully removed some of the requested monitors."),
                                        replace: false
                                    });
                                } else if (status.success.length > 0 && status.error.length === 0) {
                                    alertService.success({
                                        message: LOCALE.maketext("The system successfully removed the requested monitors."),
                                        replace: false
                                    });
                                }
                            });
                    }

                    /**
                     * Enable the monitors a few at a time so we don't flood the server or
                     * consume too much browser memory.
                     * @param { { url: string}[] } orders
                     * @returns
                     */
                    function _batchEnable(orders) {

                        // Create placholder monitor rows
                        orders.forEach(function(order) {
                            var site = sitesCache.findByDomainName(order.domain);
                            if (site) {
                                var newMonitor = Monitor.parseItem({
                                    url: order.fullUrl,
                                    name: order.url,
                                    type: MonitorType.https,
                                    state: MonitorState.None,
                                });
                                site.addMonitor(newMonitor);
                            }
                        });

                        return contacts
                            .getStoreEmailAddress()
                            .then(
                                function(storeEmail) {

                                    // CONSIDER: We should consider using the contacts email if the store one is not available.

                                    if (!storeEmail) {
                                        var message = LOCALE.maketext("The system could not find the an email address to use when setting up your monitors.");
                                        alertService.add({
                                            type: "danger",
                                            message: message,
                                            closeable: true,
                                            replace: false,
                                        });
                                        throw new Error(message);
                                    }

                                    var status = {
                                        error: [],
                                        success: [],
                                    };

                                    return batch
                                        .uniform(
                                            orders, 2,
                                            function createMonitor(order) {
                                                var monitor = sitesCache.findMonitor(order.domain, order.fullUrl);
                                                monitor.state = MonitorState.Creating;
                                                $scope.$applyAsync();
                                                return monitoringAPI.enable(order.url, null, [ storeEmail ]);
                                            },
                                            function identity(order) {
                                                return order.url;
                                            },
                                            function createMonitorDone(order, error) {
                                                var monitor = sitesCache.findMonitor(order.domain, order.fullUrl);
                                                if (error)  {
                                                    monitor.state = MonitorState.Error;
                                                    alertService.add({
                                                        type: "danger",
                                                        message: LOCALE.maketext("Failed to create the monitor [_1] with the error: [_2]", monitor.url, error),
                                                        closeable: true,
                                                        replace: false,
                                                    });
                                                    status.error.push(monitor);
                                                } else {
                                                    monitor.state = MonitorState.Ready;
                                                    status.success.push(monitor);
                                                }
                                                $scope.$applyAsync();
                                            }
                                        )
                                        .then(function(responses) {
                                            if (status.success.length > 0 && status.error.length > 0) {
                                                alertService.success({
                                                    message: LOCALE.maketext("The system successfully created some of the requested monitors."),
                                                    replace: false
                                                });
                                            } else if (status.success.length > 0 && status.error.length === 0) {
                                                alertService.success({
                                                    message: LOCALE.maketext("The system successfully created the requested monitors."),
                                                    replace: false
                                                });
                                            }
                                        });
                                }
                            );
                    }

                    /**
                     * Update the flag use to check if there are associated domains.
                     *
                     * QUESTION: Do we need this???
                     *
                     * @method _updateAssociated
                     * @param {Sites} sites
                     */
                    function _updateAssociated(sites) {
                        associatedDomainsExist = sites.some(function(site) {
                            if (site.associatedAddonDomain) {
                                return true;
                            }
                            return false;
                        });
                    }

                    /**
                     * Handles other item events like changing selection.
                     *
                     * @method _itemListerUpdated
                     * @param {Event} event
                     * @param {*} parameters
                     */
                    function _itemListerUpdated(event, parameters) {
                        // DOESN'T DO ANYTHING BESIDES CAUSE A CONSOLE ERROR ATM, UNCOMMENTED LINE THAT CALLS THIS
                        $scope.currentSearchFilterValue = parameters.meta.filterValue;
                    }

                    /**
                     * On updating of the show associated addon domains checkbox, refilter sites
                     *
                     * @private
                     * @method _filterAssociatedDomains
                     * @param {*} event
                     */
                    function _filterAssociatedDomains(domain) {
                        if ($scope.showAssociatedSubdomains) {
                            return true;
                        }
                        if (!domain.associatedAddonDomain) {
                            return true;
                        }

                        return false;
                    }

                    var lastFiltered = true;

                    /**
                     * Reapply the filters based on new input from the user.
                     *
                     * @private
                     * @method _updateFiltered
                     */
                    function _updateFiltered() {
                        var filteredSites = $filter("filter")(sitesCache.get(), _filterAssociatedDomains);
                        lastFiltered = $scope.showAssociatedSubdomains;
                        $scope.filteredSites = filteredSites;
                    }

                    /**
                     * Show and hide the associated subdomains for a domain.
                     *
                     * @scope
                     * @method toggleShowAssociatedSubdomains
                     */
                    $scope.toggleShowAssociatedSubdomains = function toggleShowAssociatedSubdomains() {
                        $scope.showAssociatedSubdomains = !$scope.showAssociatedSubdomains;
                        $CSSS.set(COMPONENT_NAME, { showAssociatedSubdomains: $scope.showAssociatedSubdomains });
                        _updateFiltered();
                        _updateConfiguration();
                    }

                    /**
                     * Gets the list of sites to show in the view.
                     *
                     * @scope
                     * @method getSites
                     * @returns
                     */
                    $scope.getSites = function getSites() {
                        if (lastFiltered !== $scope.showAssociatedSubdomains) {
                            _updateFiltered();
                        }
                        return $scope.filteredSites;
                    }

                    /**
                     * Updates the configuration array passed to the child component
                     *
                     * @method _updateConfiguration
                     * @private
                     * @returns
                     */
                    function _updateConfiguration() {

                        // remove all items from config
                        $scope.configuration.splice(0);

                        if (!associatedDomainsExist) {
                            return;
                        }

                        if ($scope.showAssociatedSubdomains) {
                            $scope.configuration.push({
                                label: LOCALE.maketext("Hide Associated Subdomains"),
                                event: LIST_DOMAIN_EVENTS.HIDE_ASSOCIATED
                            });
                        } else {
                            $scope.configuration.push({
                                label: LOCALE.maketext("Show Associated Subdomains"),
                                event: LIST_DOMAIN_EVENTS.SHOW_ASSOCIATED
                            });
                        }
                    }

                    $scope.configuration = [];
                    _updateConfiguration();

                    $scope.$on(ITEM_LISTER_CONSTANTS.ITEM_CLICKED_EVENT, _itemChangeRequested);
                    //$scope.$on(ITEM_LISTER_CONSTANTS.ITEM_SELECT_EVENT, _itemListerUpdated);
                    $scope.$on(LIST_DOMAIN_EVENTS.SHOW_ASSOCIATED, $scope.toggleShowAssociatedSubdomains);
                    $scope.$on(LIST_DOMAIN_EVENTS.HIDE_ASSOCIATED, $scope.toggleShowAssociatedSubdomains);

                    $scope.showAssociatedSubdomains = false;
                    $scope.headers = [
                        {
                            field: "batchSelect",
                            class: "batch-select-column",
                            sortable: false,
                            label: null
                        },
                        {
                            field: "domain",
                            sortable: true,
                            class: "domain-column",
                            label: LOCALE.maketext("Site")
                        },
                        {
                            field: "documentRoot",
                            class: "docroot-column",
                            sortable: true,
                            label: LOCALE.maketext("Document Root"),
                            hiddenInSmall: true
                        },
                        {
                            field: "monitors",
                            class: "monitors-column",
                            sortable: true,
                            label: LOCALE.maketext("Monitors"),
                            hiddenInSmall: true
                        },
                        {
                            field: "actions",
                            class: "actions-column",
                            label: LOCALE.maketext("Actions"),
                            hiddenInSmall: true
                        }
                    ];

                    $CSSS.register(COMPONENT_NAME)
                        .then(function _savedStateLoaded(config) {
                            if (config && $scope.showAssociatedSubdomains !== config.showAssociatedSubdomains) {
                                $scope.showAssociatedSubdomains = config.showAssociatedSubdomains;
                                _updateFiltered();
                                _updateConfiguration();
                            }
                        });

                    $scope.$on("$destroy", function() {
                        $CSSS.unregister(COMPONENT_NAME);
                    });

                    if (initialSites instanceof Error) {
                        alertService.add({
                            type: "danger",
                            message: initialSites.message,
                            closeable: true,
                            replace: true,
                        });
                        $scope.filteredSites = [];
                    } else {
                        $scope.filteredSites = initialSites;
                    }
                }
            ]
        );

        return controller;
    }
);


/*
# site-monitor/index.js                            Copyright 2021 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, define, PAGE */

/** @namespace cpanel.siteMonitor */

define(
    'app/index',[
        "angular",
        "cjt/core",
        "app/views/ROUTES",
        "cjt/modules",
        "ngRoute",
        "ngAnimate",
        "cjt/services/alertService",
        "app/services/sites",
        "app/services/sitesCache",
        "cjt/directives/loadingPanel",
        "cjt/decorators/alertAPIReporter",
        "cjt/directives/alertList",
        "angular-chosen",

        // Bundle in the views for now
        "app/views/listSiteMonitors",
    ],
    function(angular, CJT, ROUTES) {

        "use strict";

        return function() {

            // First create the application
            angular.module("cpanel.siteMonitor", [
                "ngRoute",
                "ngAnimate",
                "cjt2.cpanel",
                "cpanel.siteMonitor.sitesService",
                "cpanel.siteMonitor.sitesCacheService",
                "cjt2.directives.loadingPanel",
                "cjt2.services.alert",
                "localytics.directives",

                // View Modules
                "cpanel.siteMonitor.listSiteMonitors",
            ]);

            var requires = [
                "cjt/bootstrap",
                "cjt/directives/breadcrumbs",
                "cjt/views/applicationController",
            ];

            ROUTES.forEach(function(route) {
                requires.push("app/views/" + route.controller);
            });

            // Then load the application dependencies
            var app = require(requires, function(BOOTSTRAP) {

                var app = angular.module("cpanel.siteMonitor");

                app.value("PAGE", PAGE);

                app.value("ITEM_LISTER_CONSTANTS", {
                    ITEM_CLICKED_EVENT: "ClickEmitted",
                    ITEM_SELECT_EVENT: "SelectEmitted",
                    MONITOR_CHANGE_EVENT: "MonitorChangeEmitted"
                });

                app.config([
                    "$routeProvider",
                    "$animateProvider",
                    function($routeProvider, $animateProvider) {

                        $animateProvider.classNameFilter(/^((?!no-animate).)*$/);

                        ROUTES.forEach(function(ROUTE) {
                            var route = {
                                controller: ROUTE.controller,
                                templateUrl: ROUTE.templateUrl,
                                breadcrumb: ROUTE.breadcrumb,
                                resolve: ROUTE.resolve,
                            };
                            $routeProvider.when(ROUTE.path, route);
                        });

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

                    }
                ]);

                app.controller(
                    "main",
                    [
                        "$scope", "$rootScope", "$location", "alertService",
                        function($scope, $rootScope, $location, $alertService) {

                            $rootScope.$on("$routeChangeStart", function() {
                                $scope.loading = true;
                                $alertService.clear("danger");
                            });

                            $rootScope.$on("$routeChangeSuccess", function(event, current) {
                                $scope.loading = false;
                            });

                            $rootScope.$on("$routeChangeError", function() {
                                $scope.loading = false;
                            });
                        }
                    ]
                );

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

            });

            /*
            // The following lines are a workaround for CPANEL-3887. Having a dummy mt call
            // ensures that the minified version of this file is not deleted during a build.
            require(["cjt/util/locale"], function(LOCALE) {
                LOCALE.maketext("Enabled");
            });
            */

            return app;
        };
    }
);

Back to Directory File Manager