/* global define: false */
define(
'app/services/domainService',[
// Libraries
"angular",
"lodash",
// CJT
"cjt/util/locale",
"cjt/io/api",
"cjt/io/uapi-request",
"cjt/io/uapi", // IMPORTANT: Load the driver so its ready
// Angular components
"cjt/services/APIService"
],
function(angular, _, LOCALE, API, APIREQUEST, APIDRIVER) {
// Fetch the current application
var app = angular.module("App");
/**
* Setup the domainlist API service
*/
app.factory("DomainService", ["$q", "APIService", function($q, APIService) {
/**
* Converts the response to our application data structure
* @param {Object} response
* @return {Object} Sanitized data structure.
*/
function convertResponseToList(response) {
var items = [];
if (response.data) {
var data = response.data;
for (var i = 0, length = data.length; i < length; i++) {
items.push(data[i]);
}
var meta = response.meta;
var totalItems = meta.paginate.total_records || data.length;
var totalPages = meta.paginate.total_pages || 1;
var totalEnabled = 0,
totalDisabled = 0;
if ( meta.hasOwnProperty("cPGreyList") ) {
totalEnabled = meta.cPGreyList.total_enabled;
totalDisabled = meta.cPGreyList.total_disabled;
} else {
// calculate these since we are getting this from disableAll or
// enableAll and it does not do these calculations server side.
var enabledItems = _.filter(data, function(item) {
return !!item.enabled;
});
totalEnabled = enabledItems ? enabledItems.length : 0;
totalDisabled = data.length - totalEnabled;
}
return {
items: items,
totalItems: totalItems,
totalPages: totalPages,
totalEnabled: totalEnabled,
totalDisabled: totalDisabled
};
} else {
return {
items: [],
totalItems: 0,
totalPages: 0,
totalEnabled: 0,
totalDisabled: 0
};
}
}
/**
* Toggles greylisting on the specified domains
*
* @param {String} API method to call.
* @param {String} domains to be toggled on or off
* @return {Object} The result object.
*/
function toggleDomains(method, domains) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("cPGreyList", method, {
domains: domains
});
var deferred = this.deferred(apiCall, {
done: function(response, deferred) {
// create items from the response
response = response.parsedResponse;
var results = convertResponseToList(response);
if (response.status) {
// keep the promise
deferred.resolve(results);
} else {
// pass the error along
deferred.reject({
items: results.items,
error: response.error
});
}
}
});
// pass the promise back to the controller
return deferred.promise;
}
/**
* Toggles greylisting for all domains
*
* @param {String} API method to call.
* @return {Object} The result object.
*/
function toggleAllDomains(method) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("cPGreyList", method);
var deferred = this.deferred(apiCall, {
done: function(response, deferred) {
// create items from the response
response = response.parsedResponse;
var results = convertResponseToList(response);
if (response.status) {
// keep the promise
deferred.resolve(results);
} else {
// pass the error along
results.error = response.error;
deferred.reject(results);
}
}
});
// pass the promise back to the controller
return deferred.promise;
}
// Set up the service's constructor and parent
var DomainsService = function() {};
DomainsService.prototype = new APIService();
// Extend the prototype with any class-specific functionality
angular.extend(DomainsService.prototype, {
/**
* Get a list domains that match the selection criteria passed in meta parameter
* @param {object} meta Optional meta data to control sorting, filtering and paging
* @param {string} meta.sortBy Name of the field to sort by
* @param {string} meta.sordDirection asc or desc
* @param {string} meta.sortType Optional name of the sort rule to apply to the sorting
* @param {string} meta.filterBy Name of the filed to filter by
* @param {string} meta.filterCompare Optional comparator to use when comparing for filter.
* If not provided, will default to ???.
* May be one of:
* TODO: Need a list of valid filter types.
* @param {string} meta.filterValue Expression/argument to pass to the compare method.
* @param {string} meta.pageNumber Page number to fetch.
* @param {string} meta.pageSize Size of a page, will default to 10 if not provided.
* @return {Promise} Promise that will fulfill the request.
*/
fetchList: function(meta) {
var apiCall = new APIREQUEST.Class();
apiCall.initialize("cPGreyList", "list_domains");
if (meta) {
if (meta.sortBy && meta.sortDirection) {
apiCall.addSorting(meta.sortBy, meta.sortDirection, meta.sortType);
}
if (meta.pageNumber) {
apiCall.addPaging(meta.pageNumber, meta.pageSize || 10);
}
if (meta.filterBy && meta.filterCompare && meta.filterValue) {
apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
}
}
var deferred = this.deferred(apiCall, {
transformAPISuccess: convertResponseToList
});
// pass the promise back to the controller
return deferred.promise;
},
/**
* Helper method that calls convertResponseToList to prepare the data structure
* @param {Object} response
* @return {Object} Sanitized data structure.
*/
prepareList: function(response) {
return convertResponseToList(response);
},
/**
* Enables greylisting on the specified domains
* @param {Object} domains A coma sepperated list of domains
* @return {Object} The result object.
*/
enableDomains: function(domains) {
return toggleDomains.call(this, "enable_domains", domains);
},
/**
* Enables greylisting on all domains
* @return {Object} The result object.
*/
enableAllDomains: function() {
return toggleAllDomains.call(this, "enable_all_domains");
},
/**
* Disables greylisting on the specified domains
* @param {Object} domains A coma sepperated list of domains
* @return {Object} The result object.
*/
disableDomains: function(domains) {
return toggleDomains.call(this, "disable_domains", domains);
},
/**
* Disables greylisting on all domains
* @return {Object} The result object.
*/
disableAllDomains: function() {
return toggleAllDomains.call(this, "disable_all_domains");
}
});
return new DomainsService();
}]);
}
);
/*
# mail/greylisting/views/domains.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global define: false, PAGE: true */
/* jshint -W100 */
define(
'app/views/domains',[
"angular",
"lodash",
"cjt/util/locale",
"uiBootstrap",
"cjt/decorators/paginationDecorator",
"cjt/directives/toggleSortDirective",
"cjt/directives/pageSizeDirective",
"cjt/directives/validationItemDirective",
"cjt/directives/spinnerDirective",
"cjt/directives/autoFocus",
"cjt/directives/alertList",
"cjt/services/alertService",
"cjt/filters/wrapFilter",
"cjt/filters/breakFilter",
"app/services/domainService"
],
function(angular, _, LOCALE) {
"use strict";
// Retrieve the current application
var app = angular.module("App");
/**
* Update the enabled and exception field in the list based on
* the returned updates.
*
* @private
* @method _updateStatus
* @param {Array} list List of domains
* @param {Array} updates List of update records
*/
var _updateStatus = function(list, updates) {
for (var i = 0, l = list.length; i < l; i++) {
var update = _findUpdateByDomain(updates, list[i].domain);
if (update) {
list[i].enabled = update.enabled;
if (update.exception) {
list[i].exception = update.exception;
} else {
delete list[i].exception;
}
} else {
if (list[i].exception) {
delete list[i].exception;
}
}
}
};
/**
* Find an update record by the domain name
*
* @private
* @method _findUpdateByDomain
* @param {Array} updates List of update records
* @param {String} domain Domain to look for in the update list.
* @return {Object} Update associated with the requested domain or nothing if not found.
*/
var _findUpdateByDomain = function(updates, domain) {
for (var i = 0, l = updates.length; i < l; i++) {
if (updates[i].domain === domain) {
return updates[i];
}
}
return;
};
// Setup the controller
var controller = app.controller(
"domainListController", [
"$scope",
"$routeParams",
"$q",
"DomainService",
"spinnerAPI",
"alertService",
function(
$scope,
$routeParams,
$q,
DomainService,
spinnerAPI,
alertService
) {
/**
* Initialize the scope variables
*
* @private
* @method _initializeScope
*/
var _initializeScope = function() {
$scope.activeSearch = false;
$scope.filteredData = false;
$scope.totalEnabled = 0;
$scope.totalDisabled = 0;
$scope.selectedRow = -1;
// Setup the enabled bits...
$scope.isEnabled = PAGE.enabled;
$scope.hasFeature = PAGE.hasFeature;
if (!$scope.hasFeature) {
return;
}
// setup data structures for the view
$scope.domainList = [];
$scope.totalPages = 0;
$scope.totalItems = 0;
$scope.meta = {
filterBy: $routeParams.filterBy || "*",
filterCompare: $routeParams.filterCompare || "contains",
filterValue: $routeParams.filterValue || "",
pageSize: $routeParams.pageSize || 20,
pageNumber: $routeParams.pageNumber || 1,
sortDirection: $routeParams.sortDirection || "asc",
sortBy: $routeParams.sortBy || "domain",
sortType: $routeParams.sortType,
pageSizes: [20, 50, 100],
start: 0,
limit: 0
};
};
/**
* Are All Domains Currently Disabled
*
* @method allDomainsAreDisabled
* @return {boolean} True if all Domains are Disabled
*/
$scope.allDomainsAreDisabled = function() {
return $scope.totalDisabled === $scope.totalItems;
};
/**
* Are All Domains Currently Enabled
*
* @method allDomainsAreEnabled
* @return {boolean} True if all Domains are Enabled
*/
$scope.allDomainsAreEnabled = function() {
return $scope.totalEnabled === $scope.totalItems;
};
/**
* Does the logged in account have domains
*
* @method hasNoDomains
* @return {boolean} True if there are no domains associated with the logged in account
*/
$scope.hasNoDomains = function() {
return $scope.totalItems === 0;
};
/**
* Clear the search query
*
* @method clearFilter
*/
$scope.clearFilter = function() {
$scope.meta.filterValue = "";
$scope.activeSearch = false;
$scope.filteredData = false;
$scope.selectedRow = -1;
return $scope.fetch();
};
/**
* Start a search query
*
* @method startFilter
*/
$scope.startFilter = function() {
$scope.activeSearch = true;
$scope.filteredData = false;
$scope.selectedRow = -1;
var defer = $q.defer();
defer.promise
.then(function() {
// select the first page of search results
$scope.selectPage(1);
})
.then(function() {
$scope.filteredData = true;
});
defer.resolve();
};
/**
* Selects a table row
*
* @method toggleRow
* @param {Object} $event The Event object
* @param {Number} index The index of selected row
*/
$scope.toggleRow = function($event, index) {
// prevent the default action of the link
$event.preventDefault();
if ( index === $scope.selectedRow ) {
// collapse the row
$scope.selectedRow = -1;
} else {
// expand the selected row
$scope.selectedRow = index;
}
};
/**
* Change the page size
*
* @method selectPageSize
*/
$scope.selectPageSize = function() {
$scope.selectPage(1);
};
/**
* Select a specific page
*
* @method selectPage
* @param {Number} [page] Optional page number, if not provided will use the current
* page provided by the scope.meta.pageNumber.
* @return {Promise}
*/
$scope.selectPage = function(page) {
// clear the selected row
$scope.selectedRow = -1;
// set the page if requested
if (page && angular.isNumber(page)) {
$scope.meta.pageNumber = page;
}
// fetch the page
return $scope.fetch();
};
/**
* Sort the list of domains
*
* @method sortList
* @param {Object} meta An object with metadata properties of sortBy, sortDirection, and sortType.
* @param {Boolean} [defaultSort] If true, this sort was not initiated by the user.
*/
$scope.sortList = function(meta, defaultSort) {
// clear the selected row
$scope.selectedRow = -1;
if (!defaultSort) {
$scope.fetch();
}
};
/**
* Handles the keybinding for the clearing and searching.
* Esc clears the search field.
* Enter performs a search.
*
* @method triggerToggleSearch
* @param {Event} event - The event object
*/
$scope.triggerToggleSearch = function(event) {
// clear on Esc
if (event.keyCode === 27) {
$scope.toggleSearch(true);
}
// filter on Enter
if (event.keyCode === 13) {
$scope.toggleSearch();
}
};
/**
* Toggles the clear button and conditionally performs a search.
* The expected behavior is if the user clicks the button or focuses the button and hits enter the button state rules.
* If the user hits <enter> in the field, its a submit action with just request the data.
* If the user hits <esc> behavior which will unconditionally clear.
*
* @method toggleSearch
* @param {Boolean} isClick Toggle button clicked.
*/
$scope.toggleSearch = function(isClick) {
var filter = $scope.meta.filterValue;
if ( !filter && ($scope.activeSearch || $scope.filteredData)) {
// no query in box, but we previously filtered or there is an active search
$scope.clearFilter();
} else if (isClick && $scope.activeSearch ) {
// User clicks clear
$scope.clearFilter();
} else if (filter) {
$scope.startFilter();
}
};
/**
* Fetch the list of domains from the server.
*
* @method fetch
* @return {Promise} Promise that when fulfilled will result in the list being loaded with the new criteria.
*/
$scope.fetch = function() {
spinnerAPI.start("loadingSpinner");
return DomainService
.fetchList($scope.meta)
.then(function(results) {
$scope.domainList = results.items;
$scope.totalItems = results.totalItems;
$scope.totalPages = results.totalPages;
$scope.totalEnabled = results.totalEnabled;
$scope.totalDisabled = results.totalDisabled;
$scope.meta.start = ($scope.meta.pageNumber - 1) * $scope.meta.pageSize + 1;
$scope.meta.limit = $scope.meta.pageNumber * $scope.meta.pageSize;
if ($scope.meta.limit > $scope.totalItems) {
$scope.meta.limit = $scope.totalItems;
}
if ($scope.meta.limit === 0) {
$scope.meta.start = 0;
}
}, function(error) {
alertService.add({
type: "danger",
message: error,
closeable: true,
replace: false,
group: "greylisting"
});
})
.then(function() {
spinnerAPI.stop("loadingSpinner");
});
};
/**
* Sets the status of greylisting on the supplied domain.
*
* @method setDomain
* @param {Object} domain The domain with status to set
* @return {Promise} Promise that when fulfilled will result in the domains status being set in the API.
*/
$scope.setDomain = function(domain) {
spinnerAPI.start("loadingSpinner");
if ( domain.enabled ) {
return DomainService
.enableDomains(domain.domain)
.then(function(result) {
$scope.totalEnabled++;
$scope.totalDisabled--;
alertService.add({
type: "success",
message: LOCALE.maketext("Successfully enabled [asis,Greylisting] on “[output,class,_1,nobreak]”.", result.items[0].domain),
closeable: true,
replace: false,
autoClose: 10000,
group: "greylisting"
});
}, function(results) {
alertService.add({
type: "danger",
message: results.error,
closeable: true,
replace: false,
group: "greylisting"
});
})
.then(function() {
spinnerAPI.stop("loadingSpinner");
});
} else {
return DomainService
.disableDomains(domain.domain)
.then(function(result) {
$scope.totalDisabled++;
$scope.totalEnabled--;
alertService.add({
type: "success",
message: LOCALE.maketext("Successfully disabled [asis,Greylisting] on “[output,class,_1,nobreak]”.", result.items[0].domain),
closeable: true,
replace: false,
autoClose: 10000,
group: "greylisting"
});
}, function(results) {
alertService.add({
type: "danger",
message: results.error,
closeable: true,
replace: false,
group: "greylisting"
});
})
.then(function() {
spinnerAPI.stop("loadingSpinner");
});
}
};
/**
* Enable greylisting on all domains
*
* @method enableAllDomains
* @param {Object} $event The Event object
* @return {Promise} Promise that when fulfilled will result in the domains being enabled.
*/
$scope.enableAllDomains = function($event) {
if ($scope.allDomainsAreEnabled()) {
return;
}
spinnerAPI.start("loadingSpinner");
return DomainService
.enableAllDomains()
.then(function(results) {
_updateStatus($scope.domainList, results.items);
$scope.totalItems = results.totalItems;
$scope.totalPages = results.totalPages;
$scope.totalEnabled = results.totalItems;
$scope.totalDisabled = 0;
alertService.add({
type: "success",
message: LOCALE.maketext("Successfully enabled [asis,Greylisting] on all domains."),
closeable: true,
replace: false,
autoClose: 10000,
group: "greylisting"
});
}, function(results) {
alertService.add({
type: "danger",
message: results.error,
closeable: true,
replace: false,
group: "greylisting"
});
})
.then(function() {
spinnerAPI.stop("loadingSpinner");
});
};
/**
* Disable greylisting on all domains.
*
* @method disableAllDomains
* @param {Object} $event The Event object
* @return {Promise} Promise that when fulfilled will result in the domains being enabled.
*/
$scope.disableAllDomains = function($event) {
if ($scope.allDomainsAreDisabled()) {
return;
}
spinnerAPI.start("loadingSpinner");
return DomainService
.disableAllDomains()
.then(function(results) {
_updateStatus($scope.domainList, results.items);
$scope.totalItems = results.totalItems;
$scope.totalPages = results.totalPages;
$scope.totalDisabled = results.totalItems;
$scope.totalEnabled = 0;
alertService.add({
type: "success",
message: LOCALE.maketext("Successfully disabled [asis,Greylisting] on all domains."),
closeable: true,
replace: false,
autoClose: 10000,
group: "greylisting"
});
}, function(results) {
alertService.add({
type: "danger",
message: results.error,
closeable: true,
replace: false,
group: "greylisting"
});
})
.finally(function() {
spinnerAPI.stop("loadingSpinner");
});
};
/**
* Check if there are any disabled domains
*
* @method hasDisabledDomains
* @return {Boolean} true if there are disabled domains, false otherwise
*/
$scope.hasDisabledDomains = function() {
return $scope.totalDisabled > 0;
};
/**
* Initialize the view
*
* @private
* @method _initializeView
*/
var _initializeView = function() {
// check for page data in the template if this is a first load
if (app.firstLoad.domainList && PAGE.domainList) {
app.firstLoad.domainList = false;
var results = DomainService.prepareList(PAGE.domainList);
$scope.meta.pageNumber = 1;
$scope.domainList = results.items;
$scope.totalItems = results.totalItems;
$scope.totalPages = results.totalPages;
$scope.totalEnabled = PAGE.domainList.meta.cPGreyList.total_enabled;
$scope.totalDisabled = PAGE.domainList.meta.cPGreyList.total_disabled;
$scope.meta.start = ($scope.meta.pageNumber - 1) * $scope.meta.pageSize + 1;
$scope.meta.limit = $scope.meta.pageNumber * $scope.meta.pageSize;
if ($scope.meta.limit > $scope.totalItems) {
$scope.meta.limit = $scope.totalItems;
}
if ($scope.meta.limit === 0) {
$scope.meta.start = 0;
}
} else {
// Otherwise, retrieve it via ajax
$scope.selectPage(1);
}
};
_initializeScope();
// if the user types something else in the search box, we change the button icon so they can search again.
$scope.$watch("meta.filterValue", function(oldValue, newValue) {
if (oldValue === newValue) {
return;
}
$scope.activeSearch = false;
});
_initializeView();
}
]
);
return controller;
}
);
/*
# mail/greylisting/index.js Copyright(c) 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
/* global require: false, define: false */
define(
'app/index',[
"angular",
"jquery",
"lodash",
"cjt/core",
"cjt/modules",
"ngRoute",
"uiBootstrap"
],
function(angular, $, _, CJT) {
return function() {
// First create the application
angular.module("App", ["ngRoute", "ui.bootstrap", "cjt2.cpanel"]);
// Then load the application dependencies
var app = require(
[
// Application Modules
"cjt/bootstrap",
"cjt/views/applicationController",
"cjt/directives/alert",
"cjt/directives/alertList",
"app/views/domains",
], function(BOOTSTRAP) {
var app = angular.module("App");
app.firstLoad = {
domainList: true,
};
// routing
app.config(["$routeProvider",
function($routeProvider) {
// Setup the routes
$routeProvider.when("/domains/", {
controller: "domainListController",
templateUrl: CJT.buildFullPath("mail/greylisting/views/domains.ptt")
});
$routeProvider.otherwise({
"redirectTo": "/domains/"
});
}
]);
BOOTSTRAP("#content", "App");
});
return app;
};
}
);