File manager - Edit - /usr/local/cpanel/whostmgr/docroot/templates/mod_security/views/rulesListController.js
Back
/* # mod_security/views/rulelistController.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 */ define( [ "angular", "lodash", "cjt/util/locale", "cjt/util/logic", "uiBootstrap", "cjt/directives/responsiveSortDirective", "cjt/decorators/paginationDecorator", "cjt/directives/autoFocus", "cjt/filters/wrapFilter", "cjt/filters/splitFilter", "cjt/filters/htmlFilter", "cjt/directives/spinnerDirective", "cjt/directives/actionButtonDirective", "cjt/services/alertService", "app/services/ruleService", "app/services/vendorService", "cjt/io/whm-v1-querystring-service", ], function(angular, _, LOCALE, LOGIC) { "use strict"; var USER_CONFIG = "modsec2.user.conf"; /* TODO: EA-4700 */ var STATUS_ENUM = { ENABLED: "enabled", DISABLED: "disabled", BOTH: "both", }; var PUBLISHED_ENUM = { DEPLOYED: "deployed", STAGED: "staged", BOTH: "both", }; // Retrieve the current application var app = angular.module("App"); var controller = app.controller( "rulesListController", [ "$scope", "$location", "$anchorScroll", "$timeout", "ruleService", "vendorService", "alertService", "spinnerAPI", "queryService", "PAGE", function( $scope, $location, $anchorScroll, $timeout, ruleService, vendorService, alertService, spinnerAPI, queryService, PAGE) { $scope.loadingPageData = true; $scope.activeSearch = false; $scope.filteredData = false; $scope.advancedSearchApplied = false; /** * Update the advanced search applied flag by checking if the advanced search flags are * in their default state or not. * * @private * @method _updateAdvancedSearchApplied * @return {Boolean} true if there is an advanced search, false otherwise */ function _updateAdvancedSearchApplied() { // See if we have an advanced search different from the default if ($scope.meta.advanced.includeUserRules !== true || $scope.meta.advanced.showEnabledDisabled !== STATUS_ENUM.BOTH || $scope.meta.advanced.showStagedDeployed !== PUBLISHED_ENUM.BOTH) { $scope.advancedSearchApplied = true; } else { $scope.advancedSearchApplied = false; } // Also update the indicator at this point. $scope.appliedIncludeUserRules = $scope.meta.advanced.includeUserRules; } /** * Update the previous state information for rollback should the fetch fail. * @private * @method _updatePreviousState */ function _updatePreviousState() { $scope.previouslySelected = $scope.selectedVendors; $scope.meta.advanced.previousShowStagedDeployed = $scope.meta.advanced.showStagedDeployed; $scope.meta.advanced.previousShowEnabledDisabled = $scope.meta.advanced.showEnabledDisabled; $scope.meta.advanced.previousIncludeUserRules = $scope.meta.advanced.includeUserRules; } /** * Revert to the previous advanced filter setting. * @private * @method _revertToPreviousState */ function _revertToPreviousState() { $scope.selectedVendors = $scope.previouslySelected; $scope.meta.advanced.showStagedDeployed = $scope.meta.advanced.previousShowStagedDeployed; $scope.meta.advanced.showEnabledDisabled = $scope.meta.advanced.previousShowEnabledDisabled; $scope.meta.advanced.includeUserRules = $scope.meta.advanced.previousIncludeUserRules; $scope.meta.advanced.changed = false; } /** * Apply the advanced search filters to the query-string. * * @method _addAdvancedSearchToQuery * @private */ function _addAdvancedSearchToQuery() { if ($scope.meta.advanced.showStagedDeployed === PUBLISHED_ENUM.STAGED) { queryService.query.addSearchField("c", "staged", "eq", "1"); } else { queryService.query.clearSearchField("staged", "eq", "1"); } if ($scope.meta.advanced.showStagedDeployed === PUBLISHED_ENUM.DEPLOYED) { queryService.query.addSearchField("b", "staged", "eq", "0"); } else { queryService.query.clearSearchField("staged", "eq", "0"); } if ($scope.meta.advanced.showEnabledDisabled === STATUS_ENUM.ENABLED) { queryService.query.addSearchField("d", "disabled", "eq", "0"); } else { queryService.query.clearSearchField("disabled", "eq", "0"); } if ($scope.meta.advanced.showEnabledDisabled === STATUS_ENUM.DISABLED) { queryService.query.addSearchField("e", "disabled", "eq", "1"); } else { queryService.query.clearSearchField("disabled", "eq", "1"); } if (!$scope.meta.advanced.includeUserRules) { queryService.query.removeParameter("config"); } else { queryService.query.addParameter("config", USER_CONFIG); } var vendors = _getSelectedVendorIDs(); if (vendors.length === 0) { queryService.query.removeParameter("vendor_id"); } else { queryService.query.addParameter("vendor_id", vendors.join(",")); } } /** * Event handler triggered when one of the advanced filter options is changed. * * @method onAdvancedChanged * @param {String} type */ $scope.onAdvancedChanged = function(type) { switch (type) { case "vendor": if ( $scope.previouslySelected !== $scope.selectedVendors ) { $scope.meta.advanced.changed = true; } break; case "userDefined": if ($scope.meta.advanced.previousIncludeUserRules !== $scope.meta.advanced.includeUserRules) { $scope.meta.advanced.changed = true; } break; default: $scope.meta.advanced.changed = true; } }; /** * Check if there are any search criteria applied. This includes various advanced search * criteria different then their defaults or any unselected vendors. * * @method hasSearchFilter * @return {Boolean} true if the is an active search, false otherwise. */ $scope.hasSearchFilter = function() { return $scope.filteredData === true || $scope.advancedSearchApplied === true || $scope.appliedVendors.length < $scope.vendors.length; }; /** * Clear the search query * * @method clearFilter * @returns Promise */ $scope.clearFilter = function() { $scope.meta.filterValue = ""; $scope.activeSearch = false; $scope.filteredData = false; queryService.query.clearSearchField("*", "contains"); _addAdvancedSearchToQuery(); // select the first page of search results return $scope.selectPage(1); }; /** * Start a search query * * @method startFilter * @returns Promise */ $scope.startFilter = function() { $scope.activeSearch = ($scope.meta.filterValue !== ""); $scope.filteredData = false; // Leave history so refresh works if ($scope.meta.filterValue) { queryService.query.addSearchField("a", "*", "contains", $scope.meta.filterValue); } else { queryService.query.clearSearchField("*", "contains"); } return $scope.selectPage(1) .then(function() { _addAdvancedSearchToQuery(); if ($scope.meta.filterValue) { $scope.filteredData = true; } _updatePreviousState(); $scope.meta.advanced.changed = false; }, function() { // Revert to the previous state _revertToPreviousState(); }).finally(function() { _updateAdvancedSearchApplied(); }); }; /** * Open the advanced search menu from another button. * * @method openAdvancedSearch * @param {Event} $event The jQlite event */ $scope.openAdvancedSearch = function($event) { $event.preventDefault(); $event.stopPropagation(); $event.currentTarget.blur(); $scope.advancedSearchOpen = !$scope.advancedSearchOpen; }; /** * Apply the advanced filter and close the dropdown * * @method applyAdvancedFilter * @param {Event} $event */ $scope.applyAdvancedFilter = function($event) { $event.preventDefault(); $event.stopPropagation(); $scope.advancedSearchOpen = false; $scope.toggleSearch(); }; /** * Reset the advanced filter and close the dropdown * * @method resetAdvancedFilter * @param {Event} $event */ $scope.resetAdvancedFilter = function($event) { $event.preventDefault(); $event.stopPropagation(); $scope.advancedSearchOpen = false; $scope.resetFilter(); }; /** * Update the applied vendor field. * * @private * @method _updateAppliedVendor */ function _updateAppliedVendor() { $scope.appliedVendors = $scope.selectedVendors.slice(); } /** * Reset the advanced filter and apply */ $scope.resetFilter = function() { $scope.meta.advanced.changed = false; if ($scope.meta.advanced.showStagedDeployed !== PUBLISHED_ENUM.BOTH) { $scope.meta.advanced.showStagedDeployed = PUBLISHED_ENUM.BOTH; $scope.meta.advanced.changed = true; } if ($scope.meta.advanced.showEnabledDisabled !== STATUS_ENUM.BOTH) { $scope.meta.advanced.showEnabledDisabled = STATUS_ENUM.BOTH; $scope.meta.advanced.changed = true; } if (!$scope.meta.advanced.includeUserRules) { $scope.meta.advanced.includeUserRules = true; $scope.meta.advanced.changed = true; } if ($scope.selectedVendors.length < $scope.vendors.length) { $scope.selectedVendors = $scope.vendors; $scope.meta.advanced.changed = true; } if ($scope.meta.advanced.changed) { $scope.startFilter().then(_updateAppliedVendor); } }; /** * 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. * * @method toggleSearch * @scope * @param {Boolean} isClick Toggle button clicked. */ $scope.toggleSearch = function(isClick) { var filter = $scope.meta.filterValue; var advancedChanged = $scope.meta.advanced.changed; if ( (!filter && !advancedChanged && ($scope.activeSearch || $scope.filteredData))) { // no query in box, but we previously filtered or there is an active search $scope.clearFilter().then(_updateAppliedVendor); } else if (isClick && $scope.activeSearch ) { // User clicks clear $scope.clearFilter().then(_updateAppliedVendor); } else if (filter || advancedChanged) { $scope.startFilter().then(_updateAppliedVendor); } }; /** * Select a specific page of rules * * @method selectPage * @param {Number} [page] Optional page number, if not provided will use the current * page provided by the scope.meta.pageNumber. * @return {Promise} */ $scope.selectPage = function(page) { // set the page if requested if (page && angular.isNumber(page)) { $scope.meta.pageNumber = page; } queryService.query.updatePagination($scope.meta.pageNumber, $scope.meta.pageSize); return $scope.fetch(); }; /** * Sort the list of rules * * @param {Object} meta The sort model. * @param {Boolean} defaultSort If true, the sort was not not initiated by the user */ $scope.sortList = function(meta, defaultSort) { queryService.query.clearSort(); queryService.query.addSortField(meta.sortBy, meta.sortType, meta.sortDirection); if (!defaultSort) { $scope.fetch(); } }; /** * Disables a rule from the list using the rule service * * @method disable * @param {Object} rule The rule to disable * @return {Promise} */ $scope.disable = function(rule) { var ruleIndentifier = rule.id || "rule"; // if message is defined append it to the rule identifier if ( rule.hasOwnProperty("meta_msg") && rule.meta_msg !== "" ) { ruleIndentifier += ": " + rule.meta_msg; } return ruleService .disableRule(rule.config, rule.id, false) .then(function() { // success rule.disabled = true; rule.staged = true; $scope.stagedChanges = true; alertService.add({ type: "success", message: LOCALE.maketext("You successfully disabled “[_1]” in the list of [asis,ModSecurity™] rules.", _.escape(ruleIndentifier)), id: "alertDisableSuccess", }); }, function(error) { // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorDisablingRule", }); }); }; /** * Enables a rule from the list using the rule service * * @method enable * @param {Object} rule The rule to enable * @return {Promise} */ $scope.enable = function(rule) { var ruleIndentifier = rule.id || "rule"; // if message is defined append it to the rule identifier if ( rule.hasOwnProperty("meta_msg") && rule.meta_msg !== "" ) { ruleIndentifier += ": " + rule.meta_msg; } return ruleService .enableRule(rule.config, rule.id, false) .then(function() { // success rule.disabled = false; rule.staged = true; $scope.stagedChanges = true; alertService.add({ type: "success", message: LOCALE.maketext("You successfully enabled “[_1]” in the list of [asis,ModSecurity™] rules.", _.escape(ruleIndentifier)), id: "alertEnableSuccess", }); }, function(error) { // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorEnablingRule", }); }); }; /** * Deletes a rule from the list using the rule service * * @method delete * @param {Object} rule The rule to delete * @return {Promise} */ $scope.delete = function(rule) { var ruleIndentifier = rule.id || "rule"; // if message is defined append it to the rule identifier if ( rule.hasOwnProperty("meta_msg") && rule.meta_msg !== "" ) { ruleIndentifier += ": " + rule.meta_msg; } rule.deleting = true; return ruleService .deleteRule(rule.id) .then(function() { // success $scope.fetch(); alertService.add({ type: "success", message: LOCALE.maketext("You successfully deleted “[_1]” from the list of [asis,ModSecurity™] rules.", _.escape(ruleIndentifier)), id: "alertDeleteSuccess", }); }, function(error) { rule.deleting = false; // reset delete confirmation rule.showDeleteConfirm = false; // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorDeletingRule", }); }); }; /** * Deploys staged rules using the rule service * * @method deployChanges * @return {Promise} */ $scope.deployChanges = function() { $scope.pendingChanges = true; return ruleService .deployQueuedRules() .then(function() { // success $scope.stagedChanges = false; alertService.add({ type: "success", message: LOCALE.maketext("You successfully deployed the staged changes and [asis,Apache] received a graceful restart request."), id: "successDeployChanges", }); }, function(error) { // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorDeployChanges", }); }).finally(function() { $scope.pendingChanges = false; $scope.fetch(); }); }; /** * Discards staged rule changes using the rule service * * @method discardChanges * @return {Promise} */ $scope.discardChanges = function() { $scope.pendingChanges = true; return ruleService .discardQueuedRules() .then(function() { var replace = false; // discard changes success $scope.stagedChanges = false; return $scope.fetch().then(function() { // fetch success replace = true; }, function() { // fetch failure replace = false; }).finally(function() { // display discard changes success $scope.discardConfirm = false; alertService.add({ type: "success", message: LOCALE.maketext("You successfully discarded the staged changes."), id: "successDiscardingChanges", replace: replace, }); }); }, function(error) { $scope.fetch(); // To update the list to match the new state. // discard changes failure alertService.add({ type: "danger", message: _.escape(error), id: "errorDiscardingChanges", }); }).finally(function() { $scope.pendingChanges = false; }); }; /** * Initialize the selected vendors * * @private * @method initializeSelectedVendors */ var _initializeSelectedVendors = function() { var vendorIds = []; var vendor_id_param = queryService.route.getParameter("vendor_id"); if (vendor_id_param) { vendorIds = vendor_id_param.split(","); } var config_param = queryService.route.getParameter("config"); var vendors = []; if (!angular.isDefined(vendor_id_param) && !angular.isDefined(config_param)) { // This is a default load, so select all vendors vendors = $scope.vendors; } else if (vendorIds.length > 0) { // Some vendors were passed on the querystring, so use those vendors = _.filter($scope.vendors, function(vendor) { // find the vendors by ids from the list of vendorIds var id = _.find(vendorIds, function(id) { return vendor.vendor_id === id; }); return !!id; }); } $scope.previouslySelected = $scope.selectedVendors = vendors; // Also update the applied vendors so the ui is updated on load. $scope.appliedVendors = vendors.slice(); }; /** * Determines if a rule is from a custom set * * @method isCustomVendor * @param {Object} rule The rule to read vendor id from * @return {Boolean} Returns true if the rule is from a custom set */ $scope.isCustomVendor = function(rule) { return rule.hasOwnProperty("vendor_id") && rule.vendor_id === ""; }; /** * Returns the full vendor name for the supplied rule * * @method getVendorName * @param {Object} rule The rule to read vendor id from * @return {String} The full vendor name */ $scope.getVendorName = function(rule) { var currentVendor; if ( rule.vendor_id !== "" ) { for ( var i = 0, length = $scope.vendors.length; i < length; i++ ) { currentVendor = $scope.vendors[i]; if ( rule.vendor_id === currentVendor.vendor_id ) { return currentVendor.name; } } } return LOCALE.maketext("Custom"); }; /** * Get a list of the enabled vendors * * @method _onlyEnabledVendors * @private * @param {Array} vendor A list of vendors * @return {Array} A list of the vendors that were enabled */ function _onlyEnabledVendors(vendors) { if (vendors && angular.isArray(vendors)) { return vendors.filter( function(vendor) { return vendor.enabled; }); } else { return []; } } /** * Retrieve the list of vendors * * @method getVendors * @return {Promise} Promise that when fulfilled will result in the list being loaded with the new criteria. */ $scope.getVendors = function() { spinnerAPI.start("ruleListSpinner"); return vendorService .fetchList() .then(function(results) { if (angular.isArray(results.items)) { $scope.vendors = _onlyEnabledVendors(results.items); _initializeSelectedVendors(); } else { alertService.add({ message: "The system was unable to retrieve the list of available vendors.", type: "danger", }); } }, function(error) { // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorLoadingVendorList", }); }).finally(function() { spinnerAPI.stop("ruleListSpinner"); }); }; /** * Performs final prep work on the selectedVendors list. * Extracts the vendor ids into an array. * * @method _getSelectedVendorIDs * @return {Array} A list of vendor ids that is ready for consumption by the ruleService */ function _getSelectedVendorIDs() { return $scope.selectedVendors && $scope.selectedVendors.map(function(vendor) { return vendor.vendor_id; }); } /** * Fetch the list of rules from the server * @method fetch * @return {Promise} Promise that when fulfilled will result in the list being loaded with the new criteria. */ $scope.fetch = function() { $scope.loadingPageData = true; spinnerAPI.start("ruleListSpinner"); alertService.removeById("errorFetchRulesList"); return ruleService .fetchRulesList(_getSelectedVendorIDs(), $scope.meta) .then(function(results) { $scope.rules = results.items; $scope.stagedChanges = results.stagedChanges; $scope.totalItems = results.totalItems; $scope.totalPages = results.totalPages; }, function(error) { // failure alertService.add({ type: "danger", message: _.escape(error), id: "errorFetchRulesList", }); // throw an error for chained promises throw error; }).finally(function() { $scope.loadingPageData = false; spinnerAPI.stop("ruleListSpinner"); }); }; /** * Generates the text for the vendor/user-defined indicator button. * This is needed because we need to dynamically determine plurality of phrases. * * @method generateIndicatorText * @param {String} type The type of vendor count to generate (e.g. "short" or "long") * @return {String} The formatted vendor count string */ $scope.generateIndicatorText = function(type) { switch (type) { case "vendor-short": return $scope.appliedVendors.length; case "vendor-long": return LOCALE.maketext("[quant,_1,Vendor,Vendors]", $scope.appliedVendors.length); case "vendor-title": return $scope.generateVendorTitle(); case "user-title": return $scope.meta.advanced.previousIncludeUserRules ? LOCALE.maketext("Your user-defined rules are included below.") : LOCALE.maketext("Your user-defined rules are not included below."); default: return LOCALE.maketext("Loading …"); } }; /** * Generates the text for the title/tooltip that displays when a user hovers over the rule set count. * * @method generateVendorTitle * @return {String} The text for the tooltip */ $scope.generateVendorTitle = function() { var vendors = $scope.appliedVendors; if (vendors.length === 0) { return LOCALE.maketext("You have not selected any vendor rule sets."); } var vendorNames = vendors.map(function(vendor) { return vendor.name; }); return LOCALE.maketext("The displayed rules are from the following vendor rule [numerate,_1,set,sets]: [list_and,_2]", vendors.length, vendorNames); }; /** * Sets the left property so that the dropdown lines up with the input group * * @method _setMenuLeft * @private * @param {Element} menu The dropdown menu element * @param {Number} groupWidth The width of the input group in pixels */ function _setMenuLeft(menu, groupWidth) { menu.css("left", -1 * groupWidth); menu.css("right", "auto"); } /** * Unsets the left and right properties so that they reset back to the CSS defaults * * @method _setMenuRight * @private * @param {Element} menu The dropdown menu element */ function _setMenuRight(menu) { menu.css("left", ""); menu.css("right", ""); } /** * Adjusts the position of the dropdown menu depending on whether or not it is being * clipped by the edge of the viewport. * * @method fixMenuClipping * @param {Event} event The associated event object */ $scope.fixMenuClipping = function(event) { var menu = this.find(".advanced-filter-menu"); var inputGroup = this.siblings("input"); var groupWidth = inputGroup.outerWidth(); // This keeps the menu from flying around while still allowing offset to work if (event.type === "open") { menu.css("opacity", 0); } $timeout(function() { // We need to queue this up after $digest or the dropdown won't be visible yet and the offset will be incorrect if (menu) { switch (event.type) { case "resize": if (menu.offset().left < 0) { _setMenuLeft(menu, groupWidth); } else if (groupWidth > menu.outerWidth()) { // If the menu isn't clipping, it could be because we fixed it already or because it fits on the page _setMenuRight(menu); } break; case "open": if (menu.offset().left < 0) { _setMenuLeft(menu, groupWidth); } break; case "close": _setMenuRight(menu); break; } menu.css("opacity", 1); } }, 0, false); }; // setup data structures for the view $scope.rules = []; $scope.vendors = []; $scope.appliedVendors = []; // Differs from the selectedVendors in that this is only populated once the settings have been applied $scope.totalPages = 0; $scope.totalItems = 0; var pageSize = queryService.route.getPageSize(queryService.DEFAULT_PAGE_SIZE); var page = queryService.route.getPage(pageSize, 1); var sorting = queryService.route.getSortProperties("disabled", "", "asc"); /** * Determin if we should check the includeUserRules advanced search option. * @note if neither the config or vendor_id is set in the querystring, then default to * showing the custom config to match the default prefetch rules. * @return {Boolean} */ function _includeUserRules() { var config = queryService.route.getParameter("config"); var vendor_id = queryService.route.getParameter("vendor_id"); if (config !== USER_CONFIG && !vendor_id) { return true; // We default to showing custom rules } return config === USER_CONFIG; } var staged = LOGIC.compareOrDefault(queryService.route.getSearchFieldValue("staged"), "1", true); var deployed = LOGIC.compareOrDefault(queryService.route.getSearchFieldValue("staged"), "0", true); var disabled = LOGIC.compareOrDefault(queryService.route.getSearchFieldValue("disabled"), "1", true); var enabled = LOGIC.compareOrDefault(queryService.route.getSearchFieldValue("disabled"), "0", true); $scope.meta = { filterBy: "*", filterCompare: "contains", filterValue: "", pageSize: pageSize, pageNumber: page, sortBy: sorting.field, sortType: sorting.type, sortDirection: sorting.direction, pageSizes: [10, 20, 50, 100], advanced: { showStagedDeployed: LOGIC.translateBinaryAndToState(staged, deployed, PUBLISHED_ENUM.BOTH, PUBLISHED_ENUM.STAGED, PUBLISHED_ENUM.DEPLOYED, PUBLISHED_ENUM.BOTH), showEnabledDisabled: LOGIC.translateBinaryAndToState(enabled, disabled, STATUS_ENUM.BOTH, STATUS_ENUM.ENABLED, STATUS_ENUM.DISABLED, STATUS_ENUM.BOTH), includeUserRules: _includeUserRules(), changed: false, }, }; $scope.appliedIncludeUserRules = $scope.meta.advanced.includeUserRules; _updatePreviousState(); $scope.activeSearch = $scope.filteredData = $scope.meta.filterValue ? true : false; // 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); }); // Setup the installed bit... $scope.isInstalled = PAGE.installed; if (!$scope.isInstalled) { // redirect to the historic view of the hit list if mod_security is not installed $scope.loadView("hitList"); } $scope.$on("$viewContentLoaded", function() { // check for page data in the template if this is a first load if (app.firstLoad.rules && PAGE.rules) { app.firstLoad.rules = false; $scope.loadingPageData = false; $scope.advancedSearchOpen = false; var vendors = vendorService.prepareList(PAGE.vendors); // In the rules list page, we only care about // searching for rules from enabled vendors. $scope.vendors = _onlyEnabledVendors(vendors.items); _initializeSelectedVendors(); var rules = ruleService.prepareList(PAGE.rules); $scope.rules = rules.items; $scope.stagedChanges = rules.stagedChanges; $scope.totalItems = rules.totalItems; $scope.totalPages = rules.totalPages; if ( !rules.status ) { // on view load in an error state give the user a chance to discard staged changes $scope.stagedChanges = true; $scope.loadingPageData = "error"; alertService.add({ type: "danger", message: LOCALE.maketext("There was a problem loading the page. The system is reporting the following error: [_1].", _.escape(PAGE.rules.metadata.reason)), id: "errorFetchRulesList", }); } } else { // Otherwise, retrieve it via ajax $timeout(function() { // NOTE: Without this delay the spinners are not created on inter-view navigation. $scope.getVendors().then(function() { $scope.selectPage(1); }); }); } }); }, ]); return controller; } );
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings