Viewing File: /usr/local/cpanel/whostmgr/docroot/templates/easyapache4/views/profile.js

/*
# templates/easyapache4/views/profile.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 */

/* eslint-disable no-console, no-use-before-define, camelcase, no-useless-escape */

define(
    [
        "angular",
        "cjt/util/locale",
        "lodash",
        "cjt/services/alertService",
        "cjt/directives/alertList",
        "cjt/decorators/growlDecorator",
        "app/directives/fileModel",
        "app/directives/fileType",
        "app/directives/fileSize",
        "app/services/ea4Data",
        "app/services/ea4Util",
        "app/services/pkgResolution",
    ],
    function(angular, LOCALE, _) {
        "use strict";

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

        app.controller("profile",
            [ "$scope", "$timeout", "$location", "$uibModal", "ea4Data", "ea4Util", "alertService", "growl", "growlMessages",
                function($scope, $timeout, $location, $uibModal, ea4Data, ea4Util, alertService, growl, growlMessages) {
                    $scope.profileList = [];
                    $scope.activeProfile = {};
                    $scope.loadingProfiles = false;
                    $scope.errorOccurred = false;
                    $scope.noProfiles = false;
                    $scope.upload = {
                        show: false,
                        profile: {},
                        content: {},
                        disableLocalSec: true,
                        localSecIsOpen: true,
                        disableUrlSec: false,
                        urlSecIsOpen: false,
                        url: {
                            value: "",
                            filename: "",
                            filenameValMsg: "",
                            showFilenameInput: false,
                        },
                        overwrite: false,
                        highlightOverwrite: false,
                    };
                    $scope.convertProfile = { show: false };

                    var _ea4Recommendations = {};

                    $scope.isLoading = function() {
                        return ( $scope.loadingProfiles || $scope.loadingProfileData );
                    };

                    var resetEA4UI = function() {

                        // Reset wizard attributes.
                        // TODO: This will stay here until ui.router is implemented
                        // in the next template refactor.
                        $scope.customize.wizard.currentStep = "";
                        $scope.customize.wizard.showWizard = false;

                        alertService.clear();

                        // This cancels any previously customized packages.
                        ea4Data.clearEA4LocalStorageItems();
                    };

                    var customizeProfile = function(thisProfile) {

                        // This cancels any previously customized packages.
                        ea4Data.clearEA4LocalStorageItems();
                        ea4Data.setData(
                            {
                                "selectedPkgs": thisProfile.pkgs,
                                "customize": true,
                                "ea4Recommendations": _ea4Recommendations,
                            });
                        $location.path("loadPackages");
                    };

                    var goProvision = function(thisProfile) {
                        ea4Data.setData( { "selectedProfile": thisProfile } );
                        $location.path("review");
                    };

                    $scope.$on("$viewContentLoaded", function() {
                        resetEA4UI();
                        $scope.loadProfiles();
                    });

                    $scope.checkForEA4Updates = function() {
                        $scope.customize.checkUpdateInfo = angular.copy(ea4Util.checkUpdateInfo);
                        var promise = ea4Data.getPkgInfoList();
                        promise.then(function(data) {
                            if (typeof data !== "undefined") {
                                var rawPkgList = data;
                                ea4Data.setData({ "ea4RawPkgList": rawPkgList });
                                var updatePkgs = _.map(_.filter(rawPkgList, ["state", "updatable"]), function(pkg) {
                                    return pkg.package;
                                });

                                // Count the number of packages in updatable state.
                                $scope.customize.checkUpdateInfo.pkgNumber = updatePkgs.length;
                                $scope.customize.toggleUpdateButton();
                            }
                            $scope.customize.checkUpdateInfo.isLoading = false;
                        }, function(error) {
                            alertService.add({
                                type: "danger",
                                message: error,
                                id: "alertMessages",
                                closeable: false,
                            });
                        });
                    };

                    $scope.loadProfiles = function() {
                        $scope.profileList = [];
                        $scope.loadingProfiles = true;
                        ea4Data.getProfiles().then(function(profData) {
                            if (typeof profData !== "undefined") {

                                $scope.noProfiles = false;
                                $scope.checkForEA4Updates();
                                $scope.loadingProfileData = true;
                                ea4Data.getEA4Recommendations().
                                    then(function(result) {
                                        _ea4Recommendations = result.data;
                                        setProfileData(profData, _ea4Recommendations); // eslint-disable-line no-use-before-define
                                    }, function(error) {
                                        showProfileErrors(error);
                                    }).finally(function() {
                                        $scope.loadingProfileData = false;
                                    });
                            } else {
                                $scope.noProfiles = true;
                            }
                        }, function(error) {
                            if (error) {
                                $scope.errorOccurred = true;
                                ea4Data.setData( { "ea4ThrewError": true } );
                                $location.path("yumUpdate");
                            }
                        }).finally(function() {
                            $scope.loadingProfiles = false;
                        });
                    };

                    $scope.viewProfile = function(thisProfile) {
                        var viewingProfile = angular.copy(thisProfile);
                        $uibModal.open({
                            templateUrl: "profileModalContent.tmpl",
                            controller: "ModalInstanceCtrl",
                            resolve: {
                                data: function() {
                                    return viewingProfile;
                                },
                            },
                        });
                    };

                    $scope.customizeCurrentProfile = function(thisProfile) {
                        customizeProfile(thisProfile);
                    };

                    $scope.proceedNext = function(thisProfile, customize) {

                        // Track if customize button clicked or provision button clicked.
                        $scope.clickedCustomize = customize;

                        // Show a warning if there are packages in profile not on server.
                        if (!thisProfile.isValid) {
                            thisProfile.showValidationWarning = true;
                            return;
                        }

                        thisProfile.showValidationWarning = false;
                        $scope.continueAction(thisProfile);
                    };

                    $scope.continueAction = function(thisProfile) {
                        var customize = $scope.clickedCustomize;

                        // Reset the clicked variable for next use.
                        $scope.clickedCustomize = false;

                        // Insert Apache 2.4 into the profile. This ensures people
                        // get apache in whatever state their profile is.
                        if (thisProfile.pkgs.indexOf("ea-apache24") === -1) {
                            thisProfile.pkgs.push("ea-apache24");
                        }

                        if (customize) {
                            customizeProfile(thisProfile);
                        } else {
                            goProvision(thisProfile);
                        }
                    };

                    /**
                     * Resets the clicked variable for next use.
                     *
                     * @method reset
                     */
                    $scope.reset = function(thisProfile) {
                        $scope.clickedCustomize = false;
                        thisProfile.showValidationWarning = false;
                    };

                    $scope.hideRecommendations = function(activeProfile) {
                        activeProfile.showRecommendations = false;

                        // Upon closing recommendation panel, return focus to recommendation link for screenreader/keyboard users
                        $timeout(function() {
                            angular.element("#toggleRecommendations").focus();
                        });
                    };

                    $scope.showRecommendations = function(activeProfile) {
                        activeProfile.showRecommendations = true;

                        // Apply focus to recommendation container for screenreader/keyboard users
                        $timeout(function() {
                            angular.element("#recommendations_container").focus();
                        });
                    };

                    var recommendationsOfActiveProfile = function(activeProfile, recommendations) {
                        var currPkgList = activeProfile.pkgs;
                        var filterPkgsWithRecos = _.intersection(currPkgList, _.keys(recommendations));
                        var filteredRecos = {};
                        _.each(filterPkgsWithRecos, function(pkg) {
                            var reco = recommendations[pkg];
                            var recosList = ea4Util.decideShowHideRecommendations(reco, currPkgList, true, pkg);  // passing 'true' as args to get recommendations of installed packages.
                            // On the profiles page show only recommendations that have level: danger.
                            recosList = _.filter(recosList, ["level", "danger"]);
                            if (!_.isEmpty(recosList)) {
                                filteredRecos[pkg] = {};
                                filteredRecos[pkg].recosList = recosList;
                                filteredRecos[pkg].show = !_.every(recosList, [ "show", false ]);

                                // Set the footnote.
                                filteredRecos[pkg].footNote = LOCALE.maketext("These recommendations appear because you have “[_1]” installed on your system.", pkg);
                            }
                        });

                        return filteredRecos;
                    };

                    /* Upload Popover section */
                    var resetValidators = function(formInput) {
                        var valErrors = formInput.$error;
                        if (typeof valErrors !== "undefined") {
                            _.each(_.keys(valErrors), function(valKey) {
                                $scope.formUpload.profile_file.$setValidity(valKey, true);
                            });
                        }
                    };

                    /**
                     * Clears everything in the upload popover.
                     *
                     * @method clearUploadPopover
                     */
                    var clearUploadPopover = function() {

                        // Destroy all growls before attempting to submit something.
                        growlMessages.destroyAllMessages();

                        // reseting model values
                        var uploadData = $scope.upload;
                        uploadData.content = {};
                        uploadData.overwrite = false;
                        uploadData.highlightOverwrite = false;
                        uploadData.disableLocalSec = true;
                        uploadData.localSecIsOpen = true;
                        uploadData.disableUrlSec = false;
                        uploadData.urlSecIsOpen = false;

                        clearUploadLocalForm($scope.upload);
                        clearUploadUrlForm($scope.upload.url);
                    };

                    /**
                     * Clears the upload local section in Upload accordion.
                     *
                     * @method clearUploadLocalForm
                     */
                    var clearUploadLocalForm = function(uploadData) {

                        // reseting upload local section.
                        uploadData.profile = {};

                        if ($scope.formUpload && $scope.formUpload.$dirty) {
                            resetValidators($scope.formUpload.profile_file);

                            // mark the form pristine
                            $scope.formUpload.$setPristine();

                            try {
                                angular.element("#profile_file").val(null); // for IE11, latest browsers
                            } catch (error) {

                                // For IE10 and others
                                angular.element("#form_upload_profile").reset();
                            }
                        }
                    };

                    /**
                     * Clears the upload url section in Upload accordion.
                     *
                     * @method clearUploadUrlForm
                     */
                    var clearUploadUrlForm = function(uploadUrlData) {

                        // var uploadData = $scope.upload;
                        uploadUrlData.filename = "";
                        uploadUrlData.filenameValMsg = "";
                        uploadUrlData.value = "";
                        uploadUrlData.showFilenameInput = false;

                        if ($scope.formUpload && $scope.formUpload.$dirty) {
                            var valErrors = $scope.formUpload.profile_file_url.$error;
                            if (typeof valErrors !== "undefined") {
                                _.each(_.keys(valErrors), function(valKey) {
                                    $scope.formUpload.profile_file.$setValidity(valKey, true);
                                });
                            }
                            resetValidators($scope.formUpload.profile_file_url);
                            resetValidators($scope.formUpload.txtUploadUrlFilename);

                            // mark the form pristine
                            $scope.formUpload.$setPristine();
                        }
                    };

                    /**
                     * Validates the profile content to
                     * check if it contains name & at least
                     * one package.
                     *
                     * @method validateProfile
                     */
                    var validateProfile = function(fileContent) {
                        var valid = true;
                        if (_.isEmpty(fileContent.name) ||
                            _.isEmpty(fileContent.pkgs)) {
                            valid = false;
                        }
                        return valid;
                    };

                    /**
                     * Validate the uploaded filename to see if it contains
                     * restricted characters.
                     *
                     * @method validateFilename
                     */
                    var validateFilename = function(filename) {
                        var valid = true;
                        if (/(?:\.\.|\\|\/)/.test(filename)) {
                            valid = false;
                        }
                        return valid;
                    };

                    /**
                     * Reads the uploaded file to validate it.
                     *
                     * @method getAndValidateUploadData
                     */
                    var getAndValidateUploadData = function() {

                        // Destroy all growls before attempting to submit something.
                        growlMessages.destroyAllMessages();

                        var fileData = $scope.upload.profile;
                        if (!validateFilename(fileData.name)) {
                            $scope.$apply($scope.formUpload.profile_file.$setValidity("invalidfilename", false));
                            return;
                        } else {
                            $scope.$apply($scope.formUpload.profile_file.$setValidity("invalidfilename", true));
                        }
                        var reader = new FileReader();
                        reader.readAsText(fileData);
                        reader.onloadend = function() {
                            if (reader.readyState && !reader.error) {

                                // Check if the file has the required data.
                                var upContent = validateUploadContent(reader.result);
                                _.each(_.keys(upContent.val_results), function(val_key) {
                                    $scope.$apply($scope.formUpload.profile_file.$setValidity(val_key, upContent.val_results[val_key]));
                                });
                                if ($scope.formUpload.profile_file_url.$valid) {
                                    $scope.upload.content = upContent.content;
                                }
                            }
                        };
                    };

                    /**
                     * This validates the given content
                     * and updates the validators accordingly.
                     *
                     * @method validateUploadContent
                     */
                    var validateUploadContent = function(uploadContent) {

                        // Check if the file has the required data.
                        var content = "";
                        var valResults = {};
                        try {
                            content = JSON.parse(uploadContent);
                            valResults["invalidformat"] = true;
                            if (!validateProfile(content)) {
                                valResults["content"] = false;
                            } else {
                                valResults["content"] = true;
                            }
                        } catch (e) {
                            valResults["invalidformat"] = false;
                            console.log(e);
                        }
                        return { "content": content, "val_results": valResults };
                    };

                    /**
                     * Cancels the upload action for local section.
                     *
                     * @method cancelUpload
                     */
                    $scope.cancelUpload = function() {
                        $scope.upload.show = false;
                        clearUploadPopover();
                    };

                    /**
                     * Cancels the upload action for url section.
                     *
                     * @method resetUploadUrl
                     */
                    $scope.resetUploadUrl = function() {
                        clearUploadUrlForm($scope.upload.url);
                    };

                    /**
                     * Gets the content from the provided url and performs
                     * validation checks to make sure it is a valid JSON
                     * content with valid profile data.
                     *
                     * @method getAndValidateUploadDataFromURL
                     */
                    $scope.getAndValidateUploadDataFromURL = function() {

                        // Destroy all growls before attempting to submit something.
                        growlMessages.destroyAllMessages();

                        return ea4Data.getUploadContentFromUrl($scope.upload.url.value)
                            .then(function(data) {
                                if (typeof data !== "undefined" && data.status === "200") {
                                    var contentType = data.headers["content-type"];
                                    var validType = /^(application|text)\/json/.test(contentType);
                                    if (!validType) {
                                        $scope.formUpload.profile_file_url.$setValidity("filetype", false);
                                        return;
                                    }  else {
                                        $scope.formUpload.profile_file_url.$setValidity("filetype", true);
                                    }

                                    // Check if the file has the required data.
                                    var upContent = validateUploadContent(data.content);
                                    _.each(_.keys(upContent.val_results), function(val_key) {
                                        $scope.formUpload.profile_file_url.$setValidity(val_key, upContent.val_results[val_key]);
                                    });
                                    if ($scope.formUpload.profile_file_url.$valid) {
                                        $scope.upload.content = upContent.content;
                                        $scope.upload.url.showFilenameInput = true;
                                    } else {
                                        $scope.upload.url.showFilenameInput = false;
                                    }
                                } else {
                                    var errorMsg = LOCALE.maketext("Status: “[output,strong,_1]”. Reason: “[output,em,_2]”.", _.escape(data.status), _.escape(data.reason));
                                    growl.error(errorMsg);
                                }
                            }, function(error) {
                                growl.error(_.escape(error));
                            });
                    };

                    /**
                     * Scope method that calls ea4Util service's validateFilename method and
                     * sets the validation message accordingly.
                     *
                     * @method validateFilenameInput
                     */
                    $scope.validateFilenameInput = function() {
                        var valData = ea4Util.validateFilename($scope.upload.url.filename);
                        $scope.upload.url.filenameValMsg = valData.valMsg;
                        $scope.formUpload.txtUploadUrlFilename.$setValidity("valFilename", valData.valid);
                    };

                    /**
                     * Uploads Profiles.
                     *
                     * @method uploadProfile
                     */
                    $scope.uploadProfile = function() {

                        // Destroy all growls before attempting to submit something.
                        growlMessages.destroyAllMessages();

                        if ($scope.formUpload.$valid) {

                            // upload profile
                            var overwrite = $scope.upload.overwrite ? 1 : 0;
                            var filenameWithExt = (typeof $scope.upload.profile.name !== "undefined") ? $scope.upload.profile.name : $scope.upload.url.filename + ".json";
                            return ea4Data.saveAsNewProfile($scope.upload.content, filenameWithExt, overwrite)
                                .then(function(data) {
                                    if (typeof data !== "undefined" && !_.isEmpty(data.path)) {
                                        $scope.loadProfiles();
                                        growl.success(LOCALE.maketext("The system successfully uploaded your profile."));
                                        $scope.cancelUpload();
                                    }
                                }, function(response) {
                                    if (!_.isEmpty(response.data) && response.data.already_exists) {
                                        $scope.upload.highlightOverwrite = true;
                                    }
                                    growl.error(_.escape(response.error));
                                });
                        }
                    };

                    /**
                     * This toggle function handles enabling/disabling sections
                     * so that at least one upload section is always open.
                     *
                     * @method handleAccordionToggle
                     */
                    $scope.handleAccordionToggle = function() {
                        clearUploadLocalForm($scope.upload);
                        clearUploadUrlForm($scope.upload.url);
                        if ($scope.upload.urlSecIsOpen) {
                            $scope.upload.disableLocalSec = false;
                            $scope.upload.disableUrlSec = true;
                        } else {
                            $scope.upload.disableUrlSec = false;
                            $scope.upload.disableLocalSec = true;
                        }
                    };

                    /**
                     * Shows the given popover with their initialization logic.
                     *
                     * @method showPopover
                     */
                    $scope.showPopover = function(popoverName) {
                        $scope.convertProfile.cancel();
                        switch (popoverName) {
                            case "upload":
                                $scope.upload.show = true;
                                document.querySelector("#profile_file").onchange = getAndValidateUploadData;

                                var accordionLinkEls = document.querySelectorAll(".panel-heading a");
                                _.each(accordionLinkEls, function(el) {
                                    el.onclick = $scope.handleAccordionToggle;
                                });
                                break;
                            case "convert":
                                $scope.convertProfile.show = true;
                                break;
                        }
                    };

                    /**
                     * This method sets all the profile data including recommendations.
                     *
                     * @method setProfileData
                     */
                    var setProfileData = function(data, recommendations) {
                        var profileTypes = _.sortBy(_.keys(data));
                        _.each(profileTypes, function(type) {
                            if (typeof data[type] !== "undefined") {
                                _.each(data[type], function(profile) {
                                    profile.profileType = type;
                                    profile.tagsAsString = LOCALE.list_and(profile.tags);

                                    // Initialize with a valid flag.
                                    profile.isValid = true;
                                    profile.showValidationWarning = false;
                                    if (!profile.active) {     // Active profile is shown separately.
                                        profile.isValid = _.isEmpty(profile.validation_data.not_on_server);
                                        if (!profile.isValid) {
                                            profile.validation_data.not_on_server_without_prefix = ea4Util.getFormattedPackageList(profile.validation_data.not_on_server);
                                        }
                                        profile.id = type + "_" + profile.path.replace(/\.json/, "");

                                        // If the type is other than cPanel Or Custom, it should be a vendor in which case the
                                        // path changes a bit.
                                        var pathByType = ( type !== "cpanel" && type !== "custom" ) ? "vendor/" + type : type;
                                        profile.downloadUrl = "ea4_profile_download/" + pathByType + "?filename=" + profile.path;
                                        $scope.profileList.push(profile);
                                    } else {
                                        $scope.activeProfile = profile;

                                        // need active profile packages in customize scope so can run packages updates
                                        $scope.customize.activeProfilePkgs = profile.pkgs;

                                        var recos = recommendationsOfActiveProfile($scope.activeProfile, recommendations);
                                        if (!_.isEmpty(_.keys(recos))) {
                                            $scope.activeProfile.showRecommendations = false;

                                            var actual_recos = _.pickBy(recos, function(value, key) {
                                                return recos[key].show;
                                            });
                                            var recoCnt = 0;
                                            _.each(_.keys(actual_recos), function(key) {
                                                recoCnt += _.filter(actual_recos[key].recosList, ["show", true]).length;
                                            } );
                                            $scope.activeProfile.recommendations = actual_recos;
                                            $scope.activeProfile.recommendationLabel = LOCALE.maketext("[quant,_1,Recommendation,Recommendations]", recoCnt);
                                            $scope.activeProfile.recommendationsExist = recoCnt ? true : false;
                                        } else {
                                            $scope.activeProfile.recommendations = {};
                                        }
                                    }
                                });
                            }
                        });

                        // Check if there are any profiles.
                        $scope.noProfiles = ($scope.profileList.length <= 0);

                        // Active Profile. At present active profile will always be 'Currently Installed Packages'
                        // This may change in future.
                        // TODO: Add this method to ea4Util
                        var tags = ea4Util.createTagsForActiveProfile($scope.activeProfile.pkgs);
                        $scope.activeProfile.tags = tags;
                        $scope.activeProfile.tagsAsString = LOCALE.list_and(tags);
                    };

                    /**
                     * Error handling method for profile load failures.
                     *
                     * @method showProfileErrors
                     */
                    var showProfileErrors = function(error) {
                        $scope.errorOccurred = true;
                        alertService.add({
                            type: "danger",
                            message: error,
                            id: "alertMessages",
                            closeable: false,
                        });
                    };
                },
            ]
        );

        app.controller("ModalInstanceCtrl",
            ["$scope", "$uibModalInstance", "data", "ea4Util",
                function($scope, $uibModalInstance, data, ea4Util) {
                    $scope.modalData = {};
                    var profileInfo = data;
                    profileInfo.pkgs = ea4Util.getProfilePackagesByCategories(profileInfo.pkgs);
                    $scope.modalData = profileInfo;

                    $scope.closeModal = function() {
                        $uibModalInstance.close();
                    };
                },
            ]
        );
    }
);
Back to Directory File Manager