Viewing File: /usr/local/cpanel/base/frontend/jupiter/version_control/services/versionControlService.js

/*
 * versionControlService.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, PAGE: false */
define(
    [
        "angular",
        "cjt/util/locale",
        "cjt/util/parse",
        "cjt/io/uapi-request",
        "cjt/io/api",
        "cjt/io/uapi",
        "cjt/services/APIService",
        "cjt/filters/qaSafeIDFilter"
    ],
    function(angular, LOCALE, PARSE, UAPIREQUEST) {
        "use strict";

        var app = angular.module("cpanel.versionControl.service", []);
        app.value("PAGE", PAGE);

        app.factory("versionControlService", [
            "$q",
            "APIService",
            "$filter",
            "PAGE",
            "$timeout",
            "$rootScope",
            function($q, APIService, $filter, PAGE, $timeout, $rootScope) {

                var VersionControlService = function() {};
                var repos = [];

                var fileManagerPath = PAGE.fileManagerURL;
                var gitWebPath = PAGE.gitwebURL;

                /* Parse Head commit information
                * @method parseHeadCommitInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns head commit information
                */
                function parseHeadCommitInformation(repoInfo) {

                    // HEAD commit
                    var commitInfo = {};

                    commitInfo.lastUpdateSHA = LOCALE.maketext("Not available.");
                    commitInfo.lastUpdateDate = LOCALE.maketext("Not available.");
                    commitInfo.commitMessage = LOCALE.maketext("Not available.");
                    commitInfo.author = LOCALE.maketext("Not available.");
                    commitInfo.hasHeadInformation = false;

                    if (typeof repoInfo.last_update !== "undefined" && repoInfo.last_update) {

                        commitInfo.hasHeadInformation = true;

                        if (typeof repoInfo.last_update.identifier !== "undefined" &&
                            repoInfo.last_update.identifier) {
                            commitInfo.lastUpdateSHA = repoInfo.last_update.identifier;
                        }

                        if (typeof repoInfo.last_update.date !== "undefined" &&
                            repoInfo.last_update.date) {
                            commitInfo.lastUpdateDate = getHumanReadableTime(repoInfo.last_update.date);
                        }

                        if (typeof repoInfo.last_update.author !== "undefined" &&
                            repoInfo.last_update.author) {
                            commitInfo.author = repoInfo.last_update.author;
                        }

                        if (typeof repoInfo.last_update.message !== "undefined" &&
                            repoInfo.last_update.message) {
                            commitInfo.commitMessage = repoInfo.last_update.message;
                        }
                    }

                    return commitInfo;
                }

                /* Parse Last Deployed Information
                * @method parseLastDeployedInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns last deployed information
                */
                function parseLastDeployedInformation(repoInfo) {

                    var deployedInfo = {};

                    // last deployed information
                    deployedInfo.hasDeploymentInformation = false;
                    deployedInfo.lastDeployedSHA = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedDate = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedCommitDate = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedCommitMessage = LOCALE.maketext("Not available.");
                    deployedInfo.lastDeployedAuthor = LOCALE.maketext("Not available.");

                    if (typeof repoInfo.last_deployment !== "undefined" && repoInfo.last_deployment) {

                        deployedInfo.hasDeploymentInformation = true;

                        if (typeof repoInfo.last_deployment.timestamps !== "undefined" &&
                            typeof repoInfo.last_deployment.timestamps.succeeded !== "undefined" &&
                            repoInfo.last_deployment.timestamps.succeeded) {
                            deployedInfo.lastDeployedDate = getHumanReadableTime(repoInfo.last_deployment.timestamps.succeeded);

                            if (typeof repoInfo.last_deployment.repository_state !== "undefined" &&
                                repoInfo.last_deployment.repository_state) {

                                if (typeof repoInfo.last_deployment.repository_state.identifier !== "undefined" &&
                                repoInfo.last_deployment.repository_state.identifier) {
                                    deployedInfo.lastDeployedSHA = repoInfo.last_deployment.repository_state.identifier;
                                }

                                if (typeof repoInfo.last_deployment.repository_state.date !== "undefined" &&
                                repoInfo.last_deployment.repository_state.date) {
                                    deployedInfo.lastDeployedCommitDate = getHumanReadableTime(repoInfo.last_deployment.repository_state.date);
                                }

                                if (typeof repoInfo.last_deployment.repository_state.message !== "undefined" &&
                                repoInfo.last_deployment.repository_state.message) {
                                    deployedInfo.lastDeployedCommitMessage = repoInfo.last_deployment.repository_state.message;
                                }

                                if (typeof repoInfo.last_deployment.repository_state.author !== "undefined" &&
                                repoInfo.last_deployment.repository_state.author) {
                                    deployedInfo.lastDeployedAuthor = repoInfo.last_deployment.repository_state.author;
                                }
                            }
                        }
                    }

                    return deployedInfo;
                }

                /* Render repository list
                * @method refineRepositoryInformation
                * @params {Object} repoInfo Repository Information
                * @return {Object} Returns a single formatted repository data instance.
                */
                function refineRepositoryInformation(repoInfo) {
                    if (repoInfo && typeof repoInfo !== "undefined") {

                        // For unique ids
                        repoInfo.qaSafeSuffix = $filter("qaSafeID")(repoInfo.repository_root);

                        // File manager url
                        if (fileManagerPath) {
                            repoInfo.fileManagerRedirectURL =  fileManagerPath + encodeURIComponent(repoInfo.repository_root);
                        } else {
                            repoInfo.fileManagerRedirectURL = "";
                        }

                        // gitweb url
                        if (gitWebPath) {
                            var projectPath = repoInfo.repository_root.replace(/^\/+/g, "") + "/.git";
                            repoInfo.gitWebURL = gitWebPath + encodeURIComponent(projectPath);
                        } else {
                            repoInfo.gitWebURL = "";
                        }

                        // has remote
                        repoInfo.hasRemote = typeof repoInfo.source_repository !== "undefined" &&  repoInfo.source_repository;

                        // Clone URL
                        if (typeof repoInfo.clone_urls !== "undefined" && repoInfo.clone_urls) {
                            repoInfo.cloneURL = repoInfo.clone_urls.read_write[0] || repoInfo.clone_urls.read_only[0];
                        }

                        // Branch information
                        repoInfo.activeBranch = LOCALE.maketext("Not available.");
                        repoInfo.hasActiveBranch = false;

                        if (typeof repoInfo.branch !== "undefined" && repoInfo.branch) {
                            repoInfo.activeBranch = repoInfo.branch;
                            repoInfo.hasActiveBranch = true;
                        }

                        // Head Commit information
                        var commitInfo = parseHeadCommitInformation(repoInfo);
                        repoInfo.lastUpdateSHA = commitInfo.lastUpdateSHA;
                        repoInfo.lastUpdateDate = commitInfo.lastUpdateDate;
                        repoInfo.commitMessage = commitInfo.commitMessage;
                        repoInfo.author = commitInfo.author;
                        repoInfo.hasHeadInformation = commitInfo.hasHeadInformation;

                        // Last deployed information
                        var lastDeployedInfo = parseLastDeployedInformation(repoInfo);
                        repoInfo.hasDeploymentInformation = lastDeployedInfo.hasDeploymentInformation;
                        repoInfo.lastDeployedSHA = lastDeployedInfo.lastDeployedSHA;
                        repoInfo.lastDeployedDate = lastDeployedInfo.lastDeployedDate;
                        repoInfo.lastDeployedCommitDate = lastDeployedInfo.lastDeployedCommitDate;
                        repoInfo.lastDeployedCommitMessage = lastDeployedInfo.lastDeployedCommitMessage;
                        repoInfo.lastDeployedAuthor = lastDeployedInfo.lastDeployedAuthor;

                        // Is deployable
                        repoInfo.deployable = PARSE.parsePerlBoolean(repoInfo.deployable);

                        repoInfo.cloneInProgress = false;
                        repoInfo.deployInProgress = false;

                        // Tasks (clone and deploy)
                        if (typeof repoInfo.tasks !== "undefined" &&
                            repoInfo.tasks &&
                            repoInfo.tasks.length > 0) {

                            for ( var i = 0, len = repoInfo.tasks.length; i < len; i++) {
                                if (repoInfo.tasks[i].action === "create") {
                                    repoInfo.cloneInProgress = true;
                                    repoInfo.cloneTaskID = repoInfo.tasks[i].id;
                                }

                                if (repoInfo.tasks[i].action === "deploy") {
                                    repoInfo.deployInProgress = true;
                                }
                            }
                        }

                    }
                    return repoInfo;
                }

                /* Convert Epoch Time to Human readable
                * @method getHumanReadableTime
                * @params {Number} epochTime Represents the DateTime in epoch format.
                * @return {String} Returns a human readable DateTime string.
                */
                function getHumanReadableTime(epochTime) {
                    if (!epochTime) {
                        return LOCALE.maketext("Not available.");
                    } else if (typeof epochTime !== "number") {
                        epochTime = Number(epochTime);
                        if ( isNaN(epochTime) ) {
                            return LOCALE.maketext("Not available.");
                        }
                    }

                    epochTime = Math.floor(epochTime);
                    return LOCALE.local_datetime(epochTime, "datetime_format_medium");
                }

                /* Modify Repository List
                * @method prepareList
                * @params {Array} data List of repositories
                * @return {Array} List of Repositories
                */
                function prepareList(data) {
                    var list = [];

                    if (typeof data !== "undefined" && data !== null ) {
                        for (var i = 0, len = data.length; i < len; i++) {
                            var value = refineRepositoryInformation(data[i]);
                            list.push(value);
                        }
                    }
                    return list;
                }

                /* Gets the index of specific repository in the list
                * @method getRepositoryIndex
                * @params {Array} reposList List of Repositories
                * @params {String} path Repository path to look for
                * @return {Number} Returns the index of the repository
                */
                function getRepositoryIndex(reposList, path) {
                    var index = null;
                    if (typeof reposList !== undefined && reposList) {
                        for (var repoIndex = 0, repoLength = reposList.length; repoIndex < repoLength; repoIndex++) {
                            if (reposList[repoIndex].repository_root === path) {
                                index = repoIndex;
                                break;
                            }
                        }
                    }

                    return index;
                }

                /* Copies supplied string to the user's clipboard
                * @method copyTextToClipboard
                * @params {String} text String to be copied to the user's clipboard.
                */
                function copyTextToClipboard(text) {
                    var textArea = document.createElement("textarea");
                    textArea.value = text;
                    document.body.appendChild(textArea);
                    textArea.select();
                    try {
                        document.execCommand("copy");
                    } catch (e) {
                        throw new Error(LOCALE.maketext("You cannot copy the “[_1]” clone [output,acronym,URL,Uniform Resource Locator] to the clipboard.", text));
                    }
                    document.body.removeChild(textArea);
                }

                // get repository list from PAGE data
                repos = prepareList(PAGE.repos);

                VersionControlService.prototype = new APIService();

                angular.extend(VersionControlService.prototype, {

                    /**
                    * Gets the list of cached repositories.
                    * Used for testing purposes
                    *
                    * @method getRepositoryList
                    * @return {Array} Array of cached repositories
                    */
                    getRepositoryList: function() {
                        return repos;
                    },

                    /**
                    * List repositories
                    *
                    * @method listRepositories
                    * @param {Boolean} forceLoad Forces the API request to take place, rather than pulling the data from the JS vars.
                    * @param {String} attributeStr Comma seperated list of fields that UI needs
                    * @return {Promise} When fulfilled, will return the list of repositories.
                    */
                    listRepositories: function(forceLoad, attributeStr) {

                        if (forceLoad) {
                            var apiCall = new UAPIREQUEST.Class();
                            apiCall.initialize("VersionControl", "retrieve");

                            if (attributeStr) {
                                apiCall.addArgument("fields", attributeStr);
                            }

                            return this.deferred(apiCall).promise
                                .then(function(response) {
                                    try {
                                        repos = prepareList(response.data);
                                        return $q.resolve(repos);
                                    } catch (err) {
                                        return $q.reject(err);
                                    }
                                })
                                .catch(function(error) {
                                    return $q.reject(error);
                                });
                        } else {
                            return $q.resolve(repos);
                        }
                    },

                    /**
                    * Gets information of a single repository
                    *
                    * @method getRepositoryInformation
                    * @param {String} repositoryPath Repository root path
                    * @param {String} attributeStr Fields to request
                    * @return {Promise} When fulfilled, will return repository information.
                    */
                    getRepositoryInformation: function(repositoryPath, attributeStr) {

                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "retrieve");

                        if (repositoryPath) {
                            apiCall.addFilter("repository_root", "eq", repositoryPath);
                            if (attributeStr) {
                                apiCall.addArgument("fields", attributeStr);
                            }
                        } else {
                            throw new Error(LOCALE.maketext("Repository path cannot be empty."));
                        }

                        var repository = [];
                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                try {
                                    repository = prepareList(response.data);

                                    if (repository && repository.length > 0) {
                                        return $q.resolve(repository[0]);
                                    } else {
                                        return $q.reject(LOCALE.maketext("The system could not find the “[_1]” repository.", repositoryPath));
                                    }

                                } catch (err) {
                                    return $q.reject(err);
                                }
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    },

                    /**
                    * Clone to Clipboard
                    *
                    * @method cloneToClipboard
                    * @param {String} cloneUrl The URL to be used to clone repos.
                    * @return {Boolean} Returns true on success.
                    */
                    cloneToClipboard: function(cloneUrl) {
                        if (typeof cloneUrl === "string" && cloneUrl !== "") {

                        // set clipboard to the clone URL
                            copyTextToClipboard(cloneUrl);
                            return true;

                        } else {

                        // throw dev error
                            throw new Error(LOCALE.maketext("“[_1]” is not a valid clone [output,acronym,URL,Universal Resource Locator].", cloneUrl));
                        }
                    },

                    /**
                    * Creates or clones repository
                    *
                    * @method createRepository
                    * @return {Promise} When fulfilled, returns created repository information.
                    */
                    createRepository: function(name, fullPath, cloneURL) {

                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "create");

                        apiCall.addArgument("name", name);
                        apiCall.addArgument("repository_root", fullPath);

                        apiCall.addArgument("type", "git");

                        // Creates a new repository when cloneURL is not provided
                        if (typeof cloneURL !== "undefined" && cloneURL) {
                            apiCall.addArgument("source_repository", JSON.stringify({
                                "remote_name": "origin",
                                "url": cloneURL
                            }));
                        }

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                var processedData = {};

                                // update the repository list with data returned from create
                                if (response.data) {
                                    processedData = refineRepositoryInformation(response.data);
                                    repos.push(processedData);
                                }

                                return processedData;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    },

                    /**
                    * Delete repository
                    *
                    * @method deleteRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    deleteRepository: function(repositoryRoot) {
                        if (typeof repositoryRoot === "string" && repositoryRoot !== "") {
                            var apiCall = new UAPIREQUEST.Class();

                            apiCall.initialize("VersionControl", "delete");
                            apiCall.addArgument("repository_root", repositoryRoot);
                            return this.deferred(apiCall).promise
                                .then(function() {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos.splice(index, 1);
                                    }
                                    return $q.resolve();
                                })
                                .catch(function(error) {
                                    return $q.reject(error);
                                });
                        }
                    },

                    /**
                    * Updates repository
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @param {String} repositoryName name of the repository
                    * @param {String} branch active branch name
                    * @return {Promise} Returns a promise.
                    */
                    updateRepository: function(repositoryRoot, repositoryName, branch) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "update");

                        apiCall.addArgument("repository_root", repositoryRoot);

                        if (typeof repositoryName === "string" && repositoryName) {
                            apiCall.addArgument("name", repositoryName);
                        }

                        if (typeof branch === "string" && branch) {
                            apiCall.addArgument("branch", branch);
                        }

                        return this.deferred(apiCall).promise
                            .then(function(response) {

                                if (response.data) {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos[index] = refineRepositoryInformation(response.data);
                                    }
                                }
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });

                    },

                    /**
                    * Updates changes from remote
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    updateFromRemote: function(repositoryRoot, branch) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControl", "update");

                        apiCall.addArgument("repository_root", repositoryRoot);
                        apiCall.addArgument("branch", branch || "");

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                if (response.data) {
                                    var index = getRepositoryIndex(repos, repositoryRoot);
                                    if (typeof index === "number" && index >= 0) {
                                        repos[index] = refineRepositoryInformation(response.data);
                                    }
                                }
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });

                    },

                    /**
                    * Deploy repository
                    *
                    * @method updateRepository
                    * @param {String} repositoryRoot repository root path
                    * @return {Promise} Returns a promise.
                    */
                    deployRepository: function(repositoryRoot) {
                        var apiCall = new UAPIREQUEST.Class();
                        apiCall.initialize("VersionControlDeployment", "create");

                        apiCall.addArgument("repository_root", repositoryRoot);

                        return this.deferred(apiCall).promise
                            .then(function(response) {
                                return response;
                            })
                            .catch(function(error) {
                                return $q.reject(error);
                            });
                    }
                });

                return new VersionControlService();
            }
        ]);
    }
);
Back to Directory File Manager