Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/mod_security/views/hitListController.js

/*
# templates/mod_security/views/hitlistController.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(
    [
        "angular",
        "cjt/util/locale",
        "uiBootstrap",
        "cjt/directives/responsiveSortDirective",
        "cjt/decorators/paginationDecorator",
        "cjt/directives/autoFocus",
        "cjt/filters/wrapFilter",
        "cjt/directives/spinnerDirective",
        "cjt/services/alertService",
        "app/services/hitlistService",
        "app/services/reportService"
    ],
    function(angular, LOCALE) {

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

        var controller = app.controller("hitListController", [
            "$scope",
            "$location",
            "$anchorScroll",
            "$routeParams",
            "$timeout",
            "hitListService",
            "alertService",
            "reportService",
            "spinnerAPI",
            "PAGE",
            function(
                $scope,
                $location,
                $anchorScroll,
                $routeParams,
                $timeout,
                hitListService,
                alertService,
                reportService,
                spinnerAPI,
                PAGE
            ) {

                $scope.loadingPageData = true;
                $scope.activeSearch = false;
                $scope.filteredData = false;
                $scope.selectedRow = -1;
                $scope.showAddSuccess = false;

                /**
                 * Extracts the vendor id from a config file path.
                 *
                 * @method _getVendorFromFile
                 * @private
                 * @param  {String} file   The full file path to the config file.
                 * @return {String}        The vendor id if it's a vendor config or undefined if we
                 *                         can't parse the file path properly.
                 */
                function _getVendorFromFile(file) {
                    var VENDOR_REGEX = /\/modsec_vendor_configs\/+([^/]+)/; /* TODO: EA-4700 */
                    var match = file && file.match(VENDOR_REGEX);
                    return match ? match[1] : void 0;
                }

                /**
                 * Checks to see if the given config file is the user config file.
                 *
                 * @method _isUserConfigFile
                 * @private
                 * @param  {String}  file   The full file path to the config file.
                 * @return {Boolean}        True if it is the user config file.
                 */
                function _isUserConfigFile(file) {
                    var USER_CONF_REGEX = /\/modsec2\.user\.conf$/; /* TODO: EA-4700 */
                    return USER_CONF_REGEX.test( file );
                }

                /**
                 * Passes the hit object to the report service to save a fetch and loads the view.
                 *
                 * @method loadReportview
                 * @param  {Object} hit   A hit object that corresponds to a single row from the modsec.hits table.
                 */
                $scope.loadReportView = function(hit) {
                    reportService.fetchByHit(hit);
                    $scope.loadView("report/hit/" + hit.id);
                };

                /**
                 * Load the edit rule view with the  requested rule.
                 *
                 * @method loadEditRuleView
                 * @param  {Number} ruleId
                 */
                $scope.loadEditRuleView = function(ruleId, file) {
                    var viewParams = {
                        ruleId: ruleId,
                        back: "hitList"
                    };
                    var vendorId;

                    if (_isUserConfigFile(file)) {
                        $scope.loadView("editCustomRule", viewParams);
                    } else if ( (vendorId = _getVendorFromFile(file)) ) { // Extra parens needed for jshint: http://www.jshint.com/docs/options/#boss
                        viewParams.vendorId = vendorId;
                        $scope.loadView("editCustomRule", viewParams);
                    } else {
                        alertService.add({
                            type: "danger",
                            message: LOCALE.maketext("An unknown error occurred in the attempt to retrieve the rule."),
                            id: "errorFetchRule"
                        });
                    }
                };

                /**
                 * Clear the search query
                 */
                $scope.clearFilter = function() {
                    $scope.meta.filterValue = "";
                    $scope.activeSearch = false;
                    $scope.filteredData = false;

                    // Leave history so refresh works
                    $location.search("api.filter.enable", 0);
                    $location.search("api.filter.verbose", null);
                    $location.search("api.filter.a.field", null);
                    $location.search("api.filter.a.type", null);
                    $location.search("api.filter.a.arg0", null);

                    // select the first page of search results
                    return $scope.selectPage(1);
                };

                /**
                 * Start a search query
                 */
                $scope.startFilter = function() {
                    $scope.activeSearch = true;
                    $scope.filteredData = false;

                    // Leave history so refresh works
                    $location.search("api.filter.enable", 1);
                    $location.search("api.filter.verbose", 1);
                    $location.search("api.filter.a.field", "*");
                    $location.search("api.filter.a.type", "contains");
                    $location.search("api.filter.a.arg0", $scope.meta.filterValue);

                    // Select the first page of search results
                    $scope.selectPage(1);
                    $scope.filteredData = true;
                };

                /**
                 * Selects a table row
                 * @param  {Number} index The index of selected row
                 */
                $scope.toggleRow = function(index) {
                    if ( index === $scope.selectedRow ) {

                        // collapse the row
                        $scope.selectedRow = -1;

                    } else {

                        // expand the selected row
                        $scope.selectedRow = index;
                    }

                };

                /**
                 * Select a specific page
                 * @param  {Number} [page] Optional page number, if not provided will use the current
                 * page provided by the scope.meta.pageNumber.
                 * @return {Promise}
                */
                $scope.selectPage = function(page) {

                    // clear the selected row
                    $scope.selectedRow = -1;

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

                    // Leave history so refresh works
                    $location.search("api.chunk.enable", 1);
                    $location.search("api.chunk.verbose", 1);
                    $location.search("api.chunk.size", $scope.meta.pageSize);
                    $location.search("api.chunk.start", ( ($scope.meta.pageNumber - 1) * $scope.meta.pageSize) + 1);

                    return $scope.fetch();
                };

                /**
                 * Sort the list of hits
                 * @param {String} sortBy Field name to sort by.
                 * @param {String} sortDirection Direction to sort by: asc or decs
                 * @param {String} [sortType] Optional sort type applied to the field. Sort type is lexical by default.
                 */
                $scope.sortList = function(meta, defaultSort) {

                    // clear the selected row
                    $scope.selectedRow = -1;

                    // Leave history so refresh works
                    $location.search("api.sort.enable", 1);
                    $location.search("api.sort.a.field", meta.sortBy);
                    $location.search("api.sort.a.method", meta.sortType || "");
                    $location.search("api.sort.a.reverse", meta.sortDirection === "asc" ? 0 : 1);

                    if (!defaultSort) {
                        $scope.fetch();
                    }
                };

                /**
                 * Handles the keybinding for the clearing and searching.
                 * Esc clears the search field.
                 * Enter performs a search.
                 *
                 * @method triggerToggleSearch
                 * @param {Event} event - The event object
                 */
                $scope.triggerToggleSearch = function(event) {

                    // clear on Esc
                    if (event.keyCode === 27) {
                        $scope.toggleSearch(true);
                    }

                    // filter on Enter
                    if (event.keyCode === 13) {
                        $scope.toggleSearch();
                    }
                };

                /**
                 * Toggles the clear button and conditionally performs a search.
                 * The expected behavior is if the user clicks the button or focuses the button and hits enter the button state rules.
                 *
                 * @param {Boolean} isClick Toggle button clicked.
                 */
                $scope.toggleSearch = function(isClick) {
                    var filter = $scope.meta.filterValue;

                    if ( !filter && ($scope.activeSearch  || $scope.filteredData)) {

                        // no query in box, but we prevously filtered or there is an active search
                        $scope.clearFilter();
                    } else if (isClick && $scope.activeSearch ) {

                        // User clicks clear
                        $scope.clearFilter();
                    } else if (filter) {
                        $scope.startFilter();
                    }
                };

                /**
                 * Fetch the list of hits from the server
                 * @return {Promise} Promise that when fulfilled will result in the list being loaded with the new criteria.
                 */
                $scope.fetch = function() {
                    spinnerAPI.start("hitlistSpinner");
                    return hitListService
                        .fetchList($scope.meta)
                        .then(function(results) {
                            $scope.hitList = results.items;
                            $scope.totalItems = results.totalItems;
                            $scope.totalPages = results.totalPages;
                        }, function(error) {

                            // failure
                            alertService.add({
                                type: "danger",
                                message: error,
                                id: "errorFetchHitList"
                            });
                        })
                        .then(function() {
                            $scope.loadingPageData = false;
                            spinnerAPI.stop("hitlistSpinner");
                        });
                };

                // setup data structures for the view
                $scope.hitList = [];
                $scope.totalPages = 0;
                $scope.totalItems = 0;

                var routeHasPaging = $routeParams["api.chunk.enable"] === "1";
                var pageSize = 10;
                var page = 1;
                if (routeHasPaging) {
                    pageSize = parseInt($routeParams["api.chunk.size"], 10);
                    page = Math.floor(parseInt($routeParams["api.chunk.start"], 10) / pageSize) + 1;
                }

                var routeHasSorting = $routeParams["api.sort.enable"] === "1";

                $scope.meta = {
                    filterBy: $routeParams["api.filter.a.field"] || "*",
                    filterCompare: "contains",
                    filterValue: $routeParams["api.filter.a.arg0"] || "",
                    pageSize: routeHasPaging ?  pageSize : 10,
                    pageNumber: routeHasPaging ? page : 1,
                    sortDirection: routeHasSorting ? ( $routeParams["api.sort.a.reverse"] === "1" ? "desc" : "asc" ) : "desc",
                    sortBy: routeHasSorting ? $routeParams["api.sort.a.field"] : "timestamp",
                    sortType: routeHasSorting ? $routeParams["api.sort.a.type"] : "numeric",
                    pageSizes: [10, 20, 50, 100]
                };

                // if the user types something else in the search box, we change the button icon so they can search again.
                $scope.$watch("meta.filterValue", function(oldValue, newValue) {
                    if (oldValue === newValue) {
                        return;
                    }
                    $scope.activeSearch = false;
                });

                // watch the page size and and load the first page if it changes
                $scope.$watch("meta.pageSize", function(oldValue, newValue) {
                    if (oldValue === newValue) {
                        return;
                    }
                    $scope.selectPage(1);
                });

                $scope.activeSearch = $scope.filteredData = $scope.meta.filterValue ? true : false;

                // Setup the installed bit...
                $scope.isInstalled = PAGE.installed;

                // Expose any backend exceptions, ie missing database, missing table,
                $scope.dbException = PAGE.hitList.metadata.result === 0 ? PAGE.hitList.metadata.reason : "";

                $scope.$on("$viewContentLoaded", function() {

                    // check for page data in the template if this is a first load
                    if (app.firstLoad.hitList && PAGE.hitList) {
                        app.firstLoad.hitList = false;
                        $scope.loadingPageData = false;
                        var results = hitListService.prepareList(PAGE.hitList);
                        $scope.hitList = results.items;
                        $scope.totalItems = results.totalItems;
                        $scope.totalPages = results.totalPages;
                    } else {

                        // Otherwise, retrieve it via ajax
                        $timeout(function() {

                            // NOTE: Without this delay the spinners are not created on inter-view navigation.
                            $scope.selectPage(1);
                        });
                    }
                });

                if ($routeParams["addSuccess"]) {
                    $scope.showAddSuccess = true;
                }
            }
        ]);

        return controller;
    }
);
Back to Directory File Manager