Viewing File: /usr/local/cpanel/base/frontend/jupiter/passenger/views/manage.js

/*
# passenger/views/manage.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
*/

/* global define: false */

/* eslint-disable camelcase */

define(
    [
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/util/table",
        "cjt/util/parse",
        "cjt/directives/actionButtonDirective",
        "cjt/decorators/paginationDecorator",
        "cjt/directives/toggleSortDirective",
        "cjt/directives/searchDirective",
        "cjt/directives/pageSizeDirective",
        "cjt/directives/alertList",
        "cjt/services/alertService",
        "cjt/services/viewNavigationApi",
        "cjt/directives/quickFiltersDirective",
        "app/services/sseAPIService",
        "uiBootstrap",
    ],
    function(angular, _, LOCALE, Table, PARSE) {
        "use strict";

        var app = angular.module("cpanel.applicationManager");
        app.value("PAGE", PAGE);
        var controller = app.controller(
            "ManageApplicationsController",
            [
                "$scope",
                "$routeParams",
                "viewNavigationApi",
                "$uibModal",
                "Apps",
                "defaultInfo",
                "alertService",
                "sseAPIService",
                "PAGE",
                "$timeout",
                function(
                    $scope,
                    $routeParams,
                    viewNavigationApi,
                    $uibModal,
                    Apps,
                    defaultInfo,
                    alertService,
                    sseAPIService,
                    PAGE,
                    $timeout) {
                    var manage = this;

                    manage.is_loading = false;
                    manage.applications = [];
                    manage.loading_error = false;
                    manage.loading_error_message = "";
                    manage.user_home_dir = defaultInfo.homedir;
                    manage.change_in_progress = false;
                    manage.ensureDepList = [];

                    manage.secTokenPrefix = PAGE.securityToken; // Initialize with security token.
                    manage.sseObj = null;

                    // SSE events and config
                    var events = ["task_processing", "task_complete", "task_failed"];
                    var sseConfig = { json: true };

                    var table = new Table();

                    function searchByName(item, searchText) {
                        return item.name.indexOf(searchText) !== -1;
                    }
                    function searchByType(item, type) {
                        return item.type === type;
                    }

                    table.setSearchFunction(searchByName);
                    table.setFilterOptionFunction(searchByType);

                    manage.meta = table.getMetadata();
                    manage.filteredList = table.getList();
                    manage.paginationMessage = table.paginationMessage;
                    manage.render = function() {
                        manage.filteredList = table.update();
                    };
                    manage.sortList = function() {
                        manage.render();
                    };
                    manage.selectPage = function() {
                        manage.render();
                    };
                    manage.selectPageSize = function() {
                        manage.render();
                    };
                    manage.searchList = function() {
                        manage.render();
                    };

                    manage.quota_warning = function() {
                        return LOCALE.maketext("You can’t have more than [numf,_1] applications on your account.", Apps.get_maximum_number_of_apps());
                    };

                    manage.show_quota_warning = function() {
                        return Apps.exceeds_quota();
                    };

                    manage.dependenciesExist = function(appl) {
                        var exist = false;
                        if (appl) {
                            var deps = appl.deps;
                            exist = _.some(deps, function(key) {
                                if (key) {
                                    return true;
                                }
                            });
                        }
                        return exist;
                    };

                    /**
                     * Resets ensure deps state of application
                     * @method ensureDependencies
                     * @param {Object} app application object
                     * @returns {Object} Promise
                     */
                    manage.ensureDependencies = function(app) {
                        var types = _.keys(app.deps);
                        _.each(types, function(type) {
                            if (app.deps[type]) {
                                return Apps.ensureDependencies(type, app.path)
                                    .then(function(res) {
                                        var sseUrl = res.data.sse_url;
                                        var ensureStarted = true;
                                        app.ensureInProgress = ensureStarted;
                                        app.showEnsureView = ensureStarted;
                                        if (app.ensureDeps) {
                                            app.ensureDeps[type] = {
                                                taskId: res.data.task_id,
                                                inProgress: ensureStarted,
                                            };
                                        }

                                        if (!manage.sseURL && !manage.sseObj) {
                                            manage.sseURL = manage.secTokenPrefix + sseUrl;
                                            sseAPIService.initialize();
                                        }

                                        manage.ensureDepList.push(app);
                                    })
                                    .catch(function(error) {
                                        alertService.add({
                                            type: "danger",
                                            message: error,
                                            closeable: true,
                                            replace: false,
                                            group: "passenger",
                                        });
                                    });
                            }
                        });
                    };

                    /**
                     * Resets ensure deps state of application
                     * @method resetEnsureDependencyParams
                     * @param {Object} appObject application
                     * @returns {Object} updated application object
                     */
                    function resetEnsureDependencyParams(appObject) {
                        if (appObject) {
                            appObject.ensureDeps = {};
                            appObject.ensureState = "";
                            appObject.ensureInProgress = false;
                            appObject.showEnsureView = false;
                        }
                        return appObject;
                    }

                    /**
                     * Handles the close action of failed callout of ensure dependency.
                     * @method clearEnsureDepsTaskParams
                     * @param {Object} appObject application object
                     */
                    manage.clearEnsureDepsTaskParams = function(appObject) {
                        resetEnsureDependencyParams(appObject);
                    };

                    /**
                     * Return the appropriate message depending on application level ensure state.
                     *
                     * @method getAppLevelEnsureStateMessage
                     * @param {Object} appl application object
                     */
                    manage.getAppLevelEnsureStateMessage = function(appl) {
                        var msg = "";
                        switch (appl.ensureState) {
                            case "processing":
                                msg = LOCALE.maketext("Ensuring dependencies for your application …");
                                break;
                            case "complete":
                                msg = LOCALE.maketext("The system ensured the dependencies for your application.");
                                break;
                            case "failure":
                                msg = LOCALE.maketext("The system couldn’t ensure dependencies for your application. For more information, see the instructions below.");
                                break;
                            default:
                                msg = LOCALE.maketext("The system queued your application to ensure its dependencies …");
                                break;
                        }
                        return msg;
                    };

                    /**
                     * Determines font awesome icon to show based on individual package types' state
                     * (i.e. gem/pip/npm etc. types)
                     *
                     * @method getIconClassForEnsureState
                     * @param {String} ensureState ensure state of a package type.
                     */
                    manage.getIconClassForEnsureState = function(ensureState) {
                        var strClass = "";
                        switch (ensureState) {
                            case "complete":
                                strClass = "far fa-check-circle text-success";
                                break;
                            case "failure":
                                strClass = "fas fa-exclamation-circle text-danger";
                                break;
                            default:
                                strClass = "fas fa-spinner fa-spin";
                                break;
                        }
                        return strClass;
                    };

                    /**
                     * Retrieves the application object that owns the task given the task id.
                     *
                     * @method getTaskOwner
                     * @param {String} taskId the task id of the package type that is being ensured.
                     */
                    var getTaskOwner = function(taskId) {
                        var ensureType = "";
                        var applicationItem = _.find(manage.ensureDepList, function(app) {
                            var types = _.keys(app.ensureDeps);
                            if (types.length > 0) {
                                return _.some(app.ensureDeps, function(value, type) {
                                    if (value.taskId === taskId) {
                                        ensureType = type;
                                        return true;
                                    }
                                });
                            }
                        });

                        return { ensureType: ensureType, applicationItem: applicationItem };
                    };

                    /**
                     * Determines what is the application's ensure state
                     * depending on the individual package types state (i.e. gem/pip/npm etc. types)
                     *
                     * @method getAppLevelEnsureState
                     * @param {Object} app application object
                     * @return {String} ensureState - Final determined ensureState at the application level.
                     */
                    var getAppLevelEnsureState = function(app) {
                        var ensureState = app.ensureState;

                        // The ensure state at the application level will be marked as 'complete'
                        // only if all types in a given application are complete. But if at least one
                        // type fails, then the application's ensure state is failure.
                        var type = _.findKey(app.ensureDeps, function(value) {
                            return value.ensureState !== "complete";
                        });
                        if (type) {
                            ensureState = app.ensureDeps[type].ensureState;
                        } else {
                            ensureState = "complete";
                        }
                        return ensureState;
                    };

                    /**
                     * Determines the progress of ensure process at the application level
                     * depending on the individual package types progress state (i.e. gem/pip/npm etc. types)
                     *
                     * @method getAppLevelEnsureProgress
                     * @param {Object} app application object
                     * @return {Boolean} ensureProgress - Final determined progress state at the application level.
                     */
                    var getAppLevelEnsureProgress = function(app) {
                        var ensureProgress = app.ensureInProgress;
                        var typeInProgress = _.findKey(app.ensureDeps, function(value) {
                            return value.inProgress;
                        });
                        if (typeInProgress) {
                            ensureProgress = true;
                        } else {
                            ensureProgress = false;
                        }
                        return ensureProgress;
                    };

                    /**
                     * Handles task_processing.
                     *
                     * @method
                     * @param {sse:task_processing} event - Task processing event.
                     * @param {Object} data - Data
                     * @listens sse:task_processing
                     */
                    $scope.$on("sse:task_processing", function(event, data) {
                        var taskID = data.task_id;
                        var taskOwner = getTaskOwner(taskID);

                        if (taskOwner.applicationItem) {
                            var unfilteredIndex = _.indexOf(manage.filteredList, taskOwner.applicationItem);

                            if (unfilteredIndex !== -1) {
                                taskOwner.applicationItem.ensureDeps[taskOwner.ensureType].ensureState = "processing";

                                // Application level ensureState is set to processing if at least one of the types is in processing.
                                taskOwner.applicationItem.ensureInProgress = true;
                                taskOwner.applicationItem.ensureState = "processing";
                                _.extend(manage.filteredList[unfilteredIndex], taskOwner.applicationItem);
                                $scope.$apply(manage.render);
                            }
                        }
                    });

                    /**
                     * Close and reset the SSE connection
                     *
                     * @method close_and_reset_SSE
                     */
                    var close_and_reset_SSE = function() {
                        if (manage.sseObj) {
                            sseAPIService.close(manage.sseObj);
                            manage.sseObj = null;
                            manage.sseURL = "";
                        }
                    };

                    /**
                     * Update the params that are used in the ensure dependency view.
                     *
                     * @method updateEnsureViewParams
                     * @param {Object} taskOwner - { ensureType, applicationItem }
                     * @param {String} eventState
                     */
                    var updateEnsureViewParams  = function(taskOwner, eventState) {
                        var unfilteredIndex = _.indexOf(manage.filteredList, taskOwner.applicationItem);

                        if (unfilteredIndex !== -1) {
                            taskOwner.applicationItem.ensureDeps[taskOwner.ensureType].ensureState = eventState;
                            taskOwner.applicationItem.ensureDeps[taskOwner.ensureType].inProgress = false;
                            var appLevelEnsureProgress = getAppLevelEnsureProgress(taskOwner.applicationItem);
                            taskOwner.applicationItem.ensureInProgress = appLevelEnsureProgress;

                            // This means all types are processed and should be either success or failure at this point.
                            if (!appLevelEnsureProgress) {
                                taskOwner.applicationItem.ensureState = getAppLevelEnsureState(taskOwner.applicationItem);
                            }
                            if (eventState === "failure") {
                                taskOwner.applicationItem.ensureDeps[taskOwner.ensureType].command = taskOwner.applicationItem.deps[taskOwner.ensureType];
                            }
                            _.extend(manage.filteredList[unfilteredIndex], taskOwner.applicationItem);
                            $scope.$apply(manage.render);
                        }

                        // removing object from ensure list after the action is done (success or failure)
                        if (!taskOwner.applicationItem.ensureInProgress &&
                            (taskOwner.applicationItem.ensureState === "complete" || taskOwner.applicationItem.ensureState === "failure")) {
                            _.remove(manage.ensureDepList, taskOwner.applicationItem);
                            if (taskOwner.applicationItem.ensureState === "complete") {
                                $timeout(function() {

                                    // updating the ensure view with new information so that the row is active
                                    _.extend(manage.filteredList[unfilteredIndex], resetEnsureDependencyParams(taskOwner.applicationItem));
                                    $scope.$apply(manage.render);
                                }, 5000);
                            }
                        }
                        if (manage.ensureDepList.length === 0) {
                            close_and_reset_SSE();
                        }
                    };

                    /**
                     * Handles task_complete.
                     *
                     * @method
                     * @param {sse:task_complete} event - Task complete event.
                     * @param {Object} data - Data
                     * @listens sse:task_complete
                     */
                    $scope.$on("sse:task_complete", function(event, data) {
                        var taskID = data.task_id;
                        var taskOwner = getTaskOwner(taskID);

                        if (taskOwner.applicationItem) {

                            // The failed tasks are triggering 'complete' event as well. Skipping them here since
                            // failures are handled in 'sse:failure'.
                            if (taskOwner.applicationItem.ensureDeps[taskOwner.ensureType].ensureState === "failure") {
                                return;
                            }

                            updateEnsureViewParams(taskOwner, "complete");
                            close_and_reset_SSE();
                        }
                    });

                    /**
                     * Handles task_failed.
                     *
                     * @method
                     * @param {sse:task_failed} event - Task failed event.
                     * @param {Object} data - Data
                     * @listens sse:task_failed
                     */
                    $scope.$on("sse:task_failed", function(event, data) {
                        var taskID = data.task_id;
                        var taskOwner = getTaskOwner(taskID);

                        if (taskOwner.applicationItem) {
                            updateEnsureViewParams(taskOwner, "failure");
                            close_and_reset_SSE();
                        }
                    });

                    /**
                     * Handles ready.
                     *
                     * @method
                     * @param {sse:ready} event - Task ready event.
                     * @listens sse:ready
                     */
                    $scope.$on("sse:ready", function(event) {
                        manage.sseObj = sseAPIService.connect(manage.sseURL, events, sseConfig);
                    });

                    /**
                     * Handles destroy.
                     *
                     * @method
                     * @listens $destroy
                     */
                    $scope.$on("$destroy", function() {
                        close_and_reset_SSE();
                    });

                    manage.configure_details = function(appl) {
                        if (appl === void 0) {
                            viewNavigationApi.loadView("/details");
                        } else {
                            viewNavigationApi.loadView("/details/" + appl.name);
                        }
                    };

                    manage.toggle_status = function(app) {
                        manage.change_in_progress = true;

                        return Apps.toggle_application_status(app)
                            .then(function(application_data) {
                                if (application_data.enabled) {
                                    alertService.add({
                                        type: "success",
                                        message: LOCALE.maketext("You successfully enabled your application."),
                                        closeable: true,
                                        replace: false,
                                        autoClose: 10000,
                                        group: "passenger",
                                    });
                                } else {
                                    alertService.add({
                                        type: "success",
                                        message: LOCALE.maketext("You successfully disabled your application."),
                                        closeable: true,
                                        replace: false,
                                        autoClose: 10000,
                                        group: "passenger",
                                    });
                                }
                                app.enabled = PARSE.parsePerlBoolean(application_data.enabled);
                            })
                            .catch(function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: error,
                                    closeable: true,
                                    replace: false,
                                    group: "passenger",
                                });
                            })
                            .finally(function() {
                                manage.change_in_progress = false;
                            });
                    };

                    function RemoveRecordModalController($uibModalInstance, appl_name) {
                        var ctrl = this;

                        ctrl.confirm_msg = LOCALE.maketext("Are you sure that you want to unregister your application (“[_1]”)?", appl_name);

                        ctrl.cancel = function() {
                            $uibModalInstance.dismiss("cancel");
                        };
                        ctrl.confirm = function() {
                            return Apps.remove_application(appl_name)
                                .then(function() {
                                    table.setSort("name", "asc");
                                    _.remove(manage.applications, function(app) {
                                        return app.name === appl_name;
                                    });
                                    manage.render();
                                    alertService.add({
                                        type: "success",
                                        message: LOCALE.maketext("You successfully unregistered your application."),
                                        closeable: true,
                                        replace: false,
                                        autoClose: 10000,
                                        group: "passenger",
                                    });
                                })
                                .catch(function(error) {
                                    alertService.add({
                                        type: "danger",
                                        message: error,
                                        closeable: true,
                                        replace: false,
                                        group: "passenger",
                                    });
                                })
                                .finally(function() {
                                    $uibModalInstance.close();
                                });

                        };
                    }

                    RemoveRecordModalController.$inject = ["$uibModalInstance", "appl_name"];

                    manage.confirm_delete_record = function(applName) {
                        manage.change_in_progress = true;
                        var instance = $uibModal.open({
                            templateUrl: "confirm_delete.html",
                            controller: RemoveRecordModalController,
                            controllerAs: "ctrl",
                            resolve: {
                                appl_name: function() {
                                    return applName;
                                },
                            },
                        });
                        instance.result.finally(function() {
                            manage.change_in_progress = false;
                        });
                    };

                    manage.refresh = function() {
                        return load(true);
                    };

                    function load(force) {
                        if ($routeParams.hasOwnProperty("forceLoad") &&
                        $routeParams.forceLoad === 1) {
                            force = true;
                        } else if (force === void 0) {
                            force = false;
                        }

                        manage.is_loading = true;
                        return Apps.fetch(force)
                            .then(function(data) {
                                manage.applications = data;
                                if (manage.applications) {
                                    manage.applications = _.map(manage.applications, function(app) {

                                        // Init ensure dependency related data.
                                        app = resetEnsureDependencyParams(app);
                                        return app;
                                    });
                                }

                                table.setSort("name", "asc");
                                table.load(manage.applications);
                                manage.render();
                            })
                            .catch(function(error) {

                                // If we get an error at this point, we assume that the user
                                // should not be able to do anything on the page.
                                manage.loading_error = true;
                                manage.loading_error_message = error;
                            })
                            .finally(function() {
                                manage.is_loading = false;
                            });
                    }

                    manage.init = function() {
                        alertService.clear(void 0, "passenger");
                        load();
                    };

                    manage.init();
                },
            ]);

        return controller;
    }
);
Back to Directory File Manager