Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/hulkd/index.cmb.js

/*
# cpanel - whostmgr/docroot/templates/hulkd/services/HulkdDataSource.js
#                                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* eslint-disable camelcase */

define(
    'app/services/HulkdDataSource',[
        "angular",
        "jquery",
        "lodash",
        "cjt/util/locale",
        "cjt/util/query",
        "cjt/util/parse",
        "cjt/io/api",
        "cjt/io/whm-v1-request",
        "cjt/io/whm-v1", // IMPORTANT: Load the driver so it's ready
    ],
    function(angular, $, _, LOCALE, QUERY, PARSE, API, APIREQUEST, APIDRIVER) {
        "use strict";

        // Retrieve the current application
        var app = angular.module("App");

        var hulkDataSource = app.factory("HulkdDataSource", ["$q", "PAGE", "COUNTRY_CONSTANTS", function($q, PAGE, COUNTRY_CONSTANTS) {

            var hulkData = {};

            hulkData.whitelist = [];
            hulkData.blacklist = [];

            hulkData.whitelist_comments = {};
            hulkData.blacklist_comments = {};

            hulkData.whitelist_is_cached = false;
            hulkData.blacklist_is_cached = false;

            hulkData.config_settings = {};

            hulkData.enabled = PAGE.hulkd_status.is_enabled;

            hulkData.hulkd_status = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "cphulk_status");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        hulkData.enabled = PARSE.parsePerlBoolean(response.data.is_enabled);
                        deferred.resolve(hulkData.enabled);
                    });

                return deferred.promise;
            };

            hulkData.get_countries_with_known_ip_ranges = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "get_countries_with_known_ip_ranges");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        deferred.resolve(response.data);
                    });

                return deferred.promise;
            };

            hulkData.clear_caches = function() {
                hulkData.whitelist = [];
                hulkData.blacklist = [];

                hulkData.whitelist_comments = {};
                hulkData.blacklist_comments = {};

                hulkData.whitelist_is_cached = false;
                hulkData.blacklist_is_cached = false;
            };

            hulkData.enable_hulkd = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "enable_cphulk");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            deferred.resolve(response);
                            hulkData.enabled = true;
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            hulkData.disable_hulkd = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "disable_cphulk");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            deferred.resolve(response.status);
                            hulkData.enabled = false;

                            // clear the caches to force a reload if
                            // hulkd is re-enabled later
                            hulkData.clear_caches();
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            hulkData.convert_config_settings = function(config) {
                var settings = {};

                settings.block_brute_force_with_firewall = PARSE.parsePerlBoolean(config.cphulk_config.block_brute_force_with_firewall);
                settings.block_excessive_brute_force_with_firewall = PARSE.parsePerlBoolean(config.cphulk_config.block_excessive_brute_force_with_firewall);
                settings.brute_force_period_mins = config.cphulk_config.brute_force_period_mins;
                settings.command_to_run_on_brute_force = config.cphulk_config.command_to_run_on_brute_force;
                settings.command_to_run_on_excessive_brute_force = config.cphulk_config.command_to_run_on_excessive_brute_force;
                settings.is_enabled = PARSE.parsePerlBoolean(config.cphulk_config.is_enabled);
                settings.ip_brute_force_period_mins = config.cphulk_config.ip_brute_force_period_mins;
                settings.lookback_period_min = config.cphulk_config.lookback_period_min;
                settings.max_failures = config.cphulk_config.max_failures;
                settings.max_failures_byip = config.cphulk_config.max_failures_byip;
                settings.mark_as_brute = config.cphulk_config.mark_as_brute;
                settings.notify_on_root_login = PARSE.parsePerlBoolean(config.cphulk_config.notify_on_root_login);
                settings.notify_on_root_login_for_known_netblock = PARSE.parsePerlBoolean(config.cphulk_config.notify_on_root_login_for_known_netblock);
                settings.notify_on_brute = PARSE.parsePerlBoolean(config.cphulk_config.notify_on_brute);
                settings.can_temp_ban_firewall = PARSE.parsePerlBoolean(config.cphulk_config.can_temp_ban_firewall);
                settings.iptable_error = config.cphulk_config.iptable_error;
                settings.username_based_protection = PARSE.parsePerlBoolean(config.cphulk_config.username_based_protection);
                settings.ip_based_protection = PARSE.parsePerlBoolean(config.cphulk_config.ip_based_protection);
                settings.username_based_protection_local_origin = PARSE.parsePerlBoolean(config.cphulk_config.username_based_protection_local_origin);
                settings.username_based_protection_for_root = PARSE.parsePerlBoolean(config.cphulk_config.username_based_protection_for_root);

                settings.country_whitelist = config.cphulk_config.country_whitelist ? config.cphulk_config.country_whitelist.split(",") : [];
                settings.country_blacklist = config.cphulk_config.country_blacklist ? config.cphulk_config.country_blacklist.split(",") : [];

                hulkData.config_settings = settings;

                return settings;
            };

            hulkData.save_config_settings = function(config) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "save_cphulk_config");

                for (var i = 0, keys = _.keys(config), len = keys.length; i < len; i++) {

                    // we do not want to send this option
                    if (keys[i] === "is_enabled") {
                        continue;
                    }

                    var value = config[keys[i]];
                    if (_.isArray(value)) {
                        value = value.join(",");
                    } else if (_.isBoolean(value)) {
                        if (value) {
                            value = 1;
                        } else {
                            value = 0;
                        }
                    }

                    apiCall.addArgument(keys[i], value);
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {

                            // clean up the restart_ssh boolean
                            response.data.restart_ssh = PARSE.parsePerlBoolean(response.data.restart_ssh);
                            deferred.resolve(response.data);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };


            hulkData.load_config_settings = function() {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "load_cphulk_config");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response.data,
                                settings = hulkData.convert_config_settings(results);
                            deferred.resolve(settings);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            hulkData.set_cphulk_config_keys = function(updatedKeys) {
                var batchCalls = [];

                angular.forEach(updatedKeys, function(value, key) {
                    batchCalls.push({
                        func: "set_cphulk_config_key",
                        data: {
                            key: key,
                            value: value,
                        },
                    });
                });

                return _batch_apiv1(batchCalls).then(function(results) {

                    // Use the last updated config, giving the most recent cphulk configuration
                    var response = results.pop();
                    response = response.parsedResponse;
                    var settings = hulkData.convert_config_settings(response.data);
                    return hulkData._parse_xlisted_countries(settings);
                });
            };

            function _batch_apiv1(calls_infos) {
                var deferred = $q.defer();

                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "batch");

                calls_infos.forEach(function(call, i) {
                    apiCall.addArgument(
                        ("command-" + i),
                        call.func + "?" + QUERY.make_query_string(call.data)
                    );
                });

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response.data;
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            }

            hulkData.load_xlisted_countries = function() {
                return hulkData.load_config_settings().then(function(settings) {
                    return hulkData._parse_xlisted_countries(settings);
                });
            };

            hulkData._parse_xlisted_countries = function(settings) {
                var countries = {};
                settings.country_blacklist.forEach(function(item) {
                    countries[item] = COUNTRY_CONSTANTS.BLACKLISTED;
                });
                settings.country_whitelist.forEach(function(item) {
                    countries[item] = COUNTRY_CONSTANTS.WHITELISTED;
                });
                return countries;
            };


            hulkData.add_to_list = function(list, records) {
                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "batch_create_cphulk_records", null, null, { json: true });
                apiCall.addArgument("list_name", list);
                apiCall.addArgument("records", records);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {

                        // create items from the response
                        response = response.parsedResponse;
                        if (response.status) {

                            // on API call success, add to list
                            var return_data = {
                                added: [],
                                rejected: response.data.ips_failed,
                                updated: [],
                            };

                            var original_ips_added = response.data.original_ips_added || [];
                            var ip_list = hulkData[ list + "list" ];
                            var comment_list = hulkData[ list + "list_comments"];

                            for (var i = 0, len = original_ips_added.length; i < len; i++) {

                                var ip = original_ips_added[i];
                                var ip_formatted = response.data.ips_added[i];

                                // Update the ip cache and parse the return data
                                var index = _.indexOf(hulkData.whitelist, ip_formatted);
                                if (index === -1) {
                                    ip_list.push(ip_formatted);
                                    return_data.added.push(ip_formatted);
                                } else {
                                    return_data.updated.push(ip_formatted);
                                }

                                // Update the comment cache.
                                var record = _.find(records, function(record) {
                                    return record.ip === ip;
                                });
                                if (record && record.comment) {
                                    comment_list[ip_formatted] = record.comment;
                                }

                                // Extra info for the whitelist return.
                                if (list === "white") {
                                    return_data.requester_ip = response.data.requester_ip;
                                    return_data.requester_ip_is_whitelisted = response.data.requester_ip_is_whitelisted;
                                }
                            }

                            deferred.resolve(return_data);

                        } else {

                            // pass the error along
                            var error_details = {
                                main_message: response.error,
                                secondary_messages: [],
                            };

                            // Build the reason individual ips were rejected.
                            Object.keys(response.data.ips_failed).forEach(function(ip) {
                                var rejectReason = response.data.ips_failed[ip];
                                error_details.secondary_messages.push(rejectReason);
                            });

                            deferred.reject(error_details);
                        }
                    });

                // pass the promise back to the controller

                return deferred.promise;
            };

            hulkData.add_to_whitelist = function(records) {
                return hulkData.add_to_list("white", records);
            };

            hulkData.add_to_blacklist = function(records) {
                return hulkData.add_to_list("black", records);
            };

            hulkData.remove_from_list = function(ips_to_delete, list) {

                var deferred = $q.defer();
                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "delete_cphulk_record");
                apiCall.addArgument("list_name", list);
                for (var i = 0; i < ips_to_delete.length; i++) {
                    apiCall.addArgument("ip-" + i, ips_to_delete[i]);
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {

                        // create items from the response
                        response = response.parsedResponse;
                        if (response.status) {
                            var i = 0;

                            if (list === "white") {

                                // remove the IPs
                                hulkData.whitelist = _.difference(hulkData.whitelist, response.data.ips_removed);

                                // remove the comment
                                for (i = 0; i < response.data.ips_removed.length; i++) {
                                    delete hulkData.whitelist_comments[response.data.ips_removed[i]];
                                }
                            } else {

                                // remove the IPs
                                hulkData.blacklist = _.difference(hulkData.blacklist, response.data.ips_removed);

                                // remove the comment
                                for (i = 0; i < response.data.ips_removed.length; i++) {
                                    delete hulkData.blacklist_comments[response.data.ips_removed[i]];
                                }
                            }

                            deferred.resolve({ removed: response.data.ips_removed, not_removed: response.data.ips_failed, requester_ip: response.data.requester_ip, requester_ip_is_whitelisted: response.data.requester_ip_is_whitelisted });
                        } else {

                            // pass the error along
                            deferred.reject(response.error);
                        }
                    });

                // pass the promise back to the controller

                return deferred.promise;

            };

            hulkData.remove_from_whitelist = function(ips_to_delete) {
                return hulkData.remove_from_list(ips_to_delete, "white");
            };

            hulkData.remove_from_blacklist = function(ip_to_delete) {
                return hulkData.remove_from_list(ip_to_delete, "black");
            };

            hulkData.remove_all_from_whitelist = function() {
                return hulkData.remove_from_whitelist(hulkData.whitelist);
            };

            hulkData.remove_all_from_blacklist = function() {
                return hulkData.remove_from_blacklist(hulkData.blacklist);
            };

            hulkData.load_list = function(list) {
                var deferred = $q.defer();

                var apiCall = new APIREQUEST.Class();
                apiCall.initialize("", "read_cphulk_records");
                apiCall.addArgument("list_name", list);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {

                        // create items from the response
                        response = response.parsedResponse;
                        if (response.status) {

                            // on API call success, populate data structure

                            var return_data = {};

                            if (list === "white") {

                                // check to see if the ip address is not whitelisted
                                if (!response.data.requester_ip_is_whitelisted) {
                                    return_data.whitelist_warning = response.data.warning_ip;
                                }
                                if (response.data.restart_ssh) {
                                    return_data.restart_ssh = true;
                                }
                                if (response.data.warning_ssh) {
                                    return_data.warning_ssh = response.data.warning_ssh;
                                }

                                hulkData.whitelist = Object.keys(response.data.ips_in_list).slice();
                                for (var i = 0; i < hulkData.whitelist.length; i++) {
                                    if (response.data.ips_in_list[hulkData.whitelist[i]]) {
                                        hulkData.whitelist_comments[hulkData.whitelist[i]] = response.data.ips_in_list[hulkData.whitelist[i]];
                                    }
                                }
                                hulkData.whitelist_is_cached = true;
                                return_data.list = hulkData.whitelist;
                                return_data.comments = hulkData.whitelist_comments;
                                return_data.requester_ip = response.data.requester_ip;
                                return_data.requester_ip_is_whitelisted = response.data.requester_ip_is_whitelisted;
                                deferred.resolve(return_data);
                            } else if (list === "black") {
                                hulkData.blacklist = Object.keys(response.data.ips_in_list).slice();
                                for (var j = 0; j < hulkData.blacklist.length; j++) {
                                    if (response.data.ips_in_list[hulkData.blacklist[j]]) {
                                        hulkData.blacklist_comments[hulkData.blacklist[j]] = response.data.ips_in_list[hulkData.blacklist[j]];
                                    }
                                }
                                hulkData.blacklist_is_cached = true;
                                return_data.list = hulkData.blacklist;
                                return_data.comments = hulkData.blacklist_comments;
                                return_data.requester_ip = response.data.requester_ip;
                                deferred.resolve(return_data);
                            }
                        } else {

                            // pass the error along
                            deferred.reject(response.error);
                        }
                    });

                // pass the promise back to the controller

                return deferred.promise;
            };

            function init() {

                // check for page data in the template if this is a first load
                if (app.firstLoad && app.firstLoad.configs && PAGE.config_values) {
                    app.firstLoad.configs = false;
                    hulkData.config_settings = hulkData.convert_config_settings(PAGE.config_values);
                }
            }

            init();

            return hulkData;
        }]);

        return hulkDataSource;
    }
);

/*
# templates/hulkd/directives/disableValidation.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
*/

/**
  * @summary Directive that disables any validation functions tied to an ngModel
  * based on a value (which must be evaluated) passed to the directive.
  *
  * This code is based on this plunkr: https://embed.plnkr.co/EM1tGb/
  *
  * @required ngModel This directive requires ngModel be set on the element.
  *
  * @example
  * <input type="text"
  *     id="theText"
  *     name="theText"
  *     ng-model="myvalue"
  *     required
  *     disable-validation="toggleValidation">
  */
define(
    'app/directives/disableValidation',[
        "angular"
    ],
    function(angular) {
        "use strict";

        var app;
        try {
            app = angular.module("App");
        } catch (e) {
            app = angular.module("App", []);
        }

        app.directive("disableValidation", function() {
            return {
                require: "ngModel",
                restrict: "A",
                link: function(scope, element, attrs, ngModelController) {
                    var originalValidators = angular.copy(ngModelController.$validators);
                    Object.keys(originalValidators).forEach(function(key) {
                        ngModelController.$validators[key] = function(v) {

                            // pass the view value twice because some validators take modelValue and viewValue (e.g. required)
                            return scope.$eval(attrs.disableValidation) || originalValidators[key](v, v);
                        };
                    });

                    scope.$watch(attrs.disableValidation, function() {

                        // trigger validation
                        var originalViewValue = ngModelController.$viewValue;
                        scope.$applyAsync(function() {
                            ngModelController.$setViewValue("");
                            ngModelController.$setViewValue(originalViewValue);
                        });
                    });

                }
            };
        });
    }
);

/*
# templates/hulkd/views/configController.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, PAGE */

define(
    'app/views/configController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/validator/datatype-validators",
        "cjt/validator/compare-validators",
        "cjt/validator/length-validators",
        "uiBootstrap",
        "cjt/directives/validationContainerDirective",
        "cjt/directives/validationItemDirective",
        "cjt/decorators/growlDecorator",
        "app/services/HulkdDataSource",
        "app/directives/disableValidation"
    ],
    function(angular, _, LOCALE) {
        "use strict";

        // Retrieve the current application
        var app = angular.module("App");

        var controller = app.controller(
            "configController",
            ["$scope", "HulkdDataSource", "growl", "PAGE",
                function($scope, HulkdDataSource, growl, PAGE) {

                    $scope.username_protection_level = "local";
                    $scope.username_protection_enabled = true;

                    function ignoreKeyDownForSpacebar(event) {

                        // prevent the spacebar from scrolling the window
                        if (event.keyCode === 32) {
                            event.preventDefault();
                        }
                    }

                    $scope.growlProtectionChangeRequiresSave = function() {
                        growl.warning(LOCALE.maketext("You changed the protection level of [asis,cPHulk]. Click Save to implement this change."));
                    };

                    $scope.$watch( function() {
                        return $scope.username_protection_enabled;
                    },
                    function(newValue, oldValue) {
                        if (newValue !== oldValue ) {
                            $scope.growlProtectionChangeRequiresSave();
                        }
                    }
                    );

                    $scope.$watch( function() {
                        return $scope.config_settings.ip_based_protection;
                    },
                    function(newValue, oldValue) {
                        if (newValue !== oldValue ) {
                            $scope.growlProtectionChangeRequiresSave();
                        }
                    }
                    );

                    $scope.align_username_protection_settings = function() {

                        // set up username protection to match the combined
                        // settings
                        if ($scope.config_settings.username_based_protection) {
                            $scope.username_protection_level = "both";
                            $scope.username_protection_enabled = true;
                        } else if ($scope.config_settings.username_based_protection_local_origin) {
                            $scope.username_protection_level = "local";
                            $scope.username_protection_enabled = true;
                        } else {
                            $scope.username_protection_enabled = false;
                        }
                    };

                    $scope.prepare_username_protection_settings_for_save = function() {
                        if (!$scope.username_protection_enabled) {
                            $scope.config_settings.username_based_protection_local_origin = false;
                            $scope.config_settings.username_based_protection = false;
                        } else if ($scope.username_protection_level === "local") {
                            $scope.config_settings.username_based_protection_local_origin = true;
                            $scope.config_settings.username_based_protection = false;
                        } else {
                            $scope.config_settings.username_based_protection = true;
                        }
                    };

                    $scope.handle_protection_keydown = function(event) {
                        ignoreKeyDownForSpacebar(event);
                    };

                    $scope.handle_protection_keyup = function(event, target) {

                    // bind to the spacebar and enter keys
                        if (event.keyCode === 32 || event.keyCode === 13) {
                            event.preventDefault();
                            if ($scope.config_settings[target] !== void 0) {
                                if (target === "username") {
                                    $scope.username_protection_enabled = !$scope.username_protection_enabled;
                                } else {
                                    $scope.config_settings[target] = !$scope.config_settings[target];
                                }
                            }
                        }
                    };

                    $scope.collapse_keydown = function(event) {
                        ignoreKeyDownForSpacebar(event);
                    };


                    $scope.collapse_keyup = function(event, target) {

                    // bind to the spacebar and enter keys
                        if (event.keyCode === 32 || event.keyCode === 13) {
                            event.preventDefault();
                            if ($scope[target] !== void 0) {
                                $scope[target] = !$scope[target];
                            }
                        }
                    };

                    $scope.disableSave = function(form) {
                        return form.$invalid || $scope.loadingPageData;
                    };

                    $scope.save = function(form) {
                        if (!form.$valid) {
                            return;
                        }

                        $scope.loadingPageData = true;

                        $scope.prepare_username_protection_settings_for_save();

                        return HulkdDataSource.save_config_settings($scope.config_settings)
                            .then(
                                function(data) {
                                    growl.success(LOCALE.maketext("The system successfully saved your [asis,cPHulk] configuration settings."));
                                    if (data.restart_ssh) {
                                        growl.warning(LOCALE.maketext("The system disabled the [asis,UseDNS] setting for [asis,SSHD] in order to add IP addresses to the whitelist. You must restart SSH through the [output,url,_1,Restart SSH Server,_2] page to implement the change.", PAGE.security_token + "/scripts/ressshd", { "target": "_blank" }));
                                    } else if (data.warning) {
                                        growl.warning(data.warning);
                                    }
                                }, function(error) {
                                    growl.error(error);
                                }
                            )
                            .finally(function() {
                                $scope.loadingPageData = false;
                            });
                    };

                    $scope.fetch = function() {
                        if (_.isEmpty(HulkdDataSource.config_settings)) {
                            $scope.loadingPageData = true;
                            HulkdDataSource.load_config_settings()
                                .then(
                                    function(data) {
                                        $scope.config_settings = data;
                                        $scope.align_username_protection_settings();
                                    }, function(error) {
                                        growl.error(error);
                                    }
                                )
                                .finally(function() {
                                    $scope.loadingPageData = false;
                                });
                        } else {
                            $scope.config_settings = HulkdDataSource.config_settings;
                            $scope.align_username_protection_settings();
                        }

                    };

                    $scope.bruteInfoCollapse = true;
                    $scope.excessiveBruteInfoCollapse = true;
                    $scope.loadingPageData = false;

                    $scope.fetch();
                }
            ]);

        return controller;
    }
);

/*
# templates/hulkd/directives/countryCodesTableDirective
#                                                      Copyright 2022 cPanel, L.L.C.
#                                                             All rights reserved.
# copyright@cpanel.net                                           http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define: false */

// Then load the application dependencies
define(
    'app/directives/countryCodesTableDirective',[
        "angular",
        "lodash",
        "cjt/core",
        "uiBootstrap",
        "cjt/modules",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/searchDirective",
        "cjt/directives/pageSizeDirective",
        "cjt/filters/startFromFilter",
        "cjt/decorators/paginationDecorator",
        "cjt/directives/quickFiltersDirective",
    ], function(angular, _, CJT) {
        "use strict";

        var app = angular.module("App");

        app.directive("countryCodesTable", ["COUNTRY_CONSTANTS", "$timeout", function(COUNTRY_CONSTANTS, $timeout) {

            var TEMPLATE_PATH = "directives/countryCodesTable.ptt";
            var RELATIVE_PATH = "templates/hulkd/" + TEMPLATE_PATH;

            return {
                templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : TEMPLATE_PATH,
                restrict: "EA",
                scope: {
                    "items": "=",
                    "onChange": "&onChange",
                },
                replace: true,
                controller: ["$scope", "$filter", "$uibModal", function($scope, $filter, $uibModal) {

                    // confirm blacklist modal
                    $scope.modal_instance = null;
                    $scope.country_blacklist_in_progress = false;

                    $scope.confirm_country_blacklisting = function() {
                        $scope.country_blacklist_in_progress = true;

                        $scope.modal_instance = $uibModal.open({
                            templateUrl: "confirm_country_blacklisting.html",
                            scope: $scope,
                        });
                        return true;
                    };

                    $scope.cancel_country_blacklisting = function() {
                        $scope.clear_modal_instance();
                        $scope.deselectAll();
                        $scope.country_blacklisting_in_progress = false;
                    };

                    $scope.continue_country_blacklisting = function(selectedItems) {
                        $scope.clear_modal_instance();
                        $scope.country_blacklist_in_progress = false;
                        $scope.blacklistCountries(selectedItems);
                    };

                    $scope.clear_modal_instance = function() {
                        if ($scope.modal_instance) {
                            $scope.modal_instance.close();
                            $scope.modal_instance = null;
                        }
                    };

                    // initialize filter list
                    var updateTimeout;
                    $scope.filteredList = $scope.items;
                    $scope.COUNTRY_CONSTANTS = COUNTRY_CONSTANTS;
                    var countriesMap = {};

                    $scope.items.forEach(function(item) {
                        countriesMap[item.code] = item;
                        item.searchableCode = "(" + item.code + ")";
                    });

                    $scope.loading = false;

                    $scope.meta = {
                        filterValue: "",
                        sortBy: "name",
                        quickFilterValue: "all",
                    };

                    /**
                 * Initialize the variables required for
                 * row selections in the table.
                */

                    // This updates the selected tracker in the 'Selected' Badge.
                    $scope.selectedItems = [];

                    $scope.toggleSelect = function(itemCode, list) {

                        var idx = list.indexOf(itemCode);
                        if (idx > -1) {
                            list.splice(idx, 1);
                        } else {
                            list.push(itemCode);
                        }
                    };

                    $scope.toggleSelectAll = function() {
                        if ($scope.allSelected()) {
                            $scope.deselectAll();
                        } else {
                            $scope.selectAll();
                        }
                    };

                    $scope.selectAll = function() {
                        $scope.selectedItems = $scope.filteredList.map(function(item) {
                            return item.code;
                        });
                    };

                    $scope.deselectAll = function() {
                        $scope.selectedItems = [];
                    };

                    $scope.allSelected = function() {
                        return $scope.selectedItems.length && $scope.selectedItems.length === $scope.filteredList.length;
                    };

                    $scope.exists = function(item, list) {
                        return list.indexOf(item) > -1;
                    };

                    // update the table on sort
                    $scope.sortList = function(meta) {
                        $scope.fetch();
                    };

                    // update table on search
                    $scope.searchList = function(searchString) {
                        $scope.fetch();
                    };

                    $scope.getCountriesFromCodes = function(countryCodes) {
                        return countryCodes.map(function(countryCode) {
                            return countriesMap[countryCode];
                        });
                    };

                    $scope.whitelistCountries = function(countries) {
                        $scope.getCountriesFromCodes(countries).forEach(function(country) {
                            country.status = COUNTRY_CONSTANTS.WHITELISTED;
                        });
                        $scope.countriesUpdated();
                    };

                    $scope.blacklistCountries = function(countries) {
                        $scope.getCountriesFromCodes(countries).forEach(function(country) {
                            country.status = COUNTRY_CONSTANTS.BLACKLISTED;
                        });
                        $scope.countriesUpdated();
                    };

                    $scope.unlistCountries = function(countries) {
                        $scope.getCountriesFromCodes(countries).forEach(function(country) {
                            country.status = COUNTRY_CONSTANTS.UNLISTED;
                        });
                        $scope.countriesUpdated();
                    };

                    $scope.countriesUpdated = function() {
                        if ($scope.onChange) {

                            if (updateTimeout) {
                                $timeout.cancel(updateTimeout);
                                updateTimeout = false;
                            }

                            updateTimeout = $timeout(function() {
                                $scope.countriesUpdating = true;
                                var whitelistedDomains = [];
                                var blacklistedDomains = [];
                                $scope.items.forEach(function(item) {
                                    if (item.status === COUNTRY_CONSTANTS.WHITELISTED) {
                                        whitelistedDomains.push(item.code);
                                    } else if (item.status === COUNTRY_CONSTANTS.BLACKLISTED) {
                                        blacklistedDomains.push(item.code);
                                    }
                                });
                                $scope.onChange({ whitelist: whitelistedDomains, blacklist: blacklistedDomains }).finally(function() {
                                    $scope.countriesUpdating = false;
                                });
                            }, 250);

                        }
                    };

                    // have your filters all in one place - easy to use
                    var filters = {
                        filter: $filter("filter"),
                        orderBy: $filter("orderBy"),
                    };

                    $scope.quickFilterUpdated = function() {
                        $scope.deselectAll();
                        $scope.fetch();
                    };

                    // update table
                    $scope.fetch = function() {
                        var filteredList = [];

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

                        if ($scope.meta.quickFilterValue !== "all") {
                            filteredList = filters.filter(filteredList, { status: $scope.meta.quickFilterValue }, false);
                        }

                        // sort the filtered list
                        if ($scope.meta.sortDirection !== "" && $scope.meta.sortBy !== "") {
                            filteredList = filters.orderBy(filteredList, $scope.meta.sortBy, $scope.meta.sortDirection === "asc" ? true : false);
                        }

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

                        $scope.filteredList = filteredList;

                        return filteredList;
                    };

                    // first page load
                    $scope.fetch();
                }],
            };
        }]);
    });

/*
# templates/hulkd/views/historyController.js      Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define: false */

define(
    'app/views/countriesController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "app/directives/countryCodesTableDirective",
        "cjt/decorators/growlDecorator",
        "app/services/HulkdDataSource"
    ],
    function(angular, _, LOCALE) {

        // Retrieve the current application
        var app = angular.module("App");

        var controller = app.controller(
            "countriesController",
            ["$scope","growl","HulkdDataSource","COUNTRY_CONSTANTS","COUNTRY_CODES","XLISTED_COUNTRIES",
            function($scope, $growl, $service, COUNTRY_CONSTANTS, COUNTRY_CODES, XLISTED_COUNTRIES) {

                function _parseCountries(countryCodes, xlistedCountries){
                    return countryCodes.map(function(countryCode){
                        countryCode.status = xlistedCountries[countryCode.code] || COUNTRY_CONSTANTS.UNLISTED;
                        return countryCode;
                    });
                }

                $scope.countries = _parseCountries(COUNTRY_CODES, XLISTED_COUNTRIES);

                var startingGrowl, successGrowl;

                $scope.countriesUpdated = function(whitelist, blacklist){
                    // Using growl for consistency, but this will have to be refactored later
                    if(successGrowl){
                        successGrowl.destroy();
                    }
                    startingGrowl = $growl.info(LOCALE.maketext("Updating the country whitelist and blacklist …"));
                    return $service.set_cphulk_config_keys({
                        "country_whitelist":whitelist.sort().join(","),
                        "country_blacklist":blacklist.sort().join(",")
                    }).then(function(xlistedCountries){
                        XLISTED_COUNTRIES = xlistedCountries;
                        $scope.countries = _parseCountries(COUNTRY_CODES, xlistedCountries);
                        startingGrowl.destroy();
                        successGrowl = $growl.success(LOCALE.maketext("Country whitelist and blacklist updated."));
                    });
                };

            }
        ]);

        return controller;
    }
);



/*
# cpanel - whostmgr/docroot/templates/hulkd/utils/download.js
#                                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/
define(
    'app/utils/download',[],function() {

        "use strict";

        return {

            /**
             * Create a download name.
             *
             * @param {string} prefix
             * @returns {string}
             */
            getDownloadName: function(prefix) {
                return prefix + ".txt";
            },

            /**
             * Convert the raw data into a download url data blob.
             *
             * @param {string} data - the formatted download data.
             * @returns {string} - The data formatted for a download url.
             */
            getTextDownloadUrl: function(data) {
                var blob = new Blob([data], { type: "plain/text" });
                return window.URL.createObjectURL(blob);
            },

            /**
             * Clean up the allocated url.
             *
             * @param {string} url - the url previously created with createObjetURL.
             */
            cleanupDownloadUrl: function(url) {
                if (url) {
                    window.URL.revokeObjectURL(url);
                }
            },

            /**
             * @typedef IpRecord
             * @property {string} ip - ip address or range.
             * @property {string?} comment - comment associated with the ip or range.
             */

            /**
             * Convert the ip list into a serialized format.
             *
             * @param {IpRecord[]} list
             * @returns {string}
             */
            formatList: function(list) {
                if (list && list.length) {
                    return list.map(function(item) {
                        return item.ip + (item.comment ? " # " + item.comment : "");
                    }).join("\n") + "\n";
                }
                return "";
            },
        };

    }
);

/*
# cpanel - whostmgr/docroot/templates/hulkd/views/hulkdWhitelistController.js
#                                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* eslint camelcase: 0, no-prototype-builtins: 0 */

