Viewing File: /usr/local/cpanel/base/frontend/jupiter/site-monitor/services/sites.js

/*
 * 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(
    [
        "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();
        }]);
    }
);
Back to Directory File Manager