Viewing File: /usr/local/cpanel/base/frontend/jupiter/domains/views/manageDomain.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.

/* global define */

/** @namespace cpanel.domains.views.manageDomain */

define(
    [
        "angular",
        "lodash",
        "cjt/util/locale",
        "cjt/modules",
        "cjt/directives/callout",
        "cjt/directives/actionButtonDirective",
        "app/services/domains",
        "app/directives/domainSearchModalDirective",
        "cjt/services/cpanel/nvDataService",
    ],
    function(angular, _, LOCALE) {

        "use strict";

        var MODULE_NAMESPACE = "cpanel.domains.views.manageDomain";

        var app = angular.module(MODULE_NAMESPACE, [
            "cpanel.domains.domains.service",
            "ngRoute",
            "cjt2.services.alert",
        ]);

        /**
         * Create Controller for Domains
         *
         * @module manageDomain
         *
         * @param  {Object} $scope angular scope
         *
         */

        var controller = app.controller(
            "manageDomain",
            ["$scope", "$location", "$routeParams", "$q", "$timeout", "$sce", "domains", "alertService", "DOMAIN_TYPE_CONSTANTS", "PAGE", "nvDataService", "domainSearchModalService",
                function($scope, $location, $routeParams, $q, $timeout, $sce, $domainsService, $alertService, DOMAIN_TYPE_CONSTANTS, PAGE, nvDataService, domainSearchModalService) {

                    function init() {
                        if (!$scope.currentDomain) {
                            $alertService.add({
                                "message": LOCALE.maketext("You did not specify a domain to manage."),
                                "type": "danger",
                            });

                            $location.path("/").search("");
                        }

                        $scope.showAdvancedDetailFields = false;
                        $scope.showSubdomainInDetails = $scope.currentDomain.type === DOMAIN_TYPE_CONSTANTS.ADDON;

                        if (DOMAIN_TYPE_CONSTANTS.SUBDOMAIN === $scope.currentDomain.type) {

                            // Current type is a subdomain, if it's associated with an addon domain
                            // we'd have to delete that domain instead.

                            $domainsService.get().then(function(domains) {
                                domains.forEach(function(domain) {
                                    if (domain.subdomain === $scope.currentDomain.subdomain && domain.type === DOMAIN_TYPE_CONSTANTS.ADDON) {
                                        $scope.associatedDomains.push(domain);
                                    }
                                });
                            });
                        }

                        $scope.canUpdateDocumentRoot = $scope.currentDomain.type !== DOMAIN_TYPE_CONSTANTS.ALIAS;
                        $scope.workingDomain = _generateWorkingDomain();
                    }
                    function toggleReplaceDomainSection() {
                        $scope.replaceDomainLinkClicked = !$scope.replaceDomainLinkClicked;
                    }

                    function _generateWorkingDomain() {
                        var workingDomain = angular.copy($scope.currentDomain);
                        workingDomain.fullDocumentRoot = workingDomain.documentRoot;
                        workingDomain.renameDomainConfirmed = false;

                        // ensure documentRoot is consistent requirePublicHTMLSubs
                        var documentRoot = workingDomain.documentRoot;
                        var leadTrim = workingDomain.homedir;

                        if (documentRoot) {

                            // snip off the homedir
                            var regexp = new RegExp("^" + _.escapeRegExp(leadTrim) + "(/)?");
                            documentRoot = documentRoot.replace(regexp, "");

                            if ($scope.requirePublicHTMLSubs) {

                                // snip off public_html/
                                documentRoot = documentRoot
                                    .replace(/^public_html/, "")
                                    .replace(/^\//, "");
                            }

                            workingDomain.documentRoot = documentRoot;
                        }

                        return workingDomain;
                    }

                    function getFormFieldClasses(form) {
                        return form && !form.$pristine && form.$invalid ? "col-xs-12 col-md-6" : "col-xs-12";
                    }


                    function update(form, domainObject) {

                        var alertID = "updating_" + domainObject.domain;

                        var fullDocumentRoot = $domainsService.generateFullDocumentRoot(domainObject.documentRoot);

                        if (!domainObject.rootDomain) {

                            // This domain is missing its definition file, we should immediately error out
                            $alertService.add({
                                type: "danger",
                                id: alertID,
                                replace: true,
                                message: LOCALE.maketext("The domain userdata file for the “[_1]” domain appears to be missing.", domainObject.domain),
                            });
                            return;
                        }

                        function _updateSuccess() {
                            $alertService.add({
                                type: "success",
                                id: alertID,
                                replace: true,
                                message: LOCALE.maketext("You have successfully updated the document root to “[_1]” for the “[_2]” domain.", _.escape(fullDocumentRoot), domainObject.domain),
                            });

                            $scope.workingDomain = _generateWorkingDomain();
                        }

                        function _updateFailure() {
                            $alertService.removeById(alertID);
                        }

                        var promises = [];
                        if (!form.newDocumentRoot.$pristine) {
                            promises.push($domainsService.updateDocumentRoot(domainObject.domain, fullDocumentRoot).then(
                                _updateSuccess,
                                _updateFailure
                            ));
                        }

                        if (promises.length) {
                            return $q.all(promises);
                        }
                    }
                    function replace(domainObject) {
                        $scope.temporaryDomainReplacementPending = true;

                        const fullDocumentRoot = $domainsService.generateFullDocumentRoot(domainObject.documentRoot);
                        const submittingDomainObject = {
                            documentRoot: domainObject.documentRoot,
                            domain: domainObject.domain,
                            newDomainName: domainObject.newDomainName,
                        };

                        return $domainsService.replaceTemporaryDomain(submittingDomainObject)
                            .then(function() {
                                $alertService.add({
                                    type: "success",
                                    message: LOCALE.maketext("The domain “[_1]” has been converted to “[_2]”. Its document root is now “[_3]”.", submittingDomainObject.domain, submittingDomainObject.newDomainName, _.escape(fullDocumentRoot)),
                                    replace: false,
                                });

                                // Refresh currentDomain/workingDomain from the
                                // service cache so flags like canRename and
                                // is_temporary reflect the post-replace state.
                                // Without this, the user would have to fully
                                // reload the page before the Rename button
                                // appears for the now-registered domain.
                                var refreshed = $domainsService.findDomainByName(submittingDomainObject.newDomainName);
                                if (refreshed) {
                                    $scope.currentDomain = refreshed;
                                    $scope.workingDomain = _generateWorkingDomain();
                                }

                                $scope.replaceDomainLinkClicked = false;
                                $location.search("domain", submittingDomainObject.newDomainName);
                                $scope.temporaryDomainReplacementPending = false;
                            });
                    }

                    function _showDeletionDelayedMessage() {
                        $scope.deletionDelayed = true;
                    }

                    function removeDomain() {

                        if (!$scope.currentDomain.rootDomain) {

                            // This domain is missing it's definition file, we should immediately error out
                            $alertService.add({
                                type: "danger",
                                replace: true,
                                message: LOCALE.maketext("The domain userdata file for the “[_1]” domain appears to be missing.", $scope.currentDomain.domain),
                            });
                            return;
                        }

                        var $timer = $timeout(_showDeletionDelayedMessage, 1000);
                        $scope.domainRemovePending = true;

                        return $domainsService.remove($scope.currentDomain.domain).then(function() {
                            $alertService.add({
                                message: LOCALE.maketext("The system removed the “[_1]” domain and any associated redirections.", $scope.currentDomain.domain),
                                type: "success",
                            });

                            // Invalidate the domain cache so the next Manage
                            // page always loads fresh flags (canRemove, canRename)
                            // rather than stale data left over from mutations.
                            $domainsService.invalidate();
                            $location.path("/").search("");
                        }).finally(function() {
                            $scope.deletionDelayed = false;
                            $scope.domainRemovePending = false;
                            $timeout.cancel($timer);
                        });

                    }

                    function startRemovalConfirmation() {
                        $scope.confirmingRemoval = true;
                    }
                    function cancelRemoval() {
                        $scope.confirmingRemoval = false;
                    }
                    function toggleAdvancedDetailFields() {
                        $scope.showAdvancedDetailFields = !$scope.showAdvancedDetailFields;
                    }

                    function deleteDependentDomainsError(domain, associatedDomains) {
                        return LOCALE.maketext("The system created the “[_1]” subdomain when it created the “[_2]” domain. To delete this subdomain, you must first delete the following [numerate,_3,domain,domains]:", domain, associatedDomains[0].domain, associatedDomains.length);
                    }

                    function onUpdateNewDomainName() {
                        if (!$scope.workingDomain.newDomainName) {
                            return;
                        }
                        $scope.workingDomain.documentRoot = $scope.workingDomain.newDomainName;
                        $scope.workingDomain.fullDocumentRoot = $domainsService.generateFullDocumentRoot($scope.workingDomain.newDomainName);
                        $scope.workingDomain.subdomain = $scope.workingDomain.newDomainName;
                    }

                    function currentDomainIsTemporaryMainDomain() {
                        const currentDomainIsMainDomain = $scope.workingDomain.type === DOMAIN_TYPE_CONSTANTS.MAIN;
                        const mainDomainIsTemporary = $scope.mainDomain.is_temporary === true || String($scope.mainDomain.is_temporary) === "1";

                        return mainDomainIsTemporary && currentDomainIsMainDomain;
                    }

                    function toggleRenameDomainSection() {
                        $scope.renameDomainLinkClicked = !$scope.renameDomainLinkClicked;
                        if (!$scope.renameDomainLinkClicked) {

                            // Reset rename form state when collapsing the section
                            $scope.workingDomain.newDomainName = "";
                            $scope.workingDomain.renameDomainConfirmed = false;

                            // Restore focus to the trigger button so keyboard
                            // users do not lose their place in the page.
                            $timeout(function() {
                                var trigger = document.getElementById("rename_domain_link");
                                if (trigger) {
                                    trigger.focus();
                                }
                            });
                        } else {

                            // Move focus into the new-domain input for keyboard
                            // users; rely on $timeout to wait for ng-if to
                            // render the input.
                            $timeout(function() {
                                var input = document.getElementById("renameDomainName");
                                if (input) {
                                    input.focus();
                                }
                            });
                        }
                    }

                    function renameDomain(domainObject) {
                        var newName = domainObject.newDomainName;

                        if (!newName) {
                            return;
                        }

                        if (newName === domainObject.domain) {
                            $alertService.add({
                                type: "danger",
                                message: LOCALE.maketext("The new domain must differ from the current domain."),
                                replace: true,
                            });
                            return;
                        }

                        $scope.domainRenamePending = true;

                        var submittingDomainObject = {
                            domain: domainObject.domain,
                            newDomainName: newName,
                        };

                        return $domainsService.renameDomain(submittingDomainObject)
                            .then(function() {
                                var message;
                                if (domainObject.is_temporary) {
                                    message = LOCALE.maketext("The temporary domain “[_1]” has been replaced with “[_2]”.", submittingDomainObject.domain, submittingDomainObject.newDomainName);
                                } else {
                                    message = LOCALE.maketext("The domain “[_1]” has been renamed to “[_2]”.", submittingDomainObject.domain, submittingDomainObject.newDomainName);
                                }
                                $alertService.add({
                                    type: "success",
                                    message: message,
                                    replace: false,
                                });

                                // Update in-place — reflect new name in both working and current domain objects
                                $scope.workingDomain.domain = submittingDomainObject.newDomainName;
                                $scope.workingDomain.is_temporary = false;
                                $scope.currentDomain.domain = submittingDomainObject.newDomainName;
                                $scope.currentDomain.is_temporary = false;
                                $scope.workingDomain.newDomainName = "";
                                $scope.workingDomain.renameDomainConfirmed = false;
                                $scope.renameDomainLinkClicked = false;
                                $scope.domainRenamePending = false;

                                // Re-bind currentDomain to the refreshed cache
                                // entry so derived flags (canRename, canEdit,
                                // documentRoot, homedir) reflect the post-rename
                                // state without forcing a full page reload.
                                var refreshed = $domainsService.findDomainByName(submittingDomainObject.newDomainName);
                                if (refreshed) {
                                    $scope.currentDomain = refreshed;
                                    $scope.workingDomain = _generateWorkingDomain();
                                    $scope.workingDomain.newDomainName = "";
                                }

                                $location.search("domain", submittingDomainObject.newDomainName);
                            })
                            .catch(function(error) {
                                $scope.domainRenamePending = false;
                                $scope.workingDomain.renameDomainConfirmed = false;
                                throw error;
                            });
                    }

                    angular.extend($scope, {
                        associatedDomains: [],
                        deletionDelayed: false,
                        temporaryDomainReplacementPending: false,
                        domainRenamePending: false,
                        domainRemovePending: false,
                        renameDomainLinkClicked: false,
                        currentDomain: $domainsService.findDomainByName($routeParams["domain"]),
                        requirePublicHTMLSubs: PAGE.requirePublicHTMLSubs.toString() === "1",
                        webserverRoleAvailable: PAGE.hasWebServerRole,
                        mainDomain: $domainsService.getMainDomain(),
                        getFormFieldClasses: getFormFieldClasses,
                        documentRootPattern: $domainsService.getDocumentRootPattern(),
                        toggleAdvancedDetailFields: toggleAdvancedDetailFields,
                        update: update,
                        replace: replace,
                        renameDomain: renameDomain,
                        confirmingRemoval: false,
                        removeDomain: removeDomain,
                        startRemovalConfirmation: startRemovalConfirmation,
                        cancelRemoval: cancelRemoval,
                        deleteDependentDomainsError: deleteDependentDomainsError,
                        replaceDomainLinkClicked: false,
                        toggleReplaceDomainSection: toggleReplaceDomainSection,
                        toggleRenameDomainSection: toggleRenameDomainSection,
                        onUpdateNewDomainName: onUpdateNewDomainName,
                        currentDomainIsTemporaryMainDomain: currentDomainIsTemporaryMainDomain,

                    });

                    $scope.domainRecommendationsEnabled = !!PAGE.features.domain_recommendation;
                    $scope.domainRecommendations = [];
                    $scope.domainRecommendationPrice = "";
                    $scope.domainRecsLoading = false;
                    var cachedExploreData = null;
                    var recommendationsPromise = null;

                    $scope.domainRecsBannerDismissed = !!PAGE.domainRecsBannerDismissed;

                    if ($scope.domainRecommendationsEnabled && !$scope.domainRecsBannerDismissed && !$scope.currentDomain.is_temporary) {
                        var domainName = $domainsService.findDomainByName($routeParams["domain"]);
                        if (domainName && domainName.domain) {
                            $scope.domainRecsLoading = true;
                            recommendationsPromise = $domainsService.fetchRecommendations(domainName.domain)
                                .then(function(result) {
                                    $scope.domainRecommendations = result.banner.recommendations;
                                    if (result.banner.lowestPrice) {
                                        $scope.domainRecommendationPrice = result.banner.lowestPrice + " / " + LOCALE.maketext("year");
                                    }
                                    cachedExploreData = result.explore;
                                    return cachedExploreData;
                                })
                                .catch(function(error) {

                                    // Log so a genuine fetch/processing failure
                                    // (including a client-side timeout) stays
                                    // diagnosable rather than looking identical
                                    // to an empty result. The banner hides
                                    // itself when no recommendations load;
                                    // resolve to null so a modal opened
                                    // mid-flight stops loading too.
                                    console.error("Failed to load domain recommendations:", error);
                                    return null;
                                })
                                .finally(function() {
                                    $scope.domainRecsLoading = false;
                                });
                        }
                    }

                    $scope.dismissDomainRecommendations = function() {
                        $scope.domainRecsBannerDismissed = true;
                        nvDataService.set("domain_recs_banner_dismissed", "1", { nocache: true })
                            .catch(function(error) {
                                console.error("Failed to save banner dismissal:", error);
                            });
                    };
                    $scope.openExploreDomains = function() {
                        domainSearchModalService.open({
                            cachedExploreData: cachedExploreData,
                            source: "manage-domain-upsell-banner",
                            exploreDataPromise: recommendationsPromise,

                            // After a store purchase, send the user to the
                            // domain list and open its create-domain chooser
                            // on the "Registered Domain" step with the
                            // purchased name prefilled — mirroring the list
                            // page's own purchase → Add Domain flow. The
                            // chooser lives on the list
                            // view, so the domain is handed over through the
                            // domains service (not a query param, which would
                            // reload the route and discard the chooser), then
                            // we navigate to the list with no search params.
                            onAddDomain: function(domain) {
                                $domainsService.setPendingAddDomain(domain);
                                $location.path("/").search("");
                            },
                        });
                    };

                    init();

                },
            ]
        );

        return {
            namespace: MODULE_NAMESPACE,
        };
    }
);
Back to Directory