Viewing File: /usr/local/cpanel/base/frontend/jupiter/backup/index.js

/*
# cpanel - base/frontend/jupiter/backup/index.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
*/

(function() {
    "use strict";

    /* global YAHOO, document */

    /**
     * List of file validators and if they are currently enabled.
     * These states are used with the validation control function returned by makeValidatorControlFunction().
     * @type {Object}
     */
    var enableFileValidator = {
        "restore-home-file": true,
        "restore-db-file": true,
        "restore-forwarder-file": true,
        "restore-filter-file": true,
    };

    /**
     * List of forms and the handlers for that forms submit action.
     * Each name is an id on the actual form.
     * @type {Object}
     */
    var uploads = {
        "restore-db-form": handleDatabaseRestore,
        "restore-home-form": handleHomeFilesRestore,
        "restore-forwarder-form": handleEmailForwardersRestore,
        "restore-filter-form": handleEmailFiltersRestore,
    };

    /**
     * List of <input type="file"> ids vs the forms related submit buttons.
     *
     * Note: the file ids must match a corresponding {id}_error element where
     * the cjt2 validation system outputs validation errors.
     *
     * @type {Object}
     */
    var uploadButtonIds = {
        "restore-home-file": "restore-home-submit-button",
        "restore-db-file": "restore-db-submit-button",
        "restore-forwarder-file": "restore-forwarder-submit-button",
        "restore-filter-file": "restore-filter-submit-button",
    };

    /**
     * Check if the browser supports the features we need.
     *
     * This checks for:
     *
     *   * Promise
     *   * fetch
     *
     * @return {Boolean} true when it supports the needed features, false otherwise.
     */
    function browserSupportsNeededFeatures() {
        return window.Promise && window.fetch;
    }

    /**
     * Load a list of scripts and call the done() callback when all the scripts
     * have loaded.
     *
     * @param  {string[]} scripts List of urls to load the specified scripts.
     * @param  {Function} done    Callback method with the signature:
     *
     *  function done(error) {
     *      if(error) {
     *          // one of the scripts failed to load
     *      }
     *      // Do the thing that depends on the scripts.
     *  }
     */
    function loadScripts(scripts, done) {
        var scriptsLoaded = {};

        /**
         * Hide the AMD system while the polyfill loads
         */
        var hideAmd = function() {
            if (window.define) {
                window.defineHide = window.define;
                window.define = null;
            }
        };

        /**
         * Restore the AMD system after the polyfills are loaded.
         */
        var restoreAmd = function() {
            if (window.defineHide) {
                window.define = window.defineHide;
                delete window.defineHide;
            }
        };

        /**
         * Helper to check if all the desired scripts are loaded.
         *
         * @param  {Error} error This parameter is passed to the callback if
         *                       the load fails for some reason.
         */
        var checkIfPolyfillsLoaded = function(error) {
            var doneCount = 0;
            var calledBack = false;
            for (var index in scripts) {
                if (scripts.hasOwnProperty(index)) {
                    var script = scripts[index];
                    if (scriptsLoaded[script] === true) {
                        doneCount++;
                    } else if (scriptsLoaded[script] instanceof Error) {
                        if (!calledBack) {
                            calledBack = true;
                            restoreAmd();
                            done(scriptsLoaded[script]);
                        }
                    }
                }
            }

            if (doneCount === scripts.length) {
                if (!calledBack) {
                    calledBack = true;
                    restoreAmd();
                    done();
                }
            }
        };

        /**
         * Helper to build a load callback function.
         *
         * @param  {string} script
         * @return {function} a load callback function with the state captured.
         */
        var loadFactory = function(script) {
            return function() {
                scriptsLoaded[script] = true;
                checkIfPolyfillsLoaded();
            };
        };

        /**
         * Helper to build a error callback function.
         *
         * @param  {string} script
         * @return {function} an error callback function with the state captured.
         */
        var errorFactory = function(script) {
            return function() {
                scriptsLoaded[script] = new Error("Failed to load script " + script);
                checkIfPolyfillsLoaded();
            };
        };

        // Move define/amd out of the way.
        hideAmd();

        // Load the scripts
        for (var index in scripts) {
            if (scripts.hasOwnProperty(index)) {
                var script = scripts[index];
                var js = document.createElement("script");
                js.src = script;
                js.onload = loadFactory(script);
                js.onerror = errorFactory(script);
                document.head.appendChild(js);
            }
        }
    }

    /**
     * Create an error message block from the template on the page.
     *
     * @param  {string}   message Error message to inject into the template.
     * @param  {string}   id      Base id to use for the notices various elements.
     * @param  {string[]} rest    Additional error details.
     * @return {string}           Html blob ready to inject into an elements innerHTML.
     */
    function makeError(message, id, rest) {
        var source   = document.getElementById("error-template").innerHTML;
        var template = Handlebars.compile(source);
        return template({ id: id, message: message, rest: rest });
    }

    /**
     * Create an success message block from the template on the page.
     *
     * @param  {string} message Success message to inject into the template.
     * @param  {string} id      Base id to use for the notices various elements.
     * @return {string}         Html blob ready to inject into an elements innerHTML.
     */
    function makeSuccess(message, id) {
        var source   = document.getElementById("success-template").innerHTML;
        var template = Handlebars.compile(source);
        return template({ id: id, message: message });
    }

    /**
     * Send the file to the api via the fetch API.
     * @param  {FileInputElement} inputFile
     * @param  {string} url       url to post the file too.
     * @return {Promise}          promise that when resolved will report how the API submission went.
     */
    function handleUpload(inputFile, url) {
        var formData = new FormData();
        formData.append("file1", inputFile.files[0]);

        return fetch(url, {
            method: "POST",
            body: formData,
        }).then(function(response) {
            return response.json();
        });
    }

    /**
     * Show an element on the page.
     * @param  {Element} el  Reference to a dom element on the page.
     * @param  {string}  def Default way to show the element. If not provided it defaults to 'block'.
     */
    function show(el, def) {
        if (def === undefined) {
            def = "block";
        }
        el.style.display = def;
    }

    /**
     * Hide an element on the page.
     * @param  {Element} el  Reference to a dom element on the page.
     */
    function hide(el) {
        el.style.display = "none";
    }

    /**
     * Initialize the notice so. This sets up the close hander.
     *
     * @param  {Element}  el       Reference to a dom container element on the page.
     * @param  {string}   id       Partial id of the close button
     * @param  {Function} callback Callback to call when the close button is clicks.
     */
    function initMessageBlock(el, id, callback) {
        var btnClose = el.querySelector("#" + id + "-close");
        btnClose.addEventListener("click", function(e) {
            el.innerHTML = "";
            if (callback && typeof (callback) === "function") {
                callback();
            }
        }, false);
    }

    /**
     * Event handler for the submission of the restore database form.
     *
     * @param  {Event} e
     * @return {Boolean}   always false.
     */
    function handleDatabaseRestore(e) {
        e.preventDefault();
        var url = PAGE.session + "/execute/Backup/restore_databases";
        uploadTo(url, "db", PAGE.restoringDb);
        return false;
    }

    /**
     * Event handler for the submission of the restore home directory form.
     *
     * @param  {Event} e
     * @return {Boolean}   always false.
     */
    function handleHomeFilesRestore(e) {
        e.preventDefault();
        var url = PAGE.session + "/execute/Backup/restore_files";
        uploadTo(url, "home", PAGE.restoringHome);
        return false;
    }

    /**
     * Event handler for the submission of the restore email forwarders form.
     *
     * @param  {Event} e
     * @return {Boolean}   always false.
     */
    function handleEmailForwardersRestore(e) {
        e.preventDefault();
        var url = PAGE.session + "/execute/Backup/restore_email_forwarders";
        uploadTo(url, "forwarder", PAGE.restoringForwarders);
        return false;
    }

    /**
     * Event handler for the submission of the restore email filters form.
     *
     * @param  {Event} e
     * @return {Boolean}   always false.
     */
    function handleEmailFiltersRestore(e) {
        e.preventDefault();
        var url = PAGE.session + "/execute/Backup/restore_email_filters";
        uploadTo(url, "filter", PAGE.restoringFilters);
        return false;
    }

    /**
     * Perform the upload asynchronously.
     *
     * @param  {String} url  Api url to upload to.
     * @param  {String} kind Usually shorter name: db, home, filter, forwarder
     * @param  {String} msg  Translated message while the spinner is running.
     */
    function uploadTo(url, kind, msg) {
        enableFileValidator["restore-" + kind + "-file"] = false;

        var inputFile = document.getElementById("restore-" + kind + "-file");
        var divFields = document.getElementById("restore-" + kind + "-fields");
        var btnSubmit = document.getElementById("restore-" + kind + "-submit-button");
        var message   = document.getElementById("restore-" + kind + "-message");
        var spinner   = document.getElementById("restore-" + kind + "-spinner");

        message.innerHTML = "";
        btnSubmit.disabled = true;
        message.innerText = msg;
        hide(divFields);
        show(spinner, "inline");
        handleUpload(inputFile, url)
            .then(function(response) {
                hide(spinner);
                if (response.status == 1) { // eslint-disable-line eqeqeq
                    message.innerHTML = makeSuccess(response.messages[response.messages.length - 1], "success-" + kind + "-restore");
                    initMessageBlock(message, "success-" + kind + "-restore");
                    show(divFields);
                    inputFile.value = "";
                } else {
                    message.innerHTML = makeError(response.errors.shift(), "failed-" + kind + "-restore", response.errors);
                    initMessageBlock(message, "failed-" + kind + "-restore");
                    show(divFields);
                }

                btnSubmit.disabled = false;
                enableFileValidator["restore-" + kind + "-file"] = true;
            })
            .catch(function(error) {
                hide(spinner);
                message.innerHTML = makeError(error, "failed-network-" + kind + "-restore");
                initMessageBlock(message, "failed-network-" + kind + "-restore", function() {
                    show(divFields);
                });
                btnSubmit.disabled = false;
                show(divFields);
                enableFileValidator["restore-" + kind + "-file"] = true;
            });
    }

    /**
     * Helper to build a function to check if the file validators
     * are enabled. Generates a closure around the id.
     *
     * @param  {string} id
     * @return {Function}
     */
    function makeValidatorControlFunction(id) {
        return function(el, val) {
            return enableFileValidator[id];
        };
    }

    /**
     * Initialize the forms on the page.
     *
     *  * Setup the validators.
     *  * Setup the submit handlers.
     *
     */
    function initialize(error) {
        for ( var inputId in uploadButtonIds ) {
            if (document.getElementById(inputId)) {
                // eslint-disable-next-line new-cap
                var validator = new CPANEL.validate.validator(PAGE.validationTitle);
                validator.add(inputId, "min_length(%input%, 1)", PAGE.missingFile, makeValidatorControlFunction(inputId));
                validator.attach();
                CPANEL.validate.attach_to_form(uploadButtonIds[inputId], validator);
            }
        }

        if (error instanceof Error) {
            alert(error);
            return;
        }

        for (var formId in uploads) {
            if (uploads.hasOwnProperty(formId)) {
                var form = document.getElementById(formId);
                if (form) {
                    form.addEventListener("submit", uploads[formId], false);
                }
            }
        }
    }

    if (browserSupportsNeededFeatures()) {
        YAHOO.util.Event.onDOMReady(initialize);
    } else {
        YAHOO.util.Event.onDOMReady(function() {
            loadScripts(PAGE.polyfills, initialize);
        });
    }
})();
Back to Directory File Manager