define(
    'app/views/hulkdWhitelistController',[
        "angular",
        "jquery",
        "lodash",
        "cjt/util/locale",
        "app/utils/download",
        "uiBootstrap",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/pageSizeDirective",
        "cjt/decorators/paginationDecorator",
        "cjt/decorators/growlDecorator",
        "cjt/filters/startFromFilter",
        "app/services/HulkdDataSource",
    ],
    function(angular, $, _, LOCALE, Download) {

        "use strict";

        // Retrieve the current application
        var app = angular.module("App");
        app.config([ "$compileProvider",
            function($compileProvider) {
                $compileProvider.aHrefSanitizationWhitelist(/^blob:https:/);
            },
        ]);

        var controller = app.controller(
            "hulkdWhitelistController",
            ["$rootScope", "$scope", "$filter", "$routeParams", "$uibModal", "HulkdDataSource", "growl", "PAGE", "growlMessages", "$timeout",
                function($rootScope, $scope, $filter, $routeParams, $uibModal, HulkdDataSource, growl, PAGE, growlMessages, $timeout) {

                    $scope.whitelist_reverse = false;

                    $scope.whitelist = [];
                    $scope.whitelist_comments = {};

                    $scope.adding_batch_to_whitelist = false;

                    $scope.new_whitelist_records = "";

                    $scope.ip_being_edited = false;
                    $scope.current_ip = null;
                    $scope.current_comment = "";
                    $scope.updating_comment = false;

                    $scope.modal_instance = null;

                    $scope.loading = false;

                    $scope.downloadAllLink = "";
                    $scope.downloadSelectionLink = "";

                    $scope.meta = {
                        sortDirection: "asc",
                        sortBy: "white_ip",
                        sortType: "",
                        sortReverse: false,
                        filter: "",
                        maxPages: 0,
                        totalItems: $scope.whitelist.length || 0,
                        currentPage: 1,
                        pageNumberStart: 0,
                        pageNumberEnd: 0,
                        pageSize: 20,
                        pageSizes: [20, 50, 100],
                    };

                    $scope.LOCALE = LOCALE;

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

                    $scope.delete_in_progress = false;
                    $scope.ips_to_delete = [];

                    // Handle auto-adding an ip from a query param or POST
                    if (($routeParams["ip"] && $routeParams["ip"].length > 0) ||
                        PAGE.ipToAdd !== null) {
                        var ip;
                        var comment = "";

                        if ($routeParams["ip"] && $routeParams["ip"].length > 0) {

                            // added via a query param
                            ip = $routeParams["ip"];
                        } else if (PAGE.ipToAdd !== null) {

                            // added via a POST and stuffed into PAGE
                            ip = PAGE.ipToAdd;
                        }

                        // clear the ip so we don't add it again
                        PAGE.ipToAdd = null;

                        if (ip !== void 0) {
                            $scope._add_to_whitelist([ { ip: ip, comment: comment } ]);
                        }
                    }


                    $scope.growl_whitelist_warning = function(missing_ip) {

                        // create a new growl to be displayed.
                        var message_cache = LOCALE.maketext("Your current IP address “[_1]” is not on the whitelist.", _.escape(missing_ip));
                        $rootScope.whitelist_warning_message = growl.error(message_cache,
                            {
                                variables: {
                                    buttonLabel: LOCALE.maketext("Add to Whitelist"),
                                    showAction: true,
                                    action: function() {
                                        $rootScope.one_click_add_to_whitelist(missing_ip)
                                            .then(function() {
                                                $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                            });
                                    },
                                },
                                onclose: function() {
                                    $rootScope.whitelist_warning_message = null;
                                },
                            }
                        );
                    };

                    $scope.edit_whitelist_ip = function(whitelist_ip) {
                        $scope.current_ip = whitelist_ip;
                        $scope.current_comment = $scope.whitelist_comments.hasOwnProperty(whitelist_ip) ? $scope.whitelist_comments[whitelist_ip] : "";
                        $scope.ip_being_edited = true;
                        var whitelist_comment_field = $("#whitelist_current_comment");
                        var wait_id = setInterval( function() {
                            if (whitelist_comment_field.is(":visible")) {
                                whitelist_comment_field.focus();
                                whitelist_comment_field.select();
                                clearInterval(wait_id);
                            }
                        }, 250);
                    };

                    $scope.cancel_whitelist_editing = function() {
                        $scope.current_ip = null;
                        $scope.current_comment = "";
                        $scope.ip_being_edited = false;
                        $scope.focus_on_whitelist();
                    };

                    $scope.delete_tooltip = function(ip_address) {
                        return LOCALE.maketext("Click to delete “[_1]” from the whitelist.", ip_address);
                    };

                    $scope.edit_tooltip = function(ip_address) {
                        return LOCALE.maketext("Click to edit the comment for “[_1]”.", ip_address);
                    };

                    $scope.update_whitelist_comment = function() {
                        if ($scope.updating_comment) {
                            return;
                        }

                        $scope.updating_comment = true;
                        HulkdDataSource.add_to_whitelist([ { ip: $scope.current_ip, comment: $scope.current_comment } ])
                            .then( function(results) {
                                $scope.whitelist_comments = HulkdDataSource.whitelist_comments;

                                // Growl out each success from the batch.
                                results.updated.forEach(function(ip) {
                                    growl.success(LOCALE.maketext("You have successfully updated the comment for “[_1]”.", _.escape(ip)));
                                });

                                // Report the failures from the batch.
                                var rejectedMessages = [];
                                Object.keys(results.rejected).forEach(function(ip) {
                                    rejectedMessages.push(_.escape(ip) + ": " + _.escape(results.rejected[ip]));
                                });

                                if (rejectedMessages.length > 0) {
                                    var accumulatedMessages = LOCALE.maketext("Some records failed to update.") + "<br>";
                                    accumulatedMessages += rejectedMessages.join("<br>");
                                    growl.error(accumulatedMessages);
                                }
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updating_comment = false;
                                $scope.cancel_whitelist_editing();
                                $scope.focus_on_whitelist();
                            });
                    };

                    var ipV6 = /^(([\da-fA-F]{1,4}:){4})(([\da-fA-F]{1,4}:){3})([\da-fA-F]{1,4})$/;
                    var ipV4Range = /^((\d{1,3}.){3}\d{1,3})-((\d{1,3}.){3}\d{1,3})$/;
                    var ipRangeTest = /-/;
                    var ipV6Test = /:/;

                    /**
                     * Separates long ipv4 and ipv6 addresses with br tags.
                     * Also, supports separating ipv4 and ipv6 address ranges.
                     *
                     * @param {string} ip - an ip address
                     * @todo Implement this as an Angular Filter in a separate file
                     */
                    $scope.splitLongIp = function(ip) {

                        // ipv6?
                        if (ipV6Test.test(ip)) {

                            // is this a range?
                            if (ipRangeTest.test(ip)) {

                                // format the ipv6 addresses in range format
                                var ipv6Addresses = ip.split(ipRangeTest);
                                var ipv6AddressRange = "";

                                // get the first part of the range
                                var match = ipV6.exec(ipv6Addresses[0]);
                                if (match) {
                                    ipv6AddressRange += match[1] + "<br>" + match[3] + match[5];
                                }

                                // add the range separator
                                ipv6AddressRange += "-<br>";

                                // get the second part of the range
                                match = ipV6.exec(ipv6Addresses[1]);
                                if (match) {
                                    ipv6AddressRange += match[1] + "<br>" + match[3] + match[5];
                                }

                                // if all we have is -<br>, then forget it
                                if (ipv6AddressRange.length > 5) {
                                    return ipv6AddressRange;
                                }
                            } else {

                                // format the ipv6 address
                                var v6match = ipV6.exec(ip);
                                if (v6match) {
                                    return v6match[1] + "<br>" + v6match[3] + v6match[5];
                                }
                            }
                        } else {

                            // format the ipv4 range
                            var v4rangeMatch = ipV4Range.exec(ip);
                            if (v4rangeMatch) {
                                return v4rangeMatch[1] + "-<br>" + v4rangeMatch[3];
                            }
                        }

                        // could not format it, just return it
                        return ip;
                    };

                    $scope.$watch(function() {
                        return HulkdDataSource.enabled;
                    }, function() {
                        $scope.load_list();
                    });

                    $scope.$watch(function() {
                        return $rootScope.ip_added_with_one_click === true;
                    }, function() {
                        $scope.applyFilters();
                        $rootScope.ip_added_with_one_click = false;
                    });

                    $scope.$watchGroup([ "whitelist.length", "meta.filteredList.length" ], function() {
                        if ($scope.whitelist.length === 0 || $scope.meta.filteredList.length === 0) {
                            $("#whitelist_select_all_checkbox").prop("checked", false);
                        }
                    });

                    $scope.selectPage = function(page) {
                        $("#whitelist_select_all_checkbox").prop("checked", false);

                        // set the page if requested
                        if (page && angular.isNumber(page)) {
                            $scope.meta.currentPage = page;
                        }

                        $scope.load_list();
                    };

                    $scope.selectPageSize = function() {
                        return $scope.load_list({ reset_focus: false });
                    };

                    /**
                     * Filter the list by the `meta.filter`.
                     */
                    $scope.filterList = function() {
                        $scope.meta.currentPage = 1;
                        $scope.load_list({ reset_focus: false });
                    };

                    /**
                     * Clear the filter if it is set.
                     */
                    $scope.toggleFilter = function() {
                        $scope.meta.filter = "";
                        $scope.load_list({ reset_focus: false });
                    };

                    $scope.sortList = function(meta) {
                        $scope.meta.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        $scope.applyFilters();
                    };

                    $scope.orderByComments = function(comment_object, ip_list) {
                        var comments_as_pairs = _.toPairs(comment_object);
                        var ips_as_pairs = [];

                        // get the IPs that have no comments
                        for (var i = 0; i < ip_list.length; i++) {
                            if (!_.has(comment_object, ip_list[i] )) {
                                var one_entry = [ip_list[i], "" ];
                                ips_as_pairs.push(one_entry);
                            }
                        }

                        // sort the IPs that have no comments
                        var sorted_pairs = _.sortBy(ips_as_pairs, function(pair) {
                            return $scope.ip_padder(pair[0]);
                        });

                        // sort the comments first by comment, then by IP address
                        comments_as_pairs.sort(compareComments);

                        // create an array of the IPs from the sorted comments
                        var just_ips_comments = _.map(comments_as_pairs, function(pair) {
                            return pair[0];
                        });

                        // create an array of the sorted IPs with no comments
                        var just_ips = _.map(sorted_pairs, function(pair) {
                            return pair[0];
                        });

                        // put the IPs with comments and the IPs without comments together
                        var stuck_together = just_ips_comments.concat(just_ips);

                        if ($scope.meta.sortDirection === "desc") {
                            return stuck_together.reverse();
                        }

                        return stuck_together;
                    };

                    /**
                     * Apply the sort, filter and pagination to the whitelist data.
                     *
                     * @returns {string[]} List of ips that pass the filters.
                     */
                    $scope.applyFilters = function() {
                        var filteredList = [];
                        var start, limit;

                        filteredList = $scope.whitelist;

                        // Sort
                        if ($scope.meta.sortDirection !== "" && $scope.meta.sortBy !== "") {
                            if ($scope.meta.sortBy === "white_ip") {
                                filteredList = filters.orderBy(filteredList, $scope.ip_padder, $scope.meta.sortReverse);
                            } else {
                                filteredList = $scope.orderByComments($scope.whitelist_comments, $scope.whitelist);
                            }
                        }

                        // Totals
                        $scope.meta.totalItems = $scope.whitelist.length;

                        // Filter content
                        var expected = $scope.meta.filter.toLowerCase();
                        if (expected) {
                            filteredList = filters.filter(filteredList, function(actual) {
                                return actual.indexOf(expected) !== -1 ||
                                       ($scope.whitelist_comments[actual] && $scope.whitelist_comments[actual].toLowerCase().indexOf(expected) !== -1);
                            });
                        }

                        // Track the filtered size separatly
                        $scope.meta.filteredItems = filteredList.length;

                        // Pagination
                        start = ($scope.meta.currentPage - 1) * $scope.meta.pageSize;
                        limit = $scope.meta.pageSize;
                        filteredList = filters.limitTo(filters.startFrom(filteredList, start), limit);

                        $scope.meta.pageNumberStart = start + 1;
                        $scope.meta.pageNumberEnd = ($scope.meta.currentPage * $scope.meta.pageSize);

                        if ($scope.meta.totalItems === 0) {
                            $scope.meta.pageNumberStart = 0;
                        }

                        if ($scope.meta.pageNumberEnd > $scope.meta.totalItems) {
                            $scope.meta.pageNumberEnd = $scope.meta.totalItems;
                        }

                        $scope.meta.filteredList = filteredList;

                        return filteredList;
                    };

                    $scope.load_list = function(options) {
                        if (HulkdDataSource.enabled && !$scope.loading) {

                            $scope.loading = true;
                            var reset_focus = typeof options !== "undefined" && options.hasOwnProperty("reset_focus") ? options.reset_focus : true;

                            if (HulkdDataSource.whitelist_is_cached) {
                                $scope.whitelist = HulkdDataSource.whitelist;
                                $scope.whitelist_comments = HulkdDataSource.whitelist_comments;
                                $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                $scope.applyFilters();
                                if (reset_focus) {
                                    $scope.focus_on_whitelist();
                                }
                                $scope.loading = false;
                            } else {
                                $scope.meta.filteredList = [];
                                return HulkdDataSource.load_list("white")
                                    .then(function(results) {
                                        $scope.whitelist = HulkdDataSource.whitelist;
                                        $scope.whitelist_comments = HulkdDataSource.whitelist_comments;
                                        $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                        $scope.applyFilters();

                                        // if the requester ip is not whitelisted and the growl does not exist or is not displayed, then display it
                                        if (results.hasOwnProperty("requester_ip_is_whitelisted") && results.requester_ip_is_whitelisted <= 0) {
                                            if (results.hasOwnProperty("requester_ip") && $rootScope.whitelist_warning_message === null) {
                                                $scope.growl_whitelist_warning(results.requester_ip);
                                            }
                                        }

                                        if (results.restart_ssh) {
                                            growl.warning(LOCALE.maketext("The system disabled the [asis,UseDNS] setting for [asis,SSHD] in order to add IP addresses to the whitelist. You must restart SSH through the [output,url,_1,Restart SSH Server,_2] page to implement the change.", PAGE.security_token + "/scripts/ressshd", { "target": "_blank" }));
                                        } else if (results.warning_ssh) {
                                            growl.warning(results.warning_ssh);
                                        }
                                    }, function(error) {
                                        growl.error(error);
                                    })
                                    .finally(function() {
                                        if (reset_focus) {
                                            $scope.focus_on_whitelist();
                                        }
                                        $scope.loading = false;
                                    });
                            }
                        }
                        return null;
                    };

                    $scope.force_load_whitelist = function() {
                        HulkdDataSource.whitelist_is_cached = false;
                        $scope.whitelist = [];
                        $scope.whitelist_comments = {};
                        $scope.meta.filteredList = [];
                        return $scope.load_list();
                    };

                    $scope.delete_confirmation_message = function() {
                        if ($scope.ips_to_delete.length === 1) {
                            return LOCALE.maketext("Do you want to permanently delete “[_1]” from the whitelist?", $scope.ips_to_delete[0]);
                        } else {
                            return LOCALE.maketext("Do you want to permanently delete [quant,_1,record,records] from the whitelist?", $scope.ips_to_delete.length);
                        }
                    };

                    $scope.itemsAreChecked = function() {
                        return $(".whitelist_select_item").filter(":checked").length > 0;
                    };

                    $scope.check_whitelist_selection = function() {
                        if ($(".whitelist_select_item").filter(":not(:checked)").length === 0) {
                            $("#whitelist_select_all_checkbox").prop("checked", true);
                        } else {
                            $("#whitelist_select_all_checkbox").prop("checked", false);
                        }
                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                    };

                    /**
                     * Get the list of ips selected in the UI.
                     *
                     * @returns <string[]> List if ips selected.
                     */
                    $scope.getSelection = function()  {
                        var selected_items = [],
                            $selected_dom_nodes = $(".whitelist_select_item:checked");

                        if ($selected_dom_nodes.length === 0) {
                            return [];
                        }

                        $selected_dom_nodes.each( function() {
                            selected_items.push($(this).data("ip"));
                        });

                        return selected_items;
                    };

                    $scope.confirm_whitelist_deletion = function(ip_to_delete) {
                        if ($scope.whitelist.length === 0) {
                            return false;
                        }
                        $scope.delete_in_progress = true;
                        if (ip_to_delete !== undefined) {
                            $scope.ips_to_delete = [ip_to_delete];
                            $scope.is_single_deletion = true;
                        } else {
                            var selected_items = $scope.getSelection();
                            if (selected_items.length === 0) {
                                return false;
                            }
                            $scope.ips_to_delete = selected_items;
                            $scope.is_single_deletion = false;
                        }

                        $scope.modal_instance = $uibModal.open({
                            templateUrl: "confirm_whitelist_deletion.html",
                            scope: $scope,
                        });

                        return true;
                    };

                    $scope.clear_modal_instance = function() {
                        if ($scope.modal_instance) {
                            $scope.modal_instance.close();
                            $scope.modal_instance = null;
                        }
                    };

                    $scope.cancel_deletion = function() {
                        $scope.delete_in_progress = false;
                        $scope.ips_to_delete = [];
                        $scope.clear_modal_instance();
                        $scope.focus_on_whitelist();
                    };

                    $scope.delete_whitelist_ips = function(is_single_deletion) {
                        $scope.clear_modal_instance();
                        HulkdDataSource.remove_from_whitelist($scope.ips_to_delete)
                            .then( function(results) {
                                $scope.whitelist = HulkdDataSource.whitelist;
                                $scope.whitelist_comments = HulkdDataSource.whitelist_comments;
                                $scope.applyFilters();
                                $scope.focus_on_whitelist();

                                if (results.removed.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully deleted “[_1]” from the whitelist.", _.escape(results.removed[0])));
                                } else {
                                    growl.success(LOCALE.maketext("You have successfully deleted [quant,_1,record,records] from the whitelist.", results.removed.length));
                                }

                                if ( results.hasOwnProperty("requester_ip_is_whitelisted") && results.requester_ip_is_whitelisted <= 0 && results.hasOwnProperty("requester_ip") ) {
                                    $scope.growl_whitelist_warning(results.requester_ip);
                                }

                                if (results.not_removed.keys && results.not_removed.keys.length > 0) {
                                    growl.warning(LOCALE.maketext("The system was unable to delete [quant,_1,record,records] from the whitelist.", results.not_removed.keys.length));
                                }
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally( function() {
                                $scope.delete_in_progress = false;
                                $scope.ips_to_delete = [];
                                if (!is_single_deletion) {
                                    $scope.deselect_all_whitelist();
                                }

                                // Since this is using JQuery/DOM, we have to wait another tick for the UI to update
                                // before we try to get the selection.
                                $timeout(function() {
                                    $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                    $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                                });
                            });
                    };

                    $scope.confirm_delete_all = function() {
                        if ($scope.whitelist.length === 0) {
                            return false;
                        }
                        $scope.delete_in_progress = true;

                        $scope.modal_instance = $uibModal.open({
                            templateUrl: "confirm_whitelist_delete_all.html",
                            scope: $scope,
                        });

                        return true;
                    };

                    $scope.cancel_delete_all = function() {
                        $scope.delete_in_progress = false;
                        $scope.clear_modal_instance();
                        $scope.focus_on_whitelist();
                    };

                    $scope.delete_all = function() {
                        $scope.clear_modal_instance();
                        HulkdDataSource.remove_all_from_whitelist()
                            .then( function(results) {
                                $scope.whitelist = HulkdDataSource.whitelist;
                                $scope.whitelist_comments = HulkdDataSource.whitelist_comments;
                                $scope.applyFilters();
                                $scope.focus_on_whitelist();
                                if (results.not_removed.keys && results.not_removed.keys.length > 0) {
                                    growl.success(LOCALE.maketext("You have successfully deleted [quant,_1,record,records] from the whitelist.", results.removed.keys.length));
                                    growl.warning(LOCALE.maketext("The system was unable to delete [quant,_1,record,records] from the whitelist.", results.not_removed.keys.length));
                                } else {
                                    growl.success(LOCALE.maketext("You have deleted all records from the whitelist."));
                                }

                                if ( results.hasOwnProperty("requester_ip_is_whitelisted") && results.requester_ip_is_whitelisted <= 0 && results.hasOwnProperty("requester_ip") ) {
                                    $scope.growl_whitelist_warning(results.requester_ip);
                                }
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally( function() {
                                $scope.delete_in_progress = false;
                                $scope.deselect_all_whitelist();

                                // Since this is using JQuery/DOM, we have to wait another tick for the UI to update
                                // before we try to get the selection.
                                $timeout(function() {
                                    $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                    $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                                });
                            });
                    };

                    $scope.select_all_whitelist = function() {
                        if ($scope.whitelist.length === 0) {
                            return false;
                        }
                        $(".whitelist_select_item").prop("checked", true);
                        $("#whitelist_select_all_checkbox").prop("checked", true);

                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();

                        return true;
                    };

                    $scope.deselect_all_whitelist = function() {
                        if ($scope.whitelist.length === 0) {
                            return false;
                        }
                        $(".whitelist_select_item").prop("checked", false);
                        $("#whitelist_select_all_checkbox").prop("checked", false);

                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();

                        return true;
                    };

                    $scope.toggle_whitelist_selection = function() {
                        if ($("#whitelist_select_all_checkbox").prop("checked") === true) {
                            $scope.select_all_whitelist();
                        } else {
                            $scope.deselect_all_whitelist();
                        }
                    };

                    $scope.focus_on_whitelist = function() {
                        var whitelist_batch_field = $("#whitelist_batch_add");
                        var wait_id = setInterval( function() {
                            if (whitelist_batch_field.is(":visible")) {
                                whitelist_batch_field.focus();
                                whitelist_batch_field.select();
                                clearInterval(wait_id);
                            }
                        }, 250);
                    };

                    /**
                     *
                     * @typedef Record
                     * @property {string} ip
                     * @property {string?} comment
                     */

                    /**
                     * Parse the batch of records.
                     *
                     * @param {string} text
                     * @returns {Record[]}
                     */
                    function parseBatch(text) {
                        var lines = text.split("\n");
                        var records = [];

                        for (var i = 0; i < lines.length; i++) {
                            var line = lines[i];
                            if (line && line.length > 0) {
                                var parts = line.split("#");
                                var ip = parts.shift().trim();
                                var comment = parts.join("#").trim();
                                records.push({
                                    ip: ip,
                                    comment: comment,
                                });
                            }
                        }
                        return records;
                    }

                    /**
                     * Add a batch of records to the whitelist.
                     *
                     * @async
                     * @returns
                     */
                    $scope.add_to_whitelist = function() {
                        if (!$scope.new_whitelist_records || $scope.adding_batch_to_whitelist) {
                            return;
                        }

                        var records = parseBatch($scope.new_whitelist_records);
                        return $scope._add_to_whitelist(records);
                    };

                    /**
                     * Add a batch of whitelist records.
                     *
                     * @private
                     * @param {Record[]} batch
                     * @returns
                     */
                    $scope._add_to_whitelist = function(batch) {
                        $scope.adding_batch_to_whitelist = true;
                        return HulkdDataSource.add_to_whitelist(batch)
                            .then( function(results) {
                                $scope.whitelist = HulkdDataSource.whitelist;
                                $scope.whitelist_comments = HulkdDataSource.whitelist_comments;
                                $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                $scope.applyFilters();

                                if (results.added.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully added “[_1]” to the whitelist.", _.escape(results.added[0])));
                                } else if (results.added.length > 1) {
                                    growl.success(LOCALE.maketext("You have successfully added [quant,_1,IP address,IP addresses] to the whitelist.", results.added.length));
                                }

                                if (results.updated.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully updated the comment for “[_1]”.", _.escape(results.updated[0])));
                                } else if (results.updated.length > 1) {
                                    growl.success(LOCALE.maketext("You have successfully updated the [numerate,_1,comment,comments] for [quant,_1,IP address,IP addresses].", results.updated.length));
                                }

                                // if requester ip is marked as being whitelisted in the last call, but the growl warning
                                // is still displayed then hide the growl warning
                                if (results.hasOwnProperty("requester_ip_is_whitelisted") && results.requester_ip_is_whitelisted > 0 && $rootScope.whitelist_warning_message !== null) {
                                    $rootScope.whitelist_warning_message.ttl = 0;
                                    $rootScope.whitelist_warning_message.promises = [];
                                    $rootScope.whitelist_warning_message.promises.push($timeout(angular.bind(growlMessages, function() {
                                        growlMessages.deleteMessage($rootScope.whitelist_warning_message);
                                        $rootScope.whitelist_warning_message = null;
                                    }), 200));
                                }

                                var rejectedIps = Object.keys(results.rejected);
                                if (rejectedIps.length > 0) {
                                    var accumulatedMessages = LOCALE.maketext("Some IP addresses were not added to the whitelist.");
                                    accumulatedMessages += "<br>\n";

                                    // Put the rejected ips/comments back in the list.
                                    $scope.new_whitelist_records = rejectedIps.map(function(ip) {
                                        var record = batch.find(function(record) {
                                            return record.ip === ip;
                                        });
                                        if (record && record.comment) {
                                            return ip + " # " + record.comment + "\n";
                                        }
                                        return ip + "\n";
                                    }).join("");

                                    // Report the problems in the growl
                                    accumulatedMessages += "<ul>\n";
                                    rejectedIps.forEach(function(ip) {
                                        if (results.rejected[ip]) {
                                            accumulatedMessages += "<li>" + _.escape(results.rejected[ip]) + "</li>\n";
                                        }
                                    });
                                    accumulatedMessages += "</ul>\n";
                                    growl.error(accumulatedMessages);

                                } else {
                                    $scope.new_whitelist_records = "";
                                }
                            }, function(error_details) {
                                var error = error_details.main_message;

                                // Format the individual partial errors.
                                var secondary_count = error_details.secondary_messages.length;
                                if (secondary_count > 0) {
                                    error += "<ul>\n";
                                }

                                error_details.secondary_messages.forEach(function(message) {
                                    error += "<li>" + _.escape(message) + "</li>\n";
                                });

                                if (secondary_count > 0) {
                                    error += "</ul>\n";
                                }

                                growl.error(error);
                            })
                            .finally( function() {
                                $scope.adding_batch_to_whitelist = false;
                                $scope.focus_on_whitelist();
                            });
                    };

                    // TODO: Make this a utility system: ip-comparison
                    $scope.ip_padder = function(unpadded) {
                        var padded_ip = "";
                        if (unpadded) {
                            var split_ip = unpadded.split(".");
                            for (var i = 0; i < split_ip.length; i++) {
                                var this_section = split_ip[i];
                                while ( this_section.length < 3) {
                                    this_section = "0" + this_section;
                                }
                                padded_ip += this_section;
                            }
                        }
                        return padded_ip;
                    };

                    function compareComments(a, b) {

                        // sort by comment
                        if (a[1].toLowerCase() < b[1].toLowerCase()) {
                            return -1;
                        }
                        if (a[1].toLowerCase() > b[1].toLowerCase()) {
                            return 1;
                        }

                        // we have a duplicate comment, so sort by IP address
                        if ($scope.ip_padder(a[0]) < $scope.ip_padder(b[0])) {
                            return -1;
                        }
                        if ($scope.ip_padder(a[0]) > $scope.ip_padder(b[0])) {
                            return 1;
                        }

                        // we have a duplicate comment and IP
                        return 0;
                    }

                    /**
                     * Generate the download name.
                     *
                     * @returns {string} - the name of the download.
                     */
                    $scope.downloadName = function() {
                        return Download.getDownloadName("whitelist");
                    };

                    /**
                     * @typedef IpRecord
                     * @property {string} ip - ip address or range.
                     * @property {string?} comment - comment associated with the ip or range.
                     */

                    /**
                     * Package the ips and comments into a records structure
                     *
                     * @param {string[]} ips
                     * @param {Dictionary<string,string>} comments
                     * @returns {IpRecord[]}
                     */
                    function getRecords(ips, comments) {
                        var list = [];
                        ips.forEach(function(ip) {
                            var comment = comments[ip];
                            list.push({ ip: ip, comment: comment });
                        });
                        return list;
                    }

                    /**
                     * Generate a data blob url that contains all the whitelist ips.
                     *
                     * @returns {string} - data url.
                     */
                    $scope.generateDownloadAllLink = function() {

                        if ($scope.downloadAllLink) {

                            // Clean up the previous url
                            Download.cleanupDownloadUrl($scope.downloadAllLink);
                            $scope.downloadAllLink = null;
                        }

                        var ips = $scope.whitelist;
                        if (!ips || ips.length === 0) {
                            return "";
                        }

                        var list = getRecords(ips, $scope.whitelist_comments);
                        return Download.getTextDownloadUrl(Download.formatList(list));
                    };

                    /**
                     * Generate a data blob url that contains the selected whitelist ips.
                     *
                     * @returns {string} - data url.
                     */
                    $scope.generateDownloadSelectionLink = function() {
                        if ($scope.downloadSelectionLink) {

                            // Clean up the previous url
                            Download.cleanupDownloadUrl($scope.downloadSelectionLink);
                            $scope.downloadSelectionLink = null;
                        }

                        if (!$scope.whitelist || $scope.whitelist.length === 0) {
                            return "";
                        }

                        var selection = $scope.getSelection();
                        if (!selection || selection.length === 0) {
                            return "";
                        }

                        var list = getRecords(selection, $scope.whitelist_comments);

                        return Download.getTextDownloadUrl(Download.formatList(list));
                    };

                    $scope.focus_on_whitelist();
                },
            ]);

        return controller;
    }
);

/*
# cpanel - whostmgr/docroot/templates/hulkd/views/hulkdBlacklistController.js
#                                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* eslint camelcase: 0, no-prototype-builtins: 0 */

define(
    'app/views/hulkdBlacklistController',[
        "angular",
        "jquery",
        "lodash",
        "cjt/util/locale",
        "app/utils/download",
        "uiBootstrap",
        "cjt/directives/toggleSortDirective",
        "cjt/decorators/paginationDecorator",
        "cjt/decorators/growlDecorator",
        "cjt/filters/startFromFilter",
        "app/services/HulkdDataSource",
    ],
    function(angular, $, _, LOCALE, Download) {

        "use strict";

        // Retrieve the current application
        var app = angular.module("App");
        app.config([ "$compileProvider",
            function($compileProvider) {
                $compileProvider.aHrefSanitizationWhitelist(/^blob:https:/);
            },
        ]);

        var controller = app.controller(
            "hulkdBlacklistController",
            ["$scope", "$filter", "$routeParams", "$uibModal", "HulkdDataSource", "growl", "PAGE", "$timeout",
                function($scope, $filter, $routeParams, $uibModal, HulkdDataSource, growl, PAGE, $timeout) {

                    $scope.blacklist_reverse = false;

                    $scope.blacklist = [];
                    $scope.blacklist_comments = {};

                    $scope.adding_batch_to_blacklist = false;

                    $scope.new_blacklist_records = "";

                    $scope.ip_being_edited = false;
                    $scope.current_ip = null;
                    $scope.current_comment = "";
                    $scope.updating_comment = false;

                    $scope.modal_instance = null;

                    $scope.loading = false;

                    $scope.downloadAllLink = "";
                    $scope.downloadSelectionLink = "";

                    $scope.meta = {
                        sortDirection: "asc",
                        sortBy: "black_ip",
                        sortType: "",
                        sortReverse: false,
                        filter: "",
                        maxPages: 0,
                        totalItems: $scope.blacklist.length || 0,
                        currentPage: 1,
                        pageNumberStart: 0,
                        pageNumberEnd: 0,
                        pageSize: 20,
                        pageSizes: [20, 50, 100],
                    };

                    $scope.LOCALE = LOCALE;

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

                    $scope.delete_in_progress = false;
                    $scope.ips_to_delete = [];

                    $scope.selecting_page_size = false;

                    $scope.edit_blacklist_ip = function(blacklist_ip) {
                        $scope.current_ip = blacklist_ip;
                        $scope.current_comment = $scope.blacklist_comments.hasOwnProperty(blacklist_ip) ? $scope.blacklist_comments[blacklist_ip] : "";
                        $scope.ip_being_edited = true;
                        var blacklist_comment_field = $("#blacklist_current_comment");
                        var wait_id = setInterval( function() {
                            if (blacklist_comment_field.is(":visible")) {
                                blacklist_comment_field.focus();
                                blacklist_comment_field.select();
                                clearInterval(wait_id);
                            }
                        }, 250);
                    };

                    $scope.cancel_blacklist_editing = function() {
                        $scope.current_ip = null;
                        $scope.current_comment = "";
                        $scope.ip_being_edited = false;
                        $scope.focus_on_blacklist();
                    };

                    $scope.delete_tooltip = function(ip_address) {
                        return LOCALE.maketext("Click to delete “[_1]” from the blacklist.", ip_address);
                    };

                    $scope.edit_tooltip = function(ip_address) {
                        return LOCALE.maketext("Click to edit the comment for “[_1]”.", ip_address);
                    };

                    $scope.update_blacklist_comment = function() {
                        if ($scope.updating_comment) {
                            return;
                        }

                        $scope.updating_comment = true;
                        HulkdDataSource.add_to_blacklist([ { ip: $scope.current_ip, comment: $scope.current_comment } ])
                            .then( function(results) {
                                $scope.blacklist_comments = HulkdDataSource.blacklist_comments;

                                // Growl out each success from the batch.
                                results.updated.forEach(function(ip) {
                                    growl.success(LOCALE.maketext("You have successfully updated the comment for “[_1]”.", _.escape(ip)));
                                });

                                // Report the failures from the batch.
                                var rejectedMessages = [];
                                Object.keys(results.rejected).forEach(function(ip) {
                                    rejectedMessages.push(_.escape(ip) + ": " + _.escape(results.rejected[ip]));
                                });

                                if (rejectedMessages.length > 0) {
                                    var accumulatedMessages = LOCALE.maketext("Some records failed to update.") + "<br>";
                                    accumulatedMessages += rejectedMessages.join("<br>");
                                    growl.error(accumulatedMessages);
                                }

                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updating_comment = false;
                                $scope.cancel_blacklist_editing();
                                $scope.focus_on_blacklist();
                            });
                    };

                    var ipV6 = /^(([\da-fA-F]{1,4}:){4})(([\da-fA-F]{1,4}:){3})([\da-fA-F]{1,4})$/;
                    var ipV4Range = /^((\d{1,3}.){3}\d{1,3})-((\d{1,3}.){3}\d{1,3})$/;
                    var ipRangeTest = /-/;
                    var ipV6Test = /:/;

                    /**
                     * Separates long ipv4 and ipv6 addresses with br tags.
                     * Also, supports separating ipv4 and ipv6 address ranges.
                     *
                     * @param {string} ip - an ip address
                     * @todo Implement this as an Angular Filter in a separate file
                     */
                    $scope.splitLongIp = function(ip) {

                        // ipv6?
                        if (ipV6Test.test(ip)) {

                            // is this a range?
                            if (ipRangeTest.test(ip)) {

                                // format the ipv6 addresses in range format
                                var ipv6Addresses = ip.split(ipRangeTest);
                                var ipv6AddressRange = "";

                                // get the first part of the range
                                var match = ipV6.exec(ipv6Addresses[0]);
                                if (match) {
                                    ipv6AddressRange += match[1] + "<br>" + match[3] + match[5];
                                }

                                // add the range separator
                                ipv6AddressRange += "-<br>";

                                // get the second part of the range
                                match = ipV6.exec(ipv6Addresses[1]);
                                if (match) {
                                    ipv6AddressRange += match[1] + "<br>" + match[3] + match[5];
                                }

                                // if all we have is -<br>, then forget it
                                if (ipv6AddressRange.length > 5) {
                                    return ipv6AddressRange;
                                }
                            } else {

                                // format the ipv6 address
                                var v6match = ipV6.exec(ip);
                                if (v6match) {
                                    return v6match[1] + "<br>" + v6match[3] + v6match[5];
                                }
                            }
                        } else {

                            // format the ipv4 range
                            var v4rangeMatch = ipV4Range.exec(ip);
                            if (v4rangeMatch) {
                                return v4rangeMatch[1] + "-<br>" + v4rangeMatch[3];
                            }
                        }

                        // could not format it, just return it
                        return ip;
                    };

                    $scope.$watch(function() {
                        return HulkdDataSource.enabled;
                    }, function() {
                        $scope.load_list();
                    });

                    $scope.$watchGroup([ "blacklist.length", "meta.filteredList.length" ], function() {
                        if ($scope.blacklist.length === 0 || $scope.meta.filteredList.length === 0) {
                            $("#blacklist_select_all_checkbox").prop("checked", false);
                        }
                    });

                    $scope.selectPage = function(page) {
                        $("#blacklist_select_all_checkbox").prop("checked", false);

                        // set the page if requested
                        if (page && angular.isNumber(page)) {
                            $scope.meta.currentPage = page;
                        }

                        $scope.load_list();
                    };

                    $scope.selectPageSize = function() {
                        return $scope.load_list({ reset_focus: false });
                    };

                    /**
                     * Filter the list by the `meta.filter`.
                     */
                    $scope.filterList = function() {
                        $scope.meta.currentPage = 1;
                        $scope.load_list({ reset_focus: false });
                    };

                    /**
                     * Clear the filter if it is set.
                     */
                    $scope.toggleFilter = function() {
                        $scope.meta.filter = "";
                        $scope.load_list({ reset_focus: false });
                    };

                    $scope.sortList = function(meta) {
                        $scope.meta.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        $scope.applyFilters();
                    };

                    $scope.orderByComments = function(comment_object, ip_list) {
                        var comments_as_pairs = _.toPairs(comment_object);
                        var ips_as_pairs = [];

                        // get the IPs that have no comments
                        for (var i = 0; i < ip_list.length; i++) {
                            if (!_.has(comment_object, ip_list[i] )) {
                                var one_entry = [ip_list[i], "" ];
                                ips_as_pairs.push(one_entry);
                            }
                        }

                        // sort the IPs that have no comments
                        var sorted_pairs = _.sortBy(ips_as_pairs, function(pair) {
                            return $scope.ip_padder(pair[0]);
                        });

                        // sort the comments first by comment, then by IP address
                        comments_as_pairs.sort(compareComments);

                        // create an array of the IPs from the sorted comments
                        var just_ips_comments = _.map(comments_as_pairs, function(pair) {
                            return pair[0];
                        });

                        // create an array of the sorted IPs with no comments
                        var just_ips = _.map(sorted_pairs, function(pair) {
                            return pair[0];
                        });

                        // put the IPs with comments and the IPs without comments together
                        var stuck_together = just_ips_comments.concat(just_ips);

                        if ($scope.meta.sortDirection === "desc") {
                            return stuck_together.reverse();
                        }

                        return stuck_together;
                    };

                    /**
                     * Apply the sort, filter and pagination to the blacklist data.
                     *
                     * @returns {string[]} List of ips that pass the filters.
                     */
                    $scope.applyFilters = function() {
                        var filteredList = [];
                        var start, limit;

                        filteredList = $scope.blacklist;

                        // Sort
                        if ($scope.meta.sortDirection !== "" && $scope.meta.sortBy !== "") {
                            if ($scope.meta.sortBy === "black_ip") {
                                filteredList = filters.orderBy(filteredList, $scope.ip_padder, $scope.meta.sortReverse);
                            } else {
                                filteredList = $scope.orderByComments($scope.blacklist_comments, $scope.blacklist);
                            }
                        }

                        // Totals
                        $scope.meta.totalItems = $scope.blacklist.length;

                        // Filter content
                        var expected = $scope.meta.filter.toLowerCase();
                        if (expected) {
                            filteredList = filters.filter(filteredList, function(actual) {
                                return actual.indexOf(expected) !== -1 ||
                                       ($scope.blacklist_comments[actual] && $scope.blacklist_comments[actual].toLowerCase().indexOf(expected) !== -1);
                            });
                        }

                        // Track the filtered size separatly
                        $scope.meta.filteredItems = filteredList.length;

                        // Pagination
                        start = ($scope.meta.currentPage - 1) * $scope.meta.pageSize;
                        limit = $scope.meta.pageSize;
                        filteredList = filters.limitTo(filters.startFrom(filteredList, start), limit);

                        $scope.meta.pageNumberStart = start + 1;
                        $scope.meta.pageNumberEnd = ($scope.meta.currentPage * $scope.meta.pageSize);


                        if ($scope.meta.totalItems === 0) {
                            $scope.meta.pageNumberStart = 0;
                        }

                        if ($scope.meta.pageNumberEnd > $scope.meta.totalItems) {
                            $scope.meta.pageNumberEnd = $scope.meta.totalItems;
                        }

                        $scope.meta.filteredList = filteredList;

                        return filteredList;
                    };

                    $scope.load_list = function(options) {
                        if (HulkdDataSource.enabled && !$scope.loading) {

                            $scope.loading = true;
                            var reset_focus = typeof options !== "undefined" && options.hasOwnProperty("reset_focus") ? options.reset_focus : true;

                            if (HulkdDataSource.blacklist_is_cached) {
                                $scope.blacklist = HulkdDataSource.blacklist;
                                $scope.blacklist_comments = HulkdDataSource.blacklist_comments;
                                $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                $scope.applyFilters();
                                if (reset_focus) {
                                    $scope.focus_on_blacklist();
                                }
                                $scope.loading = false;
                            } else {
                                $scope.meta.filteredList = [];
                                return HulkdDataSource.load_list("black")
                                    .then(function() {
                                        $scope.blacklist = HulkdDataSource.blacklist;
                                        $scope.blacklist_comments = HulkdDataSource.blacklist_comments;
                                        $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                        $scope.applyFilters();
                                    }, function(error) {
                                        growl.error(error);
                                    })
                                    .finally(function() {
                                        if (reset_focus) {
                                            $scope.focus_on_blacklist();
                                        }
                                        $scope.selecting_page_size = false;
                                        $scope.loading = false;
                                    });
                            }
                        }
                        return null;
                    };

                    $scope.force_load_blacklist = function() {
                        HulkdDataSource.blacklist_is_cached = false;
                        $scope.blacklist = [];
                        $scope.blacklist_comments = {};
                        $scope.meta.filteredList = [];
                        return $scope.load_list();
                    };

                    $scope.delete_confirmation_message = function() {
                        if ($scope.ips_to_delete.length === 1) {
                            return LOCALE.maketext("Do you want to permanently delete “[_1]” from the blacklist?", $scope.ips_to_delete[0]);
                        } else {
                            return LOCALE.maketext("Do you want to permanently delete [quant,_1,record,records] from the backlist?", $scope.ips_to_delete.length);
                        }
                    };

                    $scope.itemsAreChecked = function() {
                        return $(".blacklist_select_item").filter(":checked").length > 0;
                    };

                    $scope.check_blacklist_selection = function() {
                        if ($(".blacklist_select_item").filter(":not(:checked)").length === 0) {
                            $("#blacklist_select_all_checkbox").prop("checked", true);
                        } else {
                            $("#blacklist_select_all_checkbox").prop("checked", false);
                        }
                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                    };

                    /**
                     * Get the list of ips selected in the UI.
                     *
                     * @returns <string[]> List if ips selected.
                     */
                    $scope.getSelection = function()  {
                        var selected_items = [],
                            $selected_dom_nodes = $(".blacklist_select_item:checked");

                        if ($selected_dom_nodes.length === 0) {
                            return [];
                        }

                        $selected_dom_nodes.each( function() {
                            selected_items.push($(this).data("ip"));
                        });

                        return selected_items;
                    };

                    $scope.confirm_blacklist_deletion = function(ip_to_delete) {
                        if ($scope.blacklist.length === 0) {
                            return false;
                        }

                        $scope.delete_in_progress = true;
                        if (ip_to_delete !== undefined) {
                            $scope.ips_to_delete = [ip_to_delete];
                            $scope.is_single_deletion = true;
                        } else {
                            var selected_items = $scope.getSelection();
                            if (selected_items.length === 0) {
                                return false;
                            }
                            $scope.ips_to_delete = selected_items;
                            $scope.is_single_deletion = false;
                        }

                        $scope.modal_instance = $uibModal.open({
                            templateUrl: "confirm_blacklist_deletion.html",
                            scope: $scope,
                        });

                        return true;
                    };

                    $scope.clear_modal_instance = function() {
                        if ($scope.modal_instance) {
                            $scope.modal_instance.close();
                            $scope.modal_instance = null;
                        }
                    };

                    $scope.cancel_deletion = function() {
                        $scope.delete_in_progress = false;
                        $scope.ips_to_delete = [];
                        $scope.clear_modal_instance();
                        $scope.focus_on_blacklist();
                    };

                    $scope.delete_blacklist_ips = function(is_single_deletion) {
                        $scope.clear_modal_instance();
                        HulkdDataSource.remove_from_blacklist($scope.ips_to_delete)
                            .then( function(results) {
                                $scope.blacklist = HulkdDataSource.blacklist;
                                $scope.blacklist_comments = HulkdDataSource.blacklist_comments;
                                $scope.applyFilters();
                                $scope.focus_on_blacklist();

                                if (results.removed.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully deleted “[_1]” from the blacklist.", _.escape(results.removed[0])));
                                } else {
                                    growl.success(LOCALE.maketext("You have successfully deleted [quant,_1,record,records] from the blacklist.", results.removed.length));
                                }

                                if (results.not_removed.keys && results.not_removed.keys.length > 0) {
                                    growl.warning(LOCALE.maketext("The system was unable to delete [quant,_1,record,records] from the blacklist.", results.not_removed.keys.length));
                                }
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally( function() {
                                $scope.delete_in_progress = false;
                                $scope.ips_to_delete = [];
                                if (!is_single_deletion) {
                                    $scope.deselect_all_blacklist();
                                }

                                // Since this is using JQuery/DOM, we have to wait another tick for the UI to update
                                // before we try to get the selection.
                                $timeout(function() {
                                    $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                    $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                                });
                            });
                    };

                    $scope.confirm_delete_all = function() {
                        if ($scope.blacklist.length === 0) {
                            return false;
                        }
                        $scope.delete_in_progress = true;

                        $scope.modal_instance = $uibModal.open({
                            templateUrl: "confirm_blacklist_delete_all.html",
                            scope: $scope,
                        });

                        return true;
                    };

                    $scope.cancel_delete_all = function() {
                        $scope.delete_in_progress = false;
                        $scope.clear_modal_instance();
                        $scope.focus_on_blacklist();
                    };

                    $scope.delete_all = function() {
                        $scope.clear_modal_instance();
                        HulkdDataSource.remove_all_from_blacklist()
                            .then( function(results) {
                                $scope.blacklist = HulkdDataSource.blacklist;
                                $scope.blacklist_comments = HulkdDataSource.blacklist_comments;
                                $scope.applyFilters();
                                $scope.focus_on_blacklist();
                                if (results.not_removed.keys && results.not_removed.keys.length > 0) {
                                    growl.success(LOCALE.maketext("You have successfully deleted [quant,_1,record,records] from the blacklist.", results.removed.keys.length));
                                    growl.warning(LOCALE.maketext("The system was unable to delete [quant,_1,record,records] from the blacklist.", results.not_removed.keys.length));
                                } else {
                                    growl.success(LOCALE.maketext("You have deleted all records from the blacklist."));
                                }
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally( function() {
                                $scope.delete_in_progress = false;
                                $scope.deselect_all_blacklist();

                                // Since this is using JQuery/DOM, we have to wait another tick for the UI to update
                                // before we try to get the selection.
                                $timeout(function() {
                                    $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                    $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();
                                });
                            });
                    };

                    $scope.select_all_blacklist = function() {
                        if ($scope.blacklist.length === 0) {
                            return false;
                        }
                        $(".blacklist_select_item").prop("checked", true);
                        $("#blacklist_select_all_checkbox").prop("checked", true);

                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();

                        return true;
                    };

                    $scope.deselect_all_blacklist = function() {
                        if ($scope.blacklist.length === 0) {
                            return false;
                        }
                        $(".blacklist_select_item").prop("checked", false);
                        $("#blacklist_select_all_checkbox").prop("checked", false);

                        $scope.downloadSelectionLink = $scope.generateDownloadSelectionLink();

                        return true;
                    };

                    $scope.toggle_blacklist_selection = function() {
                        if ($("#blacklist_select_all_checkbox").prop("checked") === true) {
                            $scope.select_all_blacklist();
                        } else {
                            $scope.deselect_all_blacklist();
                        }
                    };

                    $scope.focus_on_blacklist = function() {
                        var blacklist_batch_field = $("#blacklist_batch_add");
                        var wait_id = setInterval( function() {
                            if (blacklist_batch_field.is(":visible")) {
                                blacklist_batch_field.focus();
                                blacklist_batch_field.select();
                                clearInterval(wait_id);
                            }
                        }, 250);
                    };

                    /**
                     *
                     * @typedef Record
                     * @property {string} ip
                     * @property {string?} comment
                     */

                    /**
                     * Parse the batch of records.
                     *
                     * @param {string} text
                     * @returns {Record[]}
                     */
                    function parseBatch(text) {
                        var lines = text.split("\n");
                        var records = [];

                        for (var i = 0; i < lines.length; i++) {
                            var line = lines[i];
                            if (line && line.length > 0) {
                                var parts = line.split("#");
                                var ip = parts.shift().trim();
                                var comment = parts.join("#").trim();
                                records.push({
                                    ip: ip,
                                    comment: comment,
                                });
                            }
                        }
                        return records;
                    }

                    /**
                     * Add a batch of records to the blacklist.
                     *
                     * @async
                     * @returns
                     */
                    $scope.add_to_blacklist = function() {
                        if (!$scope.new_blacklist_records || $scope.adding_batch_to_blacklist) {
                            return;
                        }

                        var records = parseBatch($scope.new_blacklist_records);
                        return $scope._add_to_blacklist(records);
                    };

                    /**
                     * Add a batch of blacklist records.
                     *
                     * @private
                     * @param {Record[]} batch
                     * @returns
                     */
                    $scope._add_to_blacklist = function(batch) {
                        $scope.adding_batch_to_blacklist = true;
                        HulkdDataSource.add_to_blacklist(batch)
                            .then( function(results) {
                                $scope.blacklist = HulkdDataSource.blacklist;
                                $scope.blacklist_comments = HulkdDataSource.blacklist_comments;
                                $scope.downloadAllLink = $scope.generateDownloadAllLink();
                                $scope.applyFilters();

                                if (results.added.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully added “[_1]” to the blacklist.", _.escape(results.added[0])));
                                } else if (results.added.length > 1) {
                                    growl.success(LOCALE.maketext("You have successfully added [quant,_1,IP address,IP addresses] to the blacklist.", results.added.length));
                                }

                                if (results.updated.length === 1) {
                                    growl.success(LOCALE.maketext("You have successfully updated the comment for “[_1]”.", _.escape(results.updated[0])));
                                } else if (results.updated.length > 1) {
                                    growl.success(LOCALE.maketext("You have successfully updated the [numerate,_1,comment,comments] for [quant,_1,IP address,IP addresses].", results.updated.length));
                                }

                                var rejectedIps = Object.keys(results.rejected);
                                if (rejectedIps.length > 0) {
                                    var accumulatedMessages = LOCALE.maketext("Some IP addresses were not added to the blacklist.");
                                    accumulatedMessages += "<br>\n";

                                    // Put the rejected ips/comments back in the list.
                                    $scope.new_blacklist_records = rejectedIps.map(function(ip) {
                                        var record = batch.find(function(record) {
                                            return record.ip === ip;
                                        });
                                        if (record && record.comment) {
                                            return ip + " # " + record.comment + "\n";
                                        }
                                        return ip + "\n";
                                    }).join("");

                                    // Report the problems in the growl
                                    accumulatedMessages += "<ul>\n";
                                    rejectedIps.forEach(function(ip) {
                                        if (results.rejected[ip]) {
                                            accumulatedMessages += "<li>" + _.escape(results.rejected[ip]) + "</li>\n";
                                        }
                                    });
                                    accumulatedMessages += "</ul>\n";
                                    growl.error(accumulatedMessages);
                                } else {
                                    $scope.new_blacklist_records = "";
                                }
                            }, function(error_details) {
                                var error = error_details.main_message;

                                // Format the individual partial errors.
                                var secondary_count = error_details.secondary_messages.length;
                                if (secondary_count > 0) {
                                    error += "<ul>\n";
                                }

                                error_details.secondary_messages.forEach(function(message) {
                                    error += "<li>" + _.escape(message) + "</li>\n";
                                });

                                if (secondary_count > 0) {
                                    error += "</ul>\n";
                                }

                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.adding_batch_to_blacklist = false;
                                $scope.focus_on_blacklist();
                            });
                    };

                    // Handle auto-adding an ip from a query param or POST
                    if (($routeParams["ip"] && $routeParams["ip"].length > 0) ||
                        PAGE.ipToAdd !== null) {
                        var ip;
                        var comment = "";

                        if ($routeParams["ip"] && $routeParams["ip"].length > 0) {

                            // added via a query param
                            ip = $routeParams["ip"];
                        } else if (PAGE.ipToAdd !== null) {

                            // added via a POST and stuffed into PAGE
                            ip = PAGE.ipToAdd;
                        }

                        // clear the ip so we don't add it again
                        PAGE.ipToAdd = null;

                        if (ip !== void 0) {
                            $scope._add_to_blacklist([ { ip: ip, comment: comment } ]);
                        }
                    }

                    // TODO: Make this a utility system: ip-comparison
                    $scope.ip_padder = function(unpadded) {
                        var padded_ip = "";
                        if (unpadded) {
                            var split_ip = unpadded.split(".");
                            for (var i = 0; i < split_ip.length; i++) {
                                var this_section = split_ip[i];
                                while ( this_section.length < 3) {
                                    this_section = "0" + this_section;
                                }
                                padded_ip += this_section;
                            }
                        }
                        return padded_ip;
                    };

                    function compareComments(a, b) {

                        // sort by comment
                        if (a[1].toLowerCase() < b[1].toLowerCase()) {
                            return -1;
                        }
                        if (a[1].toLowerCase() > b[1].toLowerCase()) {
                            return 1;
                        }

                        // we have a duplicate comment, so sort by IP address
                        if ($scope.ip_padder(a[0]) < $scope.ip_padder(b[0])) {
                            return -1;
                        }
                        if ($scope.ip_padder(a[0]) > $scope.ip_padder(b[0])) {
                            return 1;
                        }

                        // we have a duplicate comment and IP
                        return 0;
                    }

                    /**
                     * Generate the download name.
                     *
                     * @returns {string} - the name of the download.
                     */
                    $scope.downloadName = function() {
                        return Download.getDownloadName("blacklist");
                    };

                    /**
                     * @typedef IpRecord
                     * @property {string} ip - ip address or range.
                     * @property {string?} comment - comment associated with the ip or range.
                     */

                    /**
                     * Package the ips and comments into a records structure
                     *
                     * @param {string[]} ips
                     * @param {Dictionary<string,string>} comments
                     * @returns {IpRecord[]}
                     */
                    function getRecords(ips, comments) {
                        var list = [];
                        ips.forEach(function(ip) {
                            var comment = comments[ip];
                            list.push({ ip: ip, comment: comment });
                        });
                        return list;
                    }

                    /**
                     * Generate a data blob url that contains all the blacklist ips.
                     *
                     * @returns {string} - data url.
                     */
                    $scope.generateDownloadAllLink = function() {
                        if ($scope.downloadAllLink) {

                            // Clean up the previous url
                            Download.cleanupDownloadUrl($scope.downloadAllLink);
                            $scope.downloadAllLink = null;
                        }

                        var ips = $scope.blacklist;
                        if (!ips || ips.length === 0) {
                            return "";
                        }

                        var list = getRecords(ips, $scope.blacklist_comments);
                        return Download.getTextDownloadUrl(Download.formatList(list));
                    };

                    /**
                     * Generate a data blob url that contains the selected blacklist ips.
                     *
                     * @returns {string} - data url.
                     */
                    $scope.generateDownloadSelectionLink = function() {
                        if ($scope.downloadSelectionLink) {

                            // Clean up the previous url
                            Download.cleanupDownloadUrl($scope.downloadSelectionLink);
                            $scope.downloadSelectionLink = null;
                        }

                        if (!$scope.blacklist || $scope.blacklist.length === 0) {
                            return "";
                        }

                        var selection = $scope.getSelection();
                        if (!selection || selection.length === 0) {
                            return "";
                        }

                        var list = getRecords(selection, $scope.blacklist_comments);

                        return Download.getTextDownloadUrl(Download.formatList(list));
                    };


                    $scope.focus_on_blacklist();
                },
            ]);

        return controller;
    }
);

/* global define: false */

define(
    'app/services/FailedLoginService',[

        // Libraries
        "angular",

        // Application

        // CJT
        "cjt/util/locale",
        "cjt/io/api",
        "cjt/io/whm-v1-request",
        "cjt/io/whm-v1" // IMPORTANT: Load the driver so its ready
    ],
    function(angular, LOCALE, API, APIREQUEST, APIDRIVER) {

        var app = angular.module("App");

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

            var exports = {};

            function normalizeData(data) {

                // make the timeleft field an actual integer for sorting
                if (angular.isDefined(data.timeleft)) {
                    data.timeleft = parseInt(data.timeleft, 10);
                }

                // make the authservice the same as the service if there is no authservice specified
                if (angular.isDefined(data.service) && angular.isDefined(data.authservice)) {
                    if (data.authservice === "") {
                        data.authservice = data.service;
                    }
                }

                return data;
            }


            function convertResponseData(responseData) {
                var items = [];

                for (var i = 0, len = responseData.length; i < len; i++) {
                    items.push(normalizeData(responseData[i]));
                }

                return items;
            }

            exports.getBrutes = function(meta) {
                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "get_cphulk_brutes");
                if (meta) {
                    if (meta.filterBy && meta.filterValue !== null && meta.filterValue !== void 0) {
                        apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
                    }
                    if (meta.sortBy && meta.sortDirection) {
                        apiCall.addSorting(meta.sortBy, meta.sortDirection, meta.sortType);
                    }
                    if (meta.pageNumber !== null && meta.pageNumber !== void 0) {
                        apiCall.addPaging(meta.pageNumber, meta.pageSize || 20);
                    }
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            results.data = convertResponseData(results.data);
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            exports.getExcessiveBrutes = function(meta) {

                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "get_cphulk_excessive_brutes");
                if (meta) {
                    if (meta.filterBy && meta.filterValue !== null && meta.filterValue !== void 0) {
                        apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
                    }
                    if (meta.sortBy && meta.sortDirection) {
                        apiCall.addSorting(meta.sortBy, meta.sortDirection, meta.sortType);
                    }
                    if (meta.pageNumber !== null && meta.pageNumber !== void 0) {
                        apiCall.addPaging(meta.pageNumber, meta.pageSize || 20);
                    }
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            results.data = convertResponseData(results.data);
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            exports.getFailedLogins = function(meta) {
                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "get_cphulk_failed_logins");
                if (meta) {
                    if (meta.filterBy && meta.filterValue !== null && meta.filterValue !== void 0) {
                        apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
                    }
                    if (meta.sortBy && meta.sortDirection) {
                        apiCall.addSorting(meta.sortBy, meta.sortDirection, meta.sortType);
                    }
                    if (meta.pageNumber !== null && meta.pageNumber !== void 0) {
                        apiCall.addPaging(meta.pageNumber, meta.pageSize || 20);
                    }
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            results.data = convertResponseData(results.data);
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            exports.getBlockedUsers = function(meta) {
                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "get_cphulk_user_brutes");
                if (meta) {
                    if (meta.filterBy && meta.filterValue !== null && meta.filterValue !== void 0) {
                        apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
                    }
                    if (meta.sortBy && meta.sortDirection) {
                        apiCall.addSorting(meta.sortBy, meta.sortDirection, meta.sortType);
                    }
                    if (meta.pageNumber !== null && meta.pageNumber !== void 0) {
                        apiCall.addPaging(meta.pageNumber, meta.pageSize || 20);
                    }
                }

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            results.data = convertResponseData(results.data);
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };


            exports.clearHistory = function() {

                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "flush_cphulk_login_history");

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            results.data = convertResponseData(results.data);
                            deferred.resolve(results);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };

            exports.unBlockAddress = function(address) {

                var deferred = $q.defer(),
                    apiCall = new APIREQUEST.Class();

                apiCall.initialize("", "flush_cphulk_login_history_for_ips");
                apiCall.addArgument("ip", address);

                API.promise(apiCall.getRunArguments())
                    .done(function(response) {

                        // create items from the response
                        response = response.parsedResponse;
                        if (response.status) {
                            var results = response;
                            deferred.resolve(results.data);
                        } else {
                            deferred.reject(response.error);
                        }
                    });

                return deferred.promise;
            };


            return exports;
        }]);
    }
);

/*
# templates/hulkd/views/historyController.js      Copyright(c) 2020 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* global define: false */

define(
    'app/views/historyController',[
        "angular",
        "lodash",
        "cjt/util/locale",
        "uiBootstrap",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/actionButtonDirective",
        "cjt/decorators/growlDecorator",
        "app/services/FailedLoginService",
        "app/services/HulkdDataSource"
    ],
    function(angular, _, LOCALE) {

        // Retrieve the current application
        var app = angular.module("App");

        var controller = app.controller(
            "historyController",
            ["$scope", "$filter", "$q", "$timeout", "FailedLoginService", "HulkdDataSource", "growl",
                function($scope, $filter, $q, $timeout, FailedLoginService, HulkdDataSource, growl) {

                    function updatePagination(scopeObj, apiResults) {
                        var page_size = parseInt(apiResults.meta.paginate.page_size, 10);
                        if (page_size === 0) {
                            scopeObj.pageSize = $scope.meta.pageSizes[0];
                        } else {
                            scopeObj.pageSize = page_size;
                        }
                        scopeObj.totalRows = apiResults.meta.paginate.total_records;
                        scopeObj.pageNumber = apiResults.meta.paginate.current_page;
                        scopeObj.pageNumberStart = apiResults.meta.paginate.current_record;

                        if (scopeObj.totalRows === 0) {
                            scopeObj.pageNumberStart = 0;
                        }

                        scopeObj.pageNumberEnd = (scopeObj.pageNumber * page_size);


                        if (scopeObj.pageNumberEnd > scopeObj.totalRows) {
                            scopeObj.pageNumberEnd = scopeObj.totalRows;
                        }
                    }

                    $scope.changePageSize = function(type) {
                        if (type === "logins") {
                            if ($scope.logins.length > 0) {
                                return $scope.fetchFailedLogins({ isUpdate: true });
                            }
                        } else if (type === "users") {
                            if ($scope.users.length > 0) {
                                return $scope.fetchBlockedUsers({ isUpdate: true });
                            }
                        } else if (type === "brutes") {
                            if ($scope.brutes.length > 0) {
                                return $scope.fetchBrutes({ isUpdate: true });
                            }
                        } else if (type === "excessiveBrutes") {
                            if ($scope.excessiveBrutes.length > 0) {
                                return $scope.fetchExcessiveBrutes({ isUpdate: true });
                            }
                        }
                    };

                    $scope.fetchPage = function(type, page) {
                        if (type === "logins") {
                            if (page && angular.isNumber(page)) {
                                $scope.meta.logins.currentPage = page;
                            }
                            return $scope.fetchFailedLogins({ isUpdate: true });
                        } else if (type === "users") {
                            if (page && angular.isNumber(page)) {
                                $scope.meta.users.currentPage = page;
                            }
                            return $scope.fetchBlockedUsers({ isUpdate: true });
                        } else if (type === "brutes") {
                            if (page && angular.isNumber(page)) {
                                $scope.meta.brutes.currentPage = page;
                            }
                            return $scope.fetchBrutes({ isUpdate: true });
                        } else if (type === "excessiveBrutes") {
                            if (page && angular.isNumber(page)) {
                                $scope.meta.excessiveBrutes.currentPage = page;
                            }
                            return $scope.fetchExcessiveBrutes({ isUpdate: true });
                        }
                    };

                    $scope.sortBruteList = function(meta) {
                        $scope.meta.brutes.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        return $scope.fetchBrutes({ isUpdate: true });
                    };

                    $scope.sortExcessiveBruteList = function(meta) {
                        $scope.meta.excessiveBrutes.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        return $scope.fetchExcessiveBrutes({ isUpdate: true });
                    };

                    $scope.sortLoginList = function(meta) {
                        $scope.meta.logins.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        return $scope.fetchFailedLogins({ isUpdate: true });
                    };

                    $scope.sortBlockedUsers = function(meta) {
                        $scope.meta.users.sortReverse = (meta.sortDirection === "asc") ? false : true;
                        return $scope.fetchBlockedUsers({ isUpdate: true });
                    };

                    $scope.search = function(type) {
                        if (type === "logins") {
                            return $scope.fetchFailedLogins({ isUpdate: true });
                        } else if (type === "users") {
                            return $scope.fetchBlockedUsers({ isUpdate: true });
                        } else if (type === "brutes") {
                            return $scope.fetchBrutes({ isUpdate: true });
                        } else if (type === "excessiveBrutes") {
                            return $scope.fetchExcessiveBrutes({ isUpdate: true });
                        }
                    };

                    $scope.loadTable = function() {
                        $scope.loadingPageData = true;
                        var table = $scope.selectedTable;
                        if (table === "failedLogins") {
                            $scope.logins = [];
                            return $q.all([
                                $scope.fetchConfig(),
                                $scope.fetchFailedLogins()
                            ]).finally(function() {
                                $scope.loadingPageData = false;
                            });
                        } else if (table === "users") {
                            $scope.users = [];
                            return $q.all([
                                $scope.fetchConfig(),
                                $scope.fetchBlockedUsers()
                            ]).finally(function() {
                                $scope.loadingPageData = false;
                            });
                        } else if (table === "brutes") {
                            $scope.brutes = [];
                            return $q.all([
                                $scope.fetchConfig(),
                                $scope.fetchBrutes()
                            ]).finally(function() {
                                $scope.loadingPageData = false;
                            });
                        } else if (table === "excessiveBrutes") {
                            $scope.excessiveBrutes = [];
                            return $q.all([
                                $scope.fetchConfig(),
                                $scope.fetchExcessiveBrutes()
                            ]).finally(function() {
                                $scope.loadingPageData = false;
                            });
                        } else {
                            $scope.logins = [];
                            $scope.brutes = [];
                            $scope.excessiveBrutes = [];
                            $scope.users = [];
                            return $q.all([
                                $scope.fetchConfig(),
                                $scope.fetchFailedLogins(),
                                $scope.fetchBlockedUsers(),
                                $scope.fetchBrutes(),
                                $scope.fetchExcessiveBrutes()
                            ]).finally(function() {
                                $scope.loadingPageData = false;
                            });
                        }
                    };

                    $scope.refreshLogins = function() {
                        return $scope.loadTable();
                    };

                    $scope.clearHistory = function() {
                        $scope.clearingHistory = true;
                        return FailedLoginService.clearHistory()
                            .then(function(results) {
                                growl.success(LOCALE.maketext("The system cleared the tables."));
                                $scope.logins = [];
                                $scope.brutes = [];
                                $scope.excessiveBrutes = [];
                                $scope.users = [];

                                // update the pagination
                                updatePagination($scope.meta.logins, results);
                                updatePagination($scope.meta.brutes, results);
                                updatePagination($scope.meta.excessiveBrutes, results);
                                updatePagination($scope.meta.users, results);

                                // clear the filter
                                $scope.meta.logins.filterValue = "";
                                $scope.meta.brutes.filterValue = "";
                                $scope.meta.excessiveBrutes.filterValue = "";
                                $scope.meta.users.filterValue = "";

                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.clearingHistory = false;
                            });
                    };

                    $scope.fetchConfig = function() {
                        if (_.isEmpty(HulkdDataSource.config_settings)) {
                            HulkdDataSource.load_config_settings()
                                .then(function(data) {
                                    $scope.config_settings = data;
                                }, function(error) {
                                    growl.error(error);
                                });
                        } else {
                            $scope.config_settings = HulkdDataSource.config_settings;
                        }
                    };

                    $scope.fetchFailedLogins = function(options) {
                        if (options && options.isUpdate) {
                            $scope.updatingPageData = true;
                        } else {
                            $scope.loadingPageData = true;
                        }

                        return FailedLoginService.getFailedLogins($scope.meta.logins)
                            .then(function(results) {
                                $scope.logins = results.data;
                                updatePagination($scope.meta.logins, results);
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updatingPageData = false;
                            });
                    };

                    $scope.fetchBrutes = function(options) {
                        if (options && options.isUpdate) {
                            $scope.updatingPageData = true;
                        } else {
                            $scope.loadingPageData = true;
                        }

                        return FailedLoginService.getBrutes($scope.meta.brutes)
                            .then(function(results) {
                                $scope.brutes = results.data;
                                updatePagination($scope.meta.brutes, results);
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updatingPageData = false;
                            });
                    };

                    $scope.fetchExcessiveBrutes = function(options) {
                        if (options && options.isUpdate) {
                            $scope.updatingPageData = true;
                        } else {
                            $scope.loadingPageData = true;
                        }

                        return FailedLoginService.getExcessiveBrutes($scope.meta.excessiveBrutes)
                            .then(function(results) {
                                $scope.excessiveBrutes = results.data;
                                updatePagination($scope.meta.excessiveBrutes, results);
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updatingPageData = false;
                            });
                    };

                    $scope.fetchBlockedUsers = function(options) {
                        if (options && options.isUpdate) {
                            $scope.updatingPageData = true;
                        } else {
                            $scope.loadingPageData = true;
                        }

                        return FailedLoginService.getBlockedUsers($scope.meta.users)
                            .then(function(results) {
                                $scope.users = results.data;
                                updatePagination($scope.meta.users, results);
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.updatingPageData = false;
                            });
                    };

                    $scope.unBlockAddress = function(address, $event) {
                        var element = $event.target;
                        if (element) {
                            if (/disabled/.test(element.className)) {

                            // do not run this again if the link is disabled
                                return;
                            } else {
                                element.className += " disabled";
                            }
                        }

                        return FailedLoginService.unBlockAddress(address)
                            .then(function(results) {
                                if (results.records_removed > 0) {

                                // remove from the one day block and blocked ip address lists
                                    $scope.removeBrute(address);
                                    growl.success(LOCALE.maketext("The system removed the block for: [_1]", address));
                                }
                            }, function(error) {
                                growl.error(error);
                            });
                    };

                    $scope.removeBrute = function(address) {
                        var item = _.find($scope.brutes, { ip: address });
                        if (item) {
                            $scope.brutes = _.difference($scope.brutes, [item]);
                            return true;
                        }

                        item = _.find($scope.excessiveBrutes, { ip: address });
                        if (item) {
                            $scope.excessiveBrutes = _.difference($scope.excessiveBrutes, [item]);
                            return true;
                        }

                        return false;
                    };

                    $scope.meta = {
                        pageSizes: [20, 50, 100],
                        maxPages: 0,
                        "brutes": {
                            sortDirection: "asc",
                            sortBy: "logintime",
                            sortType: "",
                            filterBy: "*",
                            filterCompare: "contains",
                            filterValue: "",
                            pageNumber: 1,
                            pageNumberStart: 0,
                            pageNumberEnd: 0,
                            pageSize: 20,
                            totalRows: 0
                        },
                        "excessiveBrutes": {
                            sortDirection: "asc",
                            sortBy: "logintime",
                            sortType: "",
                            filterBy: "*",
                            filterCompare: "contains",
                            filterValue: "",
                            pageNumber: 1,
                            pageNumberStart: 0,
                            pageNumberEnd: 0,
                            pageSize: 20,
                            totalRows: 0
                        },
                        "logins": {
                            sortDirection: "asc",
                            sortBy: "user",
                            sortType: "",
                            filterBy: "*",
                            filterCompare: "contains",
                            filterValue: "",
                            pageNumber: 1,
                            pageNumberStart: 0,
                            pageNumberEnd: 0,
                            pageSize: 20,
                            totalRows: 0
                        },
                        "users": {
                            sortDirection: "asc",
                            sortBy: "user",
                            sortType: "",
                            filterBy: "*",
                            filterCompare: "contains",
                            filterValue: "",
                            pageNumber: 1,
                            pageNumberStart: 0,
                            pageNumberEnd: 0,
                            pageSize: 20,
                            totalRows: 0
                        }
                    };

                    $scope.loadingPageData = true;
                    $scope.updatingPageData = false;
                    $scope.clearingHistory = false;

                    // this is the default table that we will show first
                    $scope.selectedTable = "failedLogins";

                    $scope.$on("$viewContentLoaded", function() {
                        $timeout(function() {
                            $scope.refreshLogins();
                        });
                    });

                    $scope.lookbackPeriodMinsDescription = function(config_settings) {
                        if (typeof config_settings === "undefined") {
                            return;
                        }

                        return LOCALE.maketext("The system counts Failed Logins for the duration of the specified period, which is currently set to [quant,_1,minute,minutes].", config_settings.lookback_period_min);
                    };

                    $scope.blockedUsersDescription = function(config_settings) {
                        if (typeof config_settings === "undefined") {
                            return;
                        }

                        return LOCALE.maketext("The system blocks users for [quant,_1,minute,minutes]. You can configure this value with the “[_2]” option.", config_settings.brute_force_period_mins, LOCALE.maketext("Brute Force Protection Period (in minutes)"));
                    };

                    $scope.blockedIPsDescription = function(config_settings) {
                        if (typeof config_settings === "undefined") {
                            return;
                        }

                        return LOCALE.maketext("The system blocks [asis,IP] addresses for [quant,_1,minute,minutes]. You can configure this value with the “[_2]” option.", config_settings.ip_brute_force_period_mins, LOCALE.maketext("IP Address-based Brute Force Protection Period (in minutes)"));
                    };

                }
            ]);

        return controller;
    }
);

/*
# templates/hulkd/views/hulkdEnableController.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, PAGE */

define(
    'app/views/hulkdEnableController',[
        "angular",
        "jquery",
        "cjt/util/locale",
        "cjt/util/parse",
        "uiBootstrap",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/actionButtonDirective",
        "cjt/decorators/growlDecorator",
        "app/services/HulkdDataSource"
    ],
    function(angular, $, LOCALE, PARSE) {

        // Retrieve the current application
        var app = angular.module("App");

        var controller = app.controller(
            "hulkdEnableController",
            ["$scope", "HulkdDataSource", "growl", "growlMessages", "PAGE",
                function($scope, HulkdDataSource, growl, growlMessages, PAGE) {
                    $scope.hulkdEnabled = PARSE.parsePerlBoolean(PAGE.hulkd_status.is_enabled);

                    $scope.knobLabel = "\u00a0";

                    $scope.changing_status = false;
                    $scope.status_check_in_progress = false;

                    $scope.handle_keydown = function(event) {

                    // prevent the spacebar from scrolling the window
                        if (event.keyCode === 32) {
                            event.preventDefault();
                        }
                    };

                    $scope.handle_keyup = function(event) {

                    // bind to the spacebar and enter keys
                        if (event.keyCode === 32 || event.keyCode === 13) {
                            event.preventDefault();
                            $scope.toggle_status();
                        }
                    };

                    $scope.toggle_status = function() {
                        if ($scope.changing_status) {
                            return;
                        }

                        $scope.changing_status = true;

                        if ($scope.hulkdEnabled) {
                            growlMessages.destroyAllMessages();
                            HulkdDataSource.disable_hulkd()
                                .then( function() {
                                    $scope.hulkdEnabled = false;
                                    growl.success(LOCALE.maketext("[asis,cPHulk] is now disabled."));
                                }, function(error) {
                                    growl.error(error);
                                })
                                .finally( function() {
                                    $scope.changing_status = false;
                                });
                        } else {
                            HulkdDataSource.enable_hulkd()
                                .then( function(response) {
                                    $scope.hulkdEnabled = true;
                                    growl.success(LOCALE.maketext("[asis,cPHulk] is now enabled."));
                                    if (response.data && response.data.restart_ssh) {
                                        growl.warning(LOCALE.maketext("The system disabled the [asis,UseDNS] setting for [asis,SSHD] in order to add IP addresses to the whitelist. You must restart SSH through the [output,url,_1,Restart SSH Server,_2] page to implement the change.", PAGE.security_token + "/scripts/ressshd", { "target": "_blank" }));
                                    } else if (response.data && response.data.warning) {
                                        growl.warning(response.data.warning);
                                    }
                                }, function(error) {
                                    growl.error(error);
                                })
                                .finally( function() {
                                    $scope.changing_status = false;
                                });
                        }

                    };

                    $scope.get_status = function() {
                        if ($scope.status_check_in_progress) {
                            return;
                        }
                        $scope.status_check_in_progress = true;
                        return HulkdDataSource.hulkd_status()
                            .then( function(results) {
                                if (results !== $scope.hulkdEnabled) {

                                // this test needs to run only if status has changed
                                    if (results === false) {
                                        growlMessages.destroyAllMessages();
                                    }
                                    growl.warning(LOCALE.maketext("The status for [asis,cPHulk] has changed, possibly in another browser session."));
                                }
                                $scope.hulkdEnabled = results;
                            }, function(error) {
                                growl.error(error);
                            })
                            .finally(function() {
                                $scope.status_check_in_progress = false;
                            });
                    };

                    $scope.init = function() {
                        $(document).ready(function() {

                        // for window and tab changes
                            $(window).on("focus", function() {
                                $scope.get_status();
                            });
                        });
                    };

                    $scope.init();
                }
            ]);

        return controller;
    }
);

/*
# cpanel - whostmgr/docroot/templates/hulkd/index.js
#                                                  Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/* jshint -W100 */
/* eslint-disable camelcase */

define(
    'app/index',[
        "angular",
        "jquery",
        "lodash",
        "cjt/util/locale",
        "cjt/core",
        "cjt/modules",
        "ngRoute",
        "uiBootstrap",
        "ngSanitize",
        "ngAnimate",
    ],
    function(angular, $, _, LOCALE, CJT) {
        "use strict";
        return function() {

            // First create the application
            angular.module("App", [
                "cjt2.config.whm.configProvider", // This needs to load first
                "ngRoute",
                "ui.bootstrap",
                "ngSanitize",
                "ngAnimate",
                "angular-growl",
                "cjt2.whm",
            ]);

            // Then load the application dependencies
            var app = require(
                [
                    "cjt/bootstrap",

                    // Application Modules
                    "app/views/configController",
                    "app/views/countriesController",
                    "app/views/hulkdWhitelistController",
                    "app/views/hulkdBlacklistController",
                    "app/views/historyController",
                    "app/views/hulkdEnableController",
                    "app/services/HulkdDataSource",
                    "angular-growl",
                ], function(BOOTSTRAP) {

                    var app = angular.module("App");
                    app.value("PAGE", PAGE);
                    app.value("COUNTRY_CONSTANTS", {
                        WHITELISTED: "whitelisted",
                        BLACKLISTED: "blacklisted",
                        UNLISTED: "unlisted",
                    });

                    // used to indicate that we are prefetching the following items
                    app.firstLoad = {
                        configs: true,
                    };

                    app.controller("BaseController", ["$rootScope", "$scope",
                        function($rootScope, $scope) {

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

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

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

                            // Setup the routes
                            $routeProvider.when("/config", {
                                controller: "configController",
                                templateUrl: CJT.buildFullPath("hulkd/views/configView.ptt"),
                            });

                            $routeProvider.when("/whitelist", {
                                controller: "hulkdWhitelistController",
                                templateUrl: CJT.buildFullPath("hulkd/views/hulkdWhitelistView.ptt"),
                            });

                            $routeProvider.when("/blacklist", {
                                controller: "hulkdBlacklistController",
                                templateUrl: CJT.buildFullPath("hulkd/views/hulkdBlacklistView.ptt"),
                            });

                            $routeProvider.when("/history", {
                                controller: "historyController",
                                templateUrl: CJT.buildFullPath("hulkd/views/historyView.ptt"),
                            });

                            $routeProvider.when("/countries", {
                                controller: "countriesController",
                                templateUrl: CJT.buildFullPath("hulkd/views/countriesView.ptt"),
                                resolve: {
                                    "COUNTRY_CODES": ["HulkdDataSource", function($service) {
                                        return $service.get_countries_with_known_ip_ranges();
                                    }],
                                    "XLISTED_COUNTRIES": ["HulkdDataSource", function($service) {
                                        return $service.load_xlisted_countries();
                                    }],
                                },
                            });

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

                    app.run(["$rootScope", "$timeout", "$location", "HulkdDataSource", "growl", "growlMessages", function($rootScope, $timeout, $location, HulkdDataSource, growl, growlMessages) {

                        // register listener to watch route changes
                        $rootScope.$on( "$routeChangeStart", function() {
                            $rootScope.currentRoute = $location.path();
                        });

                        $rootScope.whitelist_warning_message = null;
                        $rootScope.ip_added_with_one_click = false;

                        $rootScope.one_click_add_to_whitelist = function(missing_ip) {
                            return HulkdDataSource.add_to_whitelist([{ ip: missing_ip } ])
                                .then(function(results) {
                                    growl.success(LOCALE.maketext("You have successfully added “[_1]” to the whitelist.", results.added[0]));

                                    // check if the client ip is in the whitelist and if our growl is still shown, remove it
                                    if ((Object.prototype.hasOwnProperty.call(results, "requester_ip") &&
                                         results.added.indexOf(results.requester_ip) > -1) &&
                                         ($rootScope.whitelist_warning_message !== null)) {

                                        // remove is handled in this manner because it was not removing the growl in the right sequence
                                        // when shown with other growls
                                        $rootScope.whitelist_warning_message.ttl = 0;
                                        $rootScope.whitelist_warning_message.promises = [];
                                        $rootScope.whitelist_warning_message.promises.push($timeout(angular.bind(growlMessages, function() {
                                            growlMessages.deleteMessage($rootScope.whitelist_warning_message);
                                            $rootScope.whitelist_warning_message = null;
                                        }), 200));
                                        $rootScope.ip_added_with_one_click = true;
                                    }
                                }, function(error_details) {
                                    var combined_message = error_details.main_message;
                                    var secondary_count = error_details.secondary_messages.length;
                                    for (var z = 0; z < secondary_count; z++) {
                                        if (z === 0) {
                                            combined_message += "<br>";
                                        }
                                        combined_message += "<br>";
                                        combined_message += error_details.secondary_messages[z];
                                    }
                                    growl.error(combined_message);
                                });
                        };
                    }]);

                    BOOTSTRAP(document);

                });

            return app;
        };
    }
);

Back to Directory File Manager