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 = {
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`"
};
unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
return text.replace(unsafe_chars, function(chr) {
return map[chr] || "&";
});
};
return SelectParser;
})();
SelectParser.select_to_array = function(select) {
var child, parser, _i, _len, _ref;
parser = new SelectParser();
_ref = select.childNodes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
parser.add_node(child);
}
return parser.parsed;
};
AbstractChosen = (function() {
function AbstractChosen(form_field, options) {
this.form_field = form_field;
this.options = options != null ? options : {};
if (!AbstractChosen.browser_is_supported()) {
return;
}
this.is_multiple = this.form_field.multiple;
this.set_default_text();
this.set_default_values();
this.setup();
this.set_up_html();
this.register_observers();
this.on_ready();
}
AbstractChosen.prototype.set_default_values = function() {
var _this = this;
this.click_test_action = function(evt) {
return _this.test_active_click(evt);
};
this.activate_action = function(evt) {
return _this.activate_field(evt);
};
this.active_field = false;
this.mouse_on_container = false;
this.results_showing = false;
this.result_highlighted = null;
this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
this.disable_search_threshold = this.options.disable_search_threshold || 0;
this.disable_search = this.options.disable_search || false;
this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
this.group_search = this.options.group_search != null ? this.options.group_search : true;
this.search_contains = this.options.search_contains || false;
this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
this.max_selected_options = this.options.max_selected_options || Infinity;
this.inherit_select_classes = this.options.inherit_select_classes || false;
this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
this.include_group_label_in_selected = this.options.include_group_label_in_selected || false;
return this.max_shown_results = this.options.max_shown_results || Number.POSITIVE_INFINITY;
};
AbstractChosen.prototype.set_default_text = function() {
if (this.form_field.getAttribute("data-placeholder")) {
this.default_text = this.form_field.getAttribute("data-placeholder");
} else if (this.is_multiple) {
this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
} else {
this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
}
return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
};
AbstractChosen.prototype.choice_label = function(item) {
if (this.include_group_label_in_selected && (item.group_label != null)) {
return "<b class='group-name'>" + item.group_label + "</b>" + item.html;
} else {
return item.html;
}
};
AbstractChosen.prototype.mouse_enter = function() {
return this.mouse_on_container = true;
};
AbstractChosen.prototype.mouse_leave = function() {
return this.mouse_on_container = false;
};
AbstractChosen.prototype.input_focus = function(evt) {
var _this = this;
if (this.is_multiple) {
if (!this.active_field) {
return setTimeout((function() {
return _this.container_mousedown();
}), 50);
}
} else {
if (!this.active_field) {
return this.activate_field();
}
}
};
AbstractChosen.prototype.input_blur = function(evt) {
var _this = this;
if (!this.mouse_on_container) {
this.active_field = false;
return setTimeout((function() {
return _this.blur_test();
}), 100);
}
};
AbstractChosen.prototype.results_option_build = function(options) {
var content, data, data_content, shown_results, _i, _len, _ref;
content = '';
shown_results = 0;
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
data = _ref[_i];
data_content = '';
if (data.group) {
data_content = this.result_add_group(data);
} else {
data_content = this.result_add_option(data);
}
if (data_content !== '') {
shown_results++;
content += data_content;
}
if (options != null ? options.first : void 0) {
if (data.selected && this.is_multiple) {
this.choice_build(data);
} else if (data.selected && !this.is_multiple) {
this.single_set_selected_text(this.choice_label(data));
}
}
if (shown_results >= this.max_shown_results) {
break;
}
}
return content;
};
AbstractChosen.prototype.result_add_option = function(option) {
var classes, option_el;
if (!option.search_match) {
return '';
}
if (!this.include_option_in_results(option)) {
return '';
}
classes = [];
if (!option.disabled && !(option.selected && this.is_multiple)) {
classes.push("active-result");
}
if (option.disabled && !(option.selected && this.is_multiple)) {
classes.push("disabled-result");
}
if (option.selected) {
classes.push("result-selected");
}
if (option.group_array_index != null) {
classes.push("group-option");
}
if (option.classes !== "") {
classes.push(option.classes);
}
option_el = document.createElement("li");
option_el.className = classes.join(" ");
option_el.style.cssText = option.style;
option_el.setAttribute("data-option-array-index", option.array_index);
option_el.innerHTML = option.search_text;
if (option.title) {
option_el.title = option.title;
}
return this.outerHTML(option_el);
};
AbstractChosen.prototype.result_add_group = function(group) {
var classes, group_el;
if (!(group.search_match || group.group_match)) {
return '';
}
if (!(group.active_options > 0)) {
return '';
}
classes = [];
classes.push("group-result");
if (group.classes) {
classes.push(group.classes);
}
group_el = document.createElement("li");
group_el.className = classes.join(" ");
group_el.innerHTML = group.search_text;
if (group.title) {
group_el.title = group.title;
}
return this.outerHTML(group_el);
};
AbstractChosen.prototype.results_update_field = function() {
this.set_default_text();
if (!this.is_multiple) {
this.results_reset_cleanup();
}
this.result_clear_highlight();
this.results_build();
if (this.results_showing) {
return this.winnow_results();
}
};
AbstractChosen.prototype.reset_single_select_options = function() {
var result, _i, _len, _ref, _results;
_ref = this.results_data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
result = _ref[_i];
if (result.selected) {
_results.push(result.selected = false);
} else {
_results.push(void 0);
}
}
return _results;
};
AbstractChosen.prototype.results_toggle = function() {
if (this.results_showing) {
return this.results_hide();
} else {
return this.results_show();
}
};
AbstractChosen.prototype.results_search = function(evt) {
if (this.results_showing) {
return this.winnow_results();
} else {
return this.results_show();
}
};
AbstractChosen.prototype.winnow_results = function() {
var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
this.no_results_clear();
results = 0;
searchText = this.get_search_text();
escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
zregex = new RegExp(escapedSearchText, 'i');
regex = this.get_search_regex(escapedSearchText);
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
option.search_match = false;
results_group = null;
if (this.include_option_in_results(option)) {
if (option.group) {
option.group_match = false;
option.active_options = 0;
}
if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
results_group = this.results_data[option.group_array_index];
if (results_group.active_options === 0 && results_group.search_match) {
results += 1;
}
results_group.active_options += 1;
}
option.search_text = option.group ? option.label : option.html;
if (!(option.group && !this.group_search)) {
option.search_match = this.search_string_match(option.search_text, regex);
if (option.search_match && !option.group) {
results += 1;
}
if (option.search_match) {
if (searchText.length) {
startpos = option.search_text.search(zregex);
text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
}
if (results_group != null) {
results_group.group_match = true;
}
} else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
option.search_match = true;
}
}
}
}
this.result_clear_highlight();
if (results < 1 && searchText.length) {
this.update_results_content("");
return this.no_results(searchText);
} else {
this.update_results_content(this.results_option_build());
return this.winnow_results_set_highlight();
}
};
AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
var regex_anchor;
regex_anchor = this.search_contains ? "" : "^";
return new RegExp(regex_anchor + escaped_search_string, 'i');
};
AbstractChosen.prototype.search_string_match = function(search_string, regex) {
var part, parts, _i, _len;
if (regex.test(search_string)) {
return true;
} else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
parts = search_string.replace(/\[|\]/g, "").split(" ");
if (parts.length) {
for (_i = 0, _len = parts.length; _i < _len; _i++) {
part = parts[_i];
if (regex.test(part)) {
return true;
}
}
}
}
};
AbstractChosen.prototype.choices_count = function() {
var option, _i, _len, _ref;
if (this.selected_option_count != null) {
return this.selected_option_count;
}
this.selected_option_count = 0;
_ref = this.form_field.options;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
if (option.selected) {
this.selected_option_count += 1;
}
}
return this.selected_option_count;
};
AbstractChosen.prototype.choices_click = function(evt) {
evt.preventDefault();
if (!(this.results_showing || this.is_disabled)) {
return this.results_show();
}
};
AbstractChosen.prototype.keyup_checker = function(evt) {
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
this.search_field_scale();
switch (stroke) {
case 8:
if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
return this.keydown_backstroke();
} else if (!this.pending_backstroke) {
this.result_clear_highlight();
return this.results_search();
}
break;
case 13:
evt.preventDefault();
if (this.results_showing) {
return this.result_select(evt);
}
break;
case 27:
if (this.results_showing) {
this.results_hide();
}
return true;
case 9:
case 38:
case 40:
case 16:
case 91:
case 17:
case 18:
break;
default:
return this.results_search();
}
};
AbstractChosen.prototype.clipboard_event_checker = function(evt) {
var _this = this;
return setTimeout((function() {
return _this.results_search();
}), 50);
};
AbstractChosen.prototype.container_width = function() {
if (this.options.width != null) {
return this.options.width;
} else {
return "" + this.form_field.offsetWidth + "px";
}
};
AbstractChosen.prototype.include_option_in_results = function(option) {
if (this.is_multiple && (!this.display_selected_options && option.selected)) {
return false;
}
if (!this.display_disabled_options && option.disabled) {
return false;
}
if (option.empty) {
return false;
}
return true;
};
AbstractChosen.prototype.search_results_touchstart = function(evt) {
this.touch_started = true;
return this.search_results_mouseover(evt);
};
AbstractChosen.prototype.search_results_touchmove = function(evt) {
this.touch_started = false;
return this.search_results_mouseout(evt);
};
AbstractChosen.prototype.search_results_touchend = function(evt) {
if (this.touch_started) {
return this.search_results_mouseup(evt);
}
};
AbstractChosen.prototype.outerHTML = function(element) {
var tmp;
if (element.outerHTML) {
return element.outerHTML;
}
tmp = document.createElement("div");
tmp.appendChild(element);
return tmp.innerHTML;
};
AbstractChosen.browser_is_supported = function() {
if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
return false;
}
if (/Android/i.test(window.navigator.userAgent)) {
if (/Mobile/i.test(window.navigator.userAgent)) {
return false;
}
}
if (/IEMobile/i.test(window.navigator.userAgent)) {
return false;
}
if (/Windows Phone/i.test(window.navigator.userAgent)) {
return false;
}
if (/BlackBerry/i.test(window.navigator.userAgent)) {
return false;
}
if (/BB10/i.test(window.navigator.userAgent)) {
return false;
}
if (window.navigator.appName === "Microsoft Internet Explorer") {
return document.documentMode >= 8;
}
return true;
};
AbstractChosen.default_multiple_text = "Select Some Options";
AbstractChosen.default_single_text = "Select an Option";
AbstractChosen.default_no_result_text = "No results match";
return AbstractChosen;
})();
$ = jQuery;
$.fn.extend({
chosen: function(options) {
if (!AbstractChosen.browser_is_supported()) {
return this;
}
return this.each(function(input_field) {
var $this, chosen;
$this = $(this);
chosen = $this.data('chosen');
if (options === 'destroy') {
if (chosen instanceof Chosen) {
chosen.destroy();
}
return;
}
if (!(chosen instanceof Chosen)) {
$this.data('chosen', new Chosen(this, options));
}
});
}
});
Chosen = (function(_super) {
__extends(Chosen, _super);
function Chosen() {
_ref = Chosen.__super__.constructor.apply(this, arguments);
return _ref;
}
Chosen.prototype.setup = function() {
this.form_field_jq = $(this.form_field);
this.current_selectedIndex = this.form_field.selectedIndex;
return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
};
Chosen.prototype.set_up_html = function() {
var container_classes, container_props;
container_classes = ["chosen-container"];
container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
if (this.inherit_select_classes && this.form_field.className) {
container_classes.push(this.form_field.className);
}
if (this.is_rtl) {
container_classes.push("chosen-rtl");
}
container_props = {
'class': container_classes.join(' '),
'style': "width: " + (this.container_width()) + ";",
'title': this.form_field.title
};
if (this.form_field.id.length) {
container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
}
this.container = $("<div />", container_props);
if (this.is_multiple) {
this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
} else {
this.container.html('<a class="chosen-single chosen-default"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
}
this.form_field_jq.hide().after(this.container);
this.dropdown = this.container.find('div.chosen-drop').first();
this.search_field = this.container.find('input').first();
this.search_results = this.container.find('ul.chosen-results').first();
this.search_field_scale();
this.search_no_results = this.container.find('li.no-results').first();
if (this.is_multiple) {
this.search_choices = this.container.find('ul.chosen-choices').first();
this.search_container = this.container.find('li.search-field').first();
} else {
this.search_container = this.container.find('div.chosen-search').first();
this.selected_item = this.container.find('.chosen-single').first();
}
this.results_build();
this.set_tab_index();
return this.set_label_behavior();
};
Chosen.prototype.on_ready = function() {
return this.form_field_jq.trigger("chosen:ready", {
chosen: this
});
};
Chosen.prototype.register_observers = function() {
var _this = this;
this.container.bind('touchstart.chosen', function(evt) {
_this.container_mousedown(evt);
return evt.preventDefault();
});
this.container.bind('touchend.chosen', function(evt) {
_this.container_mouseup(evt);
return evt.preventDefault();
});
this.container.bind('mousedown.chosen', function(evt) {
_this.container_mousedown(evt);
});
this.container.bind('mouseup.chosen', function(evt) {
_this.container_mouseup(evt);
});
this.container.bind('mouseenter.chosen', function(evt) {
_this.mouse_enter(evt);
});
this.container.bind('mouseleave.chosen', function(evt) {
_this.mouse_leave(evt);
});
this.search_results.bind('mouseup.chosen', function(evt) {
_this.search_results_mouseup(evt);
});
this.search_results.bind('mouseover.chosen', function(evt) {
_this.search_results_mouseover(evt);
});
this.search_results.bind('mouseout.chosen', function(evt) {
_this.search_results_mouseout(evt);
});
this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
_this.search_results_mousewheel(evt);
});
this.search_results.bind('touchstart.chosen', function(evt) {
_this.search_results_touchstart(evt);
});
this.search_results.bind('touchmove.chosen', function(evt) {
_this.search_results_touchmove(evt);
});
this.search_results.bind('touchend.chosen', function(evt) {
_this.search_results_touchend(evt);
});
this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
_this.results_update_field(evt);
});
this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
_this.activate_field(evt);
});
this.form_field_jq.bind("chosen:open.chosen", function(evt) {
_this.container_mousedown(evt);
});
this.form_field_jq.bind("chosen:close.chosen", function(evt) {
_this.input_blur(evt);
});
this.search_field.bind('blur.chosen', function(evt) {
_this.input_blur(evt);
});
this.search_field.bind('keyup.chosen', function(evt) {
_this.keyup_checker(evt);
});
this.search_field.bind('keydown.chosen', function(evt) {
_this.keydown_checker(evt);
});
this.search_field.bind('focus.chosen', function(evt) {
_this.input_focus(evt);
});
this.search_field.bind('cut.chosen', function(evt) {
_this.clipboard_event_checker(evt);
});
this.search_field.bind('paste.chosen', function(evt) {
_this.clipboard_event_checker(evt);
});
if (this.is_multiple) {
return this.search_choices.bind('click.chosen', function(evt) {
_this.choices_click(evt);
});
} else {
return this.container.bind('click.chosen', function(evt) {
evt.preventDefault();
});
}
};
Chosen.prototype.destroy = function() {
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
if (this.search_field[0].tabIndex) {
this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
}
this.container.remove();
this.form_field_jq.removeData('chosen');
return this.form_field_jq.show();
};
Chosen.prototype.search_field_disabled = function() {
this.is_disabled = this.form_field_jq[0].disabled;
if (this.is_disabled) {
this.container.addClass('chosen-disabled');
this.search_field[0].disabled = true;
if (!this.is_multiple) {
this.selected_item.unbind("focus.chosen", this.activate_action);
}
return this.close_field();
} else {
this.container.removeClass('chosen-disabled');
this.search_field[0].disabled = false;
if (!this.is_multiple) {
return this.selected_item.bind("focus.chosen", this.activate_action);
}
}
};
Chosen.prototype.container_mousedown = function(evt) {
if (!this.is_disabled) {
if (evt && evt.type === "mousedown" && !this.results_showing) {
evt.preventDefault();
}
if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
if (!this.active_field) {
if (this.is_multiple) {
this.search_field.val("");
}
$(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
this.results_show();
} else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
evt.preventDefault();
this.results_toggle();
}
return this.activate_field();
}
}
};
Chosen.prototype.container_mouseup = function(evt) {
if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
return this.results_reset(evt);
}
};
Chosen.prototype.search_results_mousewheel = function(evt) {
var delta;
if (evt.originalEvent) {
delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
}
if (delta != null) {
evt.preventDefault();
if (evt.type === 'DOMMouseScroll') {
delta = delta * 40;
}
return this.search_results.scrollTop(delta + this.search_results.scrollTop());
}
};
Chosen.prototype.blur_test = function(evt) {
if (!this.active_field && this.container.hasClass("chosen-container-active")) {
return this.close_field();
}
};
Chosen.prototype.close_field = function() {
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
this.active_field = false;
this.results_hide();
this.container.removeClass("chosen-container-active");
this.clear_backstroke();
this.show_search_field_default();
return this.search_field_scale();
};
Chosen.prototype.activate_field = function() {
this.container.addClass("chosen-container-active");
this.active_field = true;
this.search_field.val(this.search_field.val());
return this.search_field.focus();
};
Chosen.prototype.test_active_click = function(evt) {
var active_container;
active_container = $(evt.target).closest('.chosen-container');
if (active_container.length && this.container[0] === active_container[0]) {
return this.active_field = true;
} else {
return this.close_field();
}
};
Chosen.prototype.results_build = function() {
this.parsing = true;
this.selected_option_count = null;
this.results_data = SelectParser.select_to_array(this.form_field);
if (this.is_multiple) {
this.search_choices.find("li.search-choice").remove();
} else if (!this.is_multiple) {
this.single_set_selected_text();
if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
this.search_field[0].readOnly = true;
this.container.addClass("chosen-container-single-nosearch");
} else {
this.search_field[0].readOnly = false;
this.container.removeClass("chosen-container-single-nosearch");
}
}
this.update_results_content(this.results_option_build({
first: true
}));
this.search_field_disabled();
this.show_search_field_default();
this.search_field_scale();
return this.parsing = false;
};
Chosen.prototype.result_do_highlight = function(el) {
var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
if (el.length) {
this.result_clear_highlight();
this.result_highlight = el;
this.result_highlight.addClass("highlighted");
maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
visible_top = this.search_results.scrollTop();
visible_bottom = maxHeight + visible_top;
high_top = this.result_highlight.position().top + this.search_results.scrollTop();
high_bottom = high_top + this.result_highlight.outerHeight();
if (high_bottom >= visible_bottom) {
return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
} else if (high_top < visible_top) {
return this.search_results.scrollTop(high_top);
}
}
};
Chosen.prototype.result_clear_highlight = function() {
if (this.result_highlight) {
this.result_highlight.removeClass("highlighted");
}
return this.result_highlight = null;
};
Chosen.prototype.results_show = function() {
if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
this.form_field_jq.trigger("chosen:maxselected", {
chosen: this
});
return false;
}
this.container.addClass("chosen-with-drop");
this.results_showing = true;
this.search_field.focus();
this.search_field.val(this.search_field.val());
this.winnow_results();
return this.form_field_jq.trigger("chosen:showing_dropdown", {
chosen: this
});
};
Chosen.prototype.update_results_content = function(content) {
return this.search_results.html(content);
};
Chosen.prototype.results_hide = function() {
if (this.results_showing) {
this.result_clear_highlight();
this.container.removeClass("chosen-with-drop");
this.form_field_jq.trigger("chosen:hiding_dropdown", {
chosen: this
});
}
return this.results_showing = false;
};
Chosen.prototype.set_tab_index = function(el) {
var ti;
if (this.form_field.tabIndex) {
ti = this.form_field.tabIndex;
this.form_field.tabIndex = -1;
return this.search_field[0].tabIndex = ti;
}
};
Chosen.prototype.set_label_behavior = function() {
var _this = this;
this.form_field_label = this.form_field_jq.parents("label");
if (!this.form_field_label.length && this.form_field.id.length) {
this.form_field_label = $("label[for='" + this.form_field.id + "']");
}
if (this.form_field_label.length > 0) {
return this.form_field_label.bind('click.chosen', function(evt) {
if (_this.is_multiple) {
return _this.container_mousedown(evt);
} else {
return _this.activate_field();
}
});
}
};
Chosen.prototype.show_search_field_default = function() {
if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
this.search_field.val(this.default_text);
return this.search_field.addClass("default");
} else {
this.search_field.val("");
return this.search_field.removeClass("default");
}
};
Chosen.prototype.search_results_mouseup = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target.length) {
this.result_highlight = target;
this.result_select(evt);
return this.search_field.focus();
}
};
Chosen.prototype.search_results_mouseover = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target) {
return this.result_do_highlight(target);
}
};
Chosen.prototype.search_results_mouseout = function(evt) {
if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
return this.result_clear_highlight();
}
};
Chosen.prototype.choice_build = function(item) {
var choice, close_link,
_this = this;
choice = $('<li />', {
"class": "search-choice"
}).html("<span>" + (this.choice_label(item)) + "</span>");
if (item.disabled) {
choice.addClass('search-choice-disabled');
} else {
close_link = $('<a />', {
"class": 'search-choice-close',
'data-option-array-index': item.array_index
});
close_link.bind('click.chosen', function(evt) {
return _this.choice_destroy_link_click(evt);
});
choice.append(close_link);
}
return this.search_container.before(choice);
};
Chosen.prototype.choice_destroy_link_click = function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (!this.is_disabled) {
return this.choice_destroy($(evt.target));
}
};
Chosen.prototype.choice_destroy = function(link) {
if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
this.show_search_field_default();
if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
this.results_hide();
}
link.parents('li').first().remove();
return this.search_field_scale();
}
};
Chosen.prototype.results_reset = function() {
this.reset_single_select_options();
this.form_field.options[0].selected = true;
this.single_set_selected_text();
this.show_search_field_default();
this.results_reset_cleanup();
this.form_field_jq.trigger("change");
if (this.active_field) {
return this.results_hide();
}
};
Chosen.prototype.results_reset_cleanup = function() {
this.current_selectedIndex = this.form_field.selectedIndex;
return this.selected_item.find("abbr").remove();
};
Chosen.prototype.result_select = function(evt) {
var high, item;
if (this.result_highlight) {
high = this.result_highlight;
this.result_clear_highlight();
if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
this.form_field_jq.trigger("chosen:maxselected", {
chosen: this
});
return false;
}
if (this.is_multiple) {
high.removeClass("active-result");
} else {
this.reset_single_select_options();
}
high.addClass("result-selected");
item = this.results_data[high[0].getAttribute("data-option-array-index")];
item.selected = true;
this.form_field.options[item.options_index].selected = true;
this.selected_option_count = null;
if (this.is_multiple) {
this.choice_build(item);
} else {
this.single_set_selected_text(this.choice_label(item));
}
if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
this.results_hide();
}
this.show_search_field_default();
if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
this.form_field_jq.trigger("change", {
'selected': this.form_field.options[item.options_index].value
});
}
this.current_selectedIndex = this.form_field.selectedIndex;
evt.preventDefault();
return this.search_field_scale();
}
};
Chosen.prototype.single_set_selected_text = function(text) {
if (text == null) {
text = this.default_text;
}
if (text === this.default_text) {
this.selected_item.addClass("chosen-default");
} else {
this.single_deselect_control_build();
this.selected_item.removeClass("chosen-default");
}
return this.selected_item.find("span").html(text);
};
Chosen.prototype.result_deselect = function(pos) {
var result_data;
result_data = this.results_data[pos];
if (!this.form_field.options[result_data.options_index].disabled) {
result_data.selected = false;
this.form_field.options[result_data.options_index].selected = false;
this.selected_option_count = null;
this.result_clear_highlight();
if (this.results_showing) {
this.winnow_results();
}
this.form_field_jq.trigger("change", {
deselected: this.form_field.options[result_data.options_index].value
});
this.search_field_scale();
return true;
} else {
return false;
}
};
Chosen.prototype.single_deselect_control_build = function() {
if (!this.allow_single_deselect) {
return;
}
if (!this.selected_item.find("abbr").length) {
this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
}
return this.selected_item.addClass("chosen-single-with-deselect");
};
Chosen.prototype.get_search_text = function() {
return $('<div/>').text($.trim(this.search_field.val())).html();
};
Chosen.prototype.winnow_results_set_highlight = function() {
var do_high, selected_results;
selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
if (do_high != null) {
return this.result_do_highlight(do_high);
}
};
Chosen.prototype.no_results = function(terms) {
var no_results_html;
no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
no_results_html.find("span").first().html(terms);
this.search_results.append(no_results_html);
return this.form_field_jq.trigger("chosen:no_results", {
chosen: this
});
};
Chosen.prototype.no_results_clear = function() {
return this.search_results.find(".no-results").remove();
};
Chosen.prototype.keydown_arrow = function() {
var next_sib;
if (this.results_showing && this.result_highlight) {
next_sib = this.result_highlight.nextAll("li.active-result").first();
if (next_sib) {
return this.result_do_highlight(next_sib);
}
} else {
return this.results_show();
}
};
Chosen.prototype.keyup_arrow = function() {
var prev_sibs;
if (!this.results_showing && !this.is_multiple) {
return this.results_show();
} else if (this.result_highlight) {
prev_sibs = this.result_highlight.prevAll("li.active-result");
if (prev_sibs.length) {
return this.result_do_highlight(prev_sibs.first());
} else {
if (this.choices_count() > 0) {
this.results_hide();
}
return this.result_clear_highlight();
}
}
};
Chosen.prototype.keydown_backstroke = function() {
var next_available_destroy;
if (this.pending_backstroke) {
this.choice_destroy(this.pending_backstroke.find("a").first());
return this.clear_backstroke();
} else {
next_available_destroy = this.search_container.siblings("li.search-choice").last();
if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
this.pending_backstroke = next_available_destroy;
if (this.single_backstroke_delete) {
return this.keydown_backstroke();
} else {
return this.pending_backstroke.addClass("search-choice-focus");
}
}
}
};
Chosen.prototype.clear_backstroke = function() {
if (this.pending_backstroke) {
this.pending_backstroke.removeClass("search-choice-focus");
}
return this.pending_backstroke = null;
};
Chosen.prototype.keydown_checker = function(evt) {
var stroke, _ref1;
stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
this.search_field_scale();
if (stroke !== 8 && this.pending_backstroke) {
this.clear_backstroke();
}
switch (stroke) {
case 8:
this.backstroke_length = this.search_field.val().length;
break;
case 9:
if (this.results_showing && !this.is_multiple) {
this.result_select(evt);
}
this.mouse_on_container = false;
break;
case 13:
if (this.results_showing) {
evt.preventDefault();
}
break;
case 32:
if (this.disable_search) {
evt.preventDefault();
}
break;
case 38:
evt.preventDefault();
this.keyup_arrow();
break;
case 40:
evt.preventDefault();
this.keydown_arrow();
break;
}
};
Chosen.prototype.search_field_scale = function() {
var div, f_width, h, style, style_block, styles, w, _i, _len;
if (this.is_multiple) {
h = 0;
w = 0;
style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
for (_i = 0, _len = styles.length; _i < _len; _i++) {
style = styles[_i];
style_block += style + ":" + this.search_field.css(style) + ";";
}
div = $('<div />', {
'style': style_block
});
div.text(this.search_field.val());
$('body').append(div);
w = div.width() + 25;
div.remove();
f_width = this.container.outerWidth();
if (w > f_width - 10) {
w = f_width - 10;
}
return this.search_field.css({
'width': w + 'px'
});
}
};
return Chosen;
})(AbstractChosen);
}).call(this);
}).apply(root, arguments);
});
}(this));
(function(root) {
define("angular-chosen", ["angular","jquery-chosen"], function() {
return (function() {
/**
* angular-chosen-localytics - Angular Chosen directive is an AngularJS Directive that brings the Chosen jQuery in a Angular way
* @version v1.3.0
* @link http://github.com/leocaseiro/angular-chosen
* @license MIT
*/
(function() {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
angular.module('localytics.directives', []);
angular.module('localytics.directives').directive('chosen', [
'$timeout', function($timeout) {
var CHOSEN_OPTION_WHITELIST, NG_OPTIONS_REGEXP, isEmpty, snakeCase;
NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
CHOSEN_OPTION_WHITELIST = ['persistentCreateOption', 'createOptionText', 'createOption', 'skipNoResults', 'noResultsText', 'allowSingleDeselect', 'disableSearchThreshold', 'disableSearch', 'enableSplitWordSearch', 'inheritSelectClasses', 'maxSelectedOptions', 'placeholderTextMultiple', 'placeholderTextSingle', 'searchContains', 'singleBackstrokeDelete', 'displayDisabledOptions', 'displaySelectedOptions', 'width', 'includeGroupLabelInSelected', 'maxShownResults'];
snakeCase = function(input) {
return input.replace(/[A-Z]/g, function($1) {
return "_" + ($1.toLowerCase());
});
};
isEmpty = function(value) {
var key;
if (angular.isArray(value)) {
return value.length === 0;
} else if (angular.isObject(value)) {
for (key in value) {
if (value.hasOwnProperty(key)) {
return false;
}
}
}
return true;
};
return {
restrict: 'A',
require: '?ngModel',
priority: 1,
link: function(scope, element, attr, ngModel) {
var chosen, empty, initOrUpdate, match, options, origRender, startLoading, stopLoading, updateMessage, valuesExpr, viewWatch;
scope.disabledValuesHistory = scope.disabledValuesHistory ? scope.disabledValuesHistory : [];
element = $(element);
element.addClass('localytics-chosen');
options = scope.$eval(attr.chosen) || {};
angular.forEach(attr, function(value, key) {
if (indexOf.call(CHOSEN_OPTION_WHITELIST, key) >= 0) {
return attr.$observe(key, function(value) {
options[snakeCase(key)] = String(element.attr(attr.$attr[key])).slice(0, 2) === '{{' ? value : scope.$eval(value);
return updateMessage();
});
}
});
startLoading = function() {
return element.addClass('loading').attr('disabled', true).trigger('chosen:updated');
};
stopLoading = function() {
element.removeClass('loading');
if (angular.isDefined(attr.disabled)) {
element.attr('disabled', attr.disabled);
} else {
element.attr('disabled', false);
}
return element.trigger('chosen:updated');
};
chosen = null;
empty = false;
initOrUpdate = function() {
var defaultText;
if (chosen) {
return element.trigger('chosen:updated');
} else {
$timeout(function() {
chosen = element.chosen(options).data('chosen');
});
if (angular.isObject(chosen)) {
return defaultText = chosen.default_text;
}
}
};
updateMessage = function() {
if (empty) {
element.attr('data-placeholder', chosen.results_none_found).attr('disabled', true);
} else {
element.removeAttr('data-placeholder');
}
return element.trigger('chosen:updated');
};
if (ngModel) {
origRender = ngModel.$render;
ngModel.$render = function() {
origRender();
return initOrUpdate();
};
element.on('chosen:hiding_dropdown', function() {
return scope.$apply(function() {
return ngModel.$setTouched();
});
});
if (attr.multiple) {
viewWatch = function() {
return ngModel.$viewValue;
};
scope.$watch(viewWatch, ngModel.$render, true);
}
} else {
initOrUpdate();
}
attr.$observe('disabled', function() {
return element.trigger('chosen:updated');
});
if (attr.ngOptions && ngModel) {
match = attr.ngOptions.match(NG_OPTIONS_REGEXP);
valuesExpr = match[7];
scope.$watchCollection(valuesExpr, function(newVal, oldVal) {
var timer;
return timer = $timeout(function() {
if (angular.isUndefined(newVal)) {
return startLoading();
} else {
empty = isEmpty(newVal);
stopLoading();
return updateMessage();
}
});
});
return scope.$on('$destroy', function(event) {
if (typeof timer !== "undefined" && timer !== null) {
return $timeout.cancel(timer);
}
});
}
}
};
}
]);
}).call(this);
}).apply(root, arguments);
});
}(this));
/*
* 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