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

//                                      Copyright 2026 WebPros International, LLC
//                                                           All rights reserved.
// copyright@cpanel.net                                         http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.

/* jshint -W100 */

define(
    [
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/util/parse",
        "cjt/util/table",
        "cjt/io/api",
        "cjt/io/uapi-request",
        "cjt/io/api2-request",
        "app/directives/passenger_validators",
        "cjt/directives/toggleSwitchDirective",
        "cjt/directives/actionButtonDirective",
        "cjt/directives/toggleLabelInfoDirective",
        "cjt/directives/alertList",
        "cjt/directives/pageSizeDirective",
        "cjt/services/alertService",
        "cjt/services/cpanel/componentSettingSaverService",
        "cjt/services/viewNavigationApi",
        "cjt/directives/validationContainerDirective",
        "cjt/directives/validationItemDirective",
        "cjt/validator/ip-validators",
        "cjt/validator/domain-validators",
        "cjt/validator/compare-validators",
        "cjt/validator/datatype-validators",
        "cjt/validator/path-validators",
        "cjt/validator/length-validators",
        "cjt/services/viewNavigationApi",
        "app/directives/table_row_form",
        "uiBootstrap",
        "cjt/io/uapi",
    ],
    function(angular, _, LOCALE, PARSE, Table, API, APIREQUEST, API2REQUEST) {
        "use strict";

        var app = angular.module("cpanel.applicationManager");

        var controller = app.controller(
            "ConfigurationDetailsController",
            [
                "$routeParams",
                "$uibModal",
                "viewNavigationApi",
                "Apps",
                "Domains",
                "defaultInfo",
                "alertService",
                "componentSettingSaverService",
                function(
                    $routeParams,
                    $uibModal,
                    viewNavigationApi,
                    Apps,
                    Domains,
                    defaultInfo,
                    alertService,
                    componentSettingSaverService) {
                    var details = this;

                    details.is_loading = false;
                    details.domains = [];
                    details.user_home_dir = defaultInfo.homedir;
                    details.supports_env_vars = Apps.has_support_for_env_vars;
                    details.edit_mode = $routeParams.applname !== void 0;
                    details.editing_envar = false;
                    details.forceLoad = false;
                    details.isRTL = false;
                    details.savedSettings = {};

                    var SAVED_SETTINGS_DEFAULTS = {
                        showAllHelp: true,
                    };

                    // This is the dynamic portion of the form's description text
                    details.formDescription = details.edit_mode ?
                        LOCALE.maketext("You can use this section of the interface to manage your custom applications.") :
                        LOCALE.maketext("You can use this section of the interface to register a new application.");

                    var html = document.querySelector("html");
                    if (html) {
                        details.isRTL = html.getAttribute("dir") === "rtl";
                    }

                    details.get_page_title = function() {
                        if (details.edit_mode) {
                            return LOCALE.maketext("Edit");
                        } else {
                            return LOCALE.maketext("Register");
                        }
                    };

                    details.get_application_title = function() {
                        if (details.edit_mode) {
                            return LOCALE.maketext("Edit Your Application");
                        } else {
                            return LOCALE.maketext("Register Your Application");
                        }
                    };

                    var envarTable = new Table();

                    details.meta = envarTable.getMetadata();
                    details.filteredEnvars = envarTable.getList();
                    details.paginationMessage = envarTable.paginationMessage;
                    details.renderEnvars = function() {
                        details.filteredEnvars = envarTable.update();
                    };
                    details.sortList = function() {
                        details.renderEnvars();
                    };
                    details.selectPage = function() {
                        details.renderEnvars();
                    };
                    details.selectPageSize = function() {
                        details.renderEnvars();
                    };
                    details.searchList = function() {
                        details.renderEnvars();
                    };

                    details.goToView = function(view, forceLoad) {
                        viewNavigationApi.loadView(view, forceLoad ? { "forceLoad": 1 } : {} );
                    };

                    details.save_application = function() {
                        if (details.add_app.$pristine || details.add_app.$invalid) {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("Your application is incomplete or invalid."),
                                closeable: true,
                                replace: true,
                                autoClose: false,
                                group: "passenger",
                            });
                            return;
                        }
                        if (details.editing_envar) {
                            alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("You must finish adding or editing environment variables first."),
                                closeable: true,
                                replace: true,
                                autoClose: false,
                                group: "passenger",
                            });
                            return;
                        }

                        if (details.edit_mode) {
                            return details.save_edited_application();
                        } else {
                            return details.save_new_application();
                        }
                    };

                    details.save_edited_application = function() {
                        return Apps.update_application(details.application, details.previous_name)
                            .then(function() {
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("You successfully updated your application."),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "passenger",
                                });

                                // Force load required to make sure
                                // the manage view always reflects the reality.
                                details.goToView("manage", true);
                            })
                            .catch(function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: error,
                                    closeable: true,
                                    replace: false,
                                    group: "passenger",
                                });
                            });
                    };

                    details.save_new_application = function() {
                        return Apps.add_application(details.application)
                            .then(function() {
                                alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("You successfully registered your application."),
                                    closeable: true,
                                    replace: false,
                                    autoClose: 10000,
                                    group: "passenger",
                                });

                                // Force load required to make sure
                                // the manage view always reflects the reality.
                                details.goToView("manage", true);
                            })
                            .catch(function(error) {
                                alertService.add({
                                    type: "danger",
                                    message: error,
                                    closeable: true,
                                    replace: false,
                                    group: "passenger",
                                });
                            });
                    };

                    details.add_envar = function() {
                        envarTable.add({ "name": "", "value": "", "is_editing": true, "is_new": true });
                        details.editing_envar = true;
                        details.renderEnvars();
                    };

                    details.close_envar_editor = function(envar) {
                        envar.is_editing = false;
                        details.editing_envar = false;

                        // check if the user cancelled a new item
                        if (envar.is_new) {
                            envarTable.remove(envar);
                            details.renderEnvars();
                        }
                    };

                    details.save_envar = function(envar, name, value) {
                        if (envar.is_new) {
                            delete envar.is_new;
                        } else {

                            // check for a rename
                            if (name !== envar.name) {
                                delete details.application.envvars[envar.name];
                            }
                        }

                        envar.name = name;
                        envar.value = value;
                        details.application.envvars[name] = value;
                        details.close_envar_editor(envar);
                        details.renderEnvars();
                    };

                    details.delete_envar = function(envar) {
                        envarTable.remove(envar);
                        delete details.application.envvars[envar.name];
                        details.renderEnvars();
                        details.add_app.$setDirty();
                    };

                    details.edit_envar = function(envar) {
                        envar.is_editing = true;
                        details.editing_envar = true;
                    };

                    /**
                     * Toggles whether all help info blocks should be shown or not. Saves this
                     * preference to the user's personalization store.
                     */
                    details.toggleShowAllHelp = function() {
                        details.savedSettings.showAllHelp = !details.savedSettings.showAllHelp;
                        componentSettingSaverService.set("application_details", details.savedSettings);
                    };

                    function trackMixpanelEvent(eventName) {
                        if (window.mixpanel && typeof window.mixpanel.track === "function") {
                            try {
                                window.mixpanel.track(eventName);
                            } catch (e) { // eslint-disable-line no-unused-vars
                                // Mixpanel errors must never block UI interactions.
                            }
                        }
                    }

                    var SelectDirectoryModalController = [
                        "$uibModalInstance",
                        "$uibModal",
                        "$q",
                        "$timeout",
                        "$document",
                        "currentPath",
                        "homeDir",
                        function($uibModalInstance, $uibModal, $q, $timeout, $document, currentPath, homeDir) {
                            var ctrl = this;

                            var docNode = $document[0];

                            function decodeHtmlEntities(str) {
                                if (!str || typeof str !== "string") {
                                    return str || "";
                                }
                                var textarea = docNode.createElement("textarea");
                                textarea.innerHTML = str;
                                return textarea.value;
                            }

                            function normalizeRelativePath(path) {
                                if (typeof path !== "string") {
                                    path = "";
                                }
                                if (typeof homeDir === "string" && homeDir) {
                                    if (path === homeDir) {
                                        path = "";
                                    } else if (path.indexOf(homeDir + "/") === 0) {
                                        path = path.slice(homeDir.length);
                                    }
                                }
                                while (path.charAt(0) === "/") {
                                    path = path.slice(1);
                                }
                                if (!path) {
                                    return "";
                                }
                                var parts = path.split("/");
                                var safeParts = [];
                                for (var i = 0; i < parts.length; i++) {
                                    var part = parts[i];
                                    if (!part || part === ".") {
                                        continue;
                                    }
                                    if (part === "..") {
                                        if (safeParts.length) {
                                            safeParts.pop();
                                        }
                                        continue;
                                    }
                                    safeParts.push(part);
                                }
                                return safeParts.join("/");
                            }

                            ctrl.currentPath = normalizeRelativePath(currentPath || "");
                            ctrl.selectedDir = null;
                            ctrl.directories = [];
                            ctrl.isLoading = false;
                            ctrl.homeDir = homeDir;

                            ctrl.getDisplayPath = function() {
                                if (!ctrl.currentPath) {
                                    return "/" + (ctrl.selectedDir || "");
                                }
                                var path = "/" + ctrl.currentPath;
                                if (ctrl.selectedDir) {
                                    path += (ctrl.currentPath.slice(-1) === "/" ? "" : "/") + ctrl.selectedDir;
                                }
                                return path;
                            };

                            var _requestSeq = 0;

                            ctrl.errorMessage = null;

                            function getFullPath(path) {
                                var normalizedPath = normalizeRelativePath(path || "");
                                return homeDir + (normalizedPath ? "/" + normalizedPath : "");
                            }

                            function scrollSelectedDirectoryIntoView() {
                                $timeout(function() {
                                    var selectedButton = docNode.querySelector(".directory-browser-item.active .directory-browser-item-select");
                                    if (!selectedButton || !selectedButton.scrollIntoView) {
                                        return;
                                    }

                                    try {
                                        selectedButton.scrollIntoView({ block: "nearest" });
                                    } catch (e) { // eslint-disable-line no-unused-vars
                                        selectedButton.scrollIntoView(false);
                                    }
                                });
                            }

                            function createDirectory(path, folderName) {
                                var apiCall = new API2REQUEST.Class();
                                apiCall.initialize("Fileman", "mkdir");
                                apiCall.addArgument("path", getFullPath(path));
                                apiCall.addArgument("name", folderName);

                                return $q.when(API.promise(apiCall.getRunArguments()))
                                    .then(function(response) {
                                        response = response.parsedResponse;
                                        if (response.status) {
                                            if (angular.isArray(response.data)) {
                                                return response.data[0] || {};
                                            }
                                            return response.data || {};
                                        }

                                        return $q.reject(decodeHtmlEntities(response.error) || LOCALE.maketext("The system could not create the folder."));
                                    })
                                    .catch(function(error) {
                                        return $q.reject(error || LOCALE.maketext("The system could not create the folder."));
                                    });
                            }

                            ctrl.fetchDirectories = function(path, options) {
                                options = options || {};
                                ctrl.isLoading = true;
                                ctrl.selectedDir = null;
                                ctrl.directories = [];
                                ctrl.errorMessage = null;

                                var seq = ++_requestSeq;
                                var fullPath = getFullPath(path || "");

                                var apiCall = new APIREQUEST.Class();
                                apiCall.initialize("Fileman", "list_files");
                                apiCall.addArgument("dir", fullPath);
                                apiCall.addArgument("types", "dir");
                                apiCall.addArgument("show_hidden", 0);

                                return $q.when(API.promise(apiCall.getRunArguments()))
                                    .then(function(response) {
                                        if (seq !== _requestSeq) {
                                            return;
                                        }
                                        response = response.parsedResponse;
                                        if (response.status) {
                                            ctrl.directories = [];
                                            var files = response.data || [];
                                            for (var i = 0; i < files.length; i++) {
                                                if (files[i].type === "dir" && files[i].file !== "." && files[i].file !== "..") {
                                                    ctrl.directories.push({ name: files[i].file });
                                                }
                                            }
                                            ctrl.directories.sort(function(a, b) {
                                                return a.name.localeCompare(b.name);
                                            });
                                            if (options.selectedDir) {
                                                for (var j = 0; j < ctrl.directories.length; j++) {
                                                    if (ctrl.directories[j].name === options.selectedDir) {
                                                        ctrl.selectedDir = options.selectedDir;
                                                        scrollSelectedDirectoryIntoView();
                                                        break;
                                                    }
                                                }
                                            }
                                        } else {
                                            if (path) {
                                                ctrl.currentPath = "";
                                                ctrl.fetchDirectories("");
                                            } else {
                                                ctrl.errorMessage = decodeHtmlEntities(response.error) || LOCALE.maketext("Failed to load directories.");
                                            }
                                        }
                                    })
                                    .catch(function() {
                                        if (seq !== _requestSeq) {
                                            return;
                                        }
                                        ctrl.errorMessage = LOCALE.maketext("Failed to load directories.");
                                    })
                                    .finally(function() {
                                        if (seq === _requestSeq) {
                                            ctrl.isLoading = false;
                                        }
                                    });
                            };

                            ctrl.navigateTo = function(path) {
                                ctrl.currentPath = normalizeRelativePath(path || "");
                                ctrl.fetchDirectories(ctrl.currentPath);
                            };

                            ctrl.navigateToParent = function() {
                                if (!ctrl.currentPath) {
                                    return;
                                }
                                var parts = ctrl.currentPath.split("/").filter(Boolean);
                                parts.pop();
                                var parentPath = parts.join("/");
                                ctrl.navigateTo(parentPath);
                            };

                            ctrl.hasParentDirectory = function() {
                                return !!ctrl.currentPath;
                            };

                            ctrl.navigateInto = function(dirName, $event) {
                                if ($event) {
                                    $event.stopPropagation();
                                }
                                var newPath = ctrl.currentPath ? ctrl.currentPath + "/" + dirName : dirName;
                                ctrl.currentPath = normalizeRelativePath(newPath);
                                ctrl.fetchDirectories(ctrl.currentPath);
                            };

                            ctrl.selectDirectory = function(dirName) {
                                ctrl.selectedDir = (ctrl.selectedDir === dirName) ? null : dirName;
                            };

                            ctrl.openCreateFolderModal = function() {
                                var instance = $uibModal.open({
                                    templateUrl: "create_directory.html",
                                    ariaLabelledBy: "createDirectoryModalLabel",
                                    controller: [
                                        "$uibModalInstance",
                                        function($uibModalInstance) {
                                            var createFolder = this;

                                            createFolder.errorMessage = null;
                                            createFolder.folderName = "";
                                            createFolder.isSubmitting = false;

                                            createFolder.cancel = function() {
                                                $uibModalInstance.dismiss("cancel");
                                            };

                                            createFolder.submit = function() {
                                                if (createFolder.isSubmitting) {
                                                    return;
                                                }

                                                var trimmedName = (createFolder.folderName || "").trim();
                                                if (!trimmedName) {
                                                    createFolder.errorMessage = LOCALE.maketext("Enter a folder name.");
                                                    return;
                                                }

                                                createFolder.errorMessage = null;
                                                createFolder.isSubmitting = true;

                                                createDirectory(ctrl.currentPath, trimmedName)
                                                    .then(function(response) {
                                                        $uibModalInstance.close(response.name || trimmedName);
                                                    })
                                                    .catch(function(error) {
                                                        createFolder.errorMessage = error;
                                                    })
                                                    .finally(function() {
                                                        createFolder.isSubmitting = false;
                                                    });
                                            };

                                            $timeout(function() {
                                                var input = docNode.getElementById("newFolderNameInput");
                                                if (input && input.focus) {
                                                    input.focus();
                                                }
                                            });
                                        },
                                    ],
                                    controllerAs: "ctrl",
                                    size: "sm",
                                });

                                instance.result.then(function(createdDirName) {
                                    ctrl.fetchDirectories(ctrl.currentPath, { selectedDir: createdDirName });
                                }, angular.noop);
                            };

                            ctrl.cancel = function() {
                                $uibModalInstance.dismiss("cancel");
                            };

                            ctrl.selectAndClose = function() {
                                trackMixpanelEvent("CPANEL_USR:APPLICATION_MANAGER-REGISTER_APPLICATION-SELECT_DIRECTORY-CLICKED");
                                var resultPath = ctrl.currentPath || "";
                                if (ctrl.selectedDir) {
                                    resultPath = resultPath ? resultPath + "/" + ctrl.selectedDir : ctrl.selectedDir;
                                }
                                $uibModalInstance.close(resultPath);
                            };

                            ctrl.fetchDirectories(ctrl.currentPath);
                        },
                    ];

                    details.openDirectoryBrowser = function() {
                        trackMixpanelEvent("CPANEL_USR:APPLICATION_MANAGER-REGISTER_APPLICATION-SELECT_PATH-CLICKED");

                        var instance = $uibModal.open({
                            templateUrl: "select_directory.html",
                            ariaLabelledBy: "selectDirectoryModalLabel",
                            controller: SelectDirectoryModalController,
                            controllerAs: "ctrl",
                            resolve: {
                                currentPath: function() {
                                    return details.application.path || "";
                                },
                                homeDir: function() {
                                    return details.user_home_dir;
                                },
                            },
                        });

                        instance.result.then(function(selectedPath) {
                            details.application.path = selectedPath;
                            details.add_app.$setDirty();
                        }, angular.noop);
                    };

                    details.init = function() {
                        alertService.clear(void 0, "passenger");
                        var savedSettings = componentSettingSaverService.getCached("application_details").cachedValue;
                        _.merge(details.savedSettings, SAVED_SETTINGS_DEFAULTS, savedSettings || {});

                        if (details.edit_mode) {
                            details.is_loading = true;
                            Apps.get_application_by_name($routeParams.applname)
                                .then(function(data) {
                                    details.application = angular.copy(data);
                                    details.application = Apps.strip_homedir_from_path(details.application);
                                    var arrayForTable = [];
                                    _.forOwn(details.application.envvars, function(envarValue, envarKey) {
                                        arrayForTable.push({
                                            "name": envarKey,
                                            "value": envarValue,
                                            "is_editing": false,
                                        });
                                    });
                                    envarTable.load(arrayForTable);
                                    details.renderEnvars();
                                    details.previous_name = details.application.name;
                                    details.is_loading = false;
                                });
                        } else {
                            if (Apps.applications.length === 0) {
                                details.forceLoad = true;
                            }
                            details.application = Apps.get_default_application();
                        }

                        Domains.fetch()
                            .then(function(data) {
                                details.domains = data;
                            });

                        envarTable.setSort("name", "asc");
                    };

                    details.init();
                },
            ]);

        return controller;
    }
);
Back to Directory