Viewing File: /usr/local/cpanel/base/frontend/jupiter/site_publisher/index.cmb.js
/*
# site_publisher/services/publishService.js Copyright(c) 2020 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, _: true */
define(
'app/services/publishService',[
"angular",
"cjt/io/api",
"cjt/io/uapi-request",
"cjt/io/uapi", // IMPORTANT: Load the driver so its ready
],
function(angular, API, APIREQUEST) {
var app = angular.module("App");
function publishServiceFactory($q) {
var publishService = {};
/**
* Converts the response to our application data structure
* @param {Object} response
* @return {Object} Sanitized data structure.
*/
publishService.convertResponseToList = function(response) {
var items = [];
if (response.data) {
var data = response.data;
for (var i = 0, length = data.length; i < length; i++) {
items.push(data[i]);
}
var meta = response.meta || {};
if ( !angular.isDefined(meta.paginate) ) {
meta.paginate = {
total_records: data.length,
total_pages: 1
};
}
if ( !angular.isDefined(meta.records_before_filter) ) {
meta.records_before_filter = data.length;
}
return {
meta: meta,
data: items
};
} else {
return {
meta: {
paginate: {
total_records: 0,
total_pages: 0
}
},
data: []
};
}
};
/**
* Lists the domains with user settings
* @param {Object} meta The meta data to filter the domain list by
* @return {Promise} Promise that will fulfill the request.
*/
publishService.listDomains = function(meta) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
apiCall.initialize("SiteTemplates", "list_user_settings");
if (meta) {
if (meta.currentPage) {
apiCall.addPaging(meta.currentPage, meta.pageSize || 10);
}
if (meta.filterBy && meta.filterCompare && meta.filterValue) {
apiCall.addFilter(meta.filterBy, meta.filterCompare, meta.filterValue);
}
}
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
/**
* Publishes the site
* @param {Object} template The selected template
* @param {Object} domain The selected domain
* @return {Promise} Promise that will fulfill the request.
*/
publishService.publish = function(template, domain) {
var deferred = $q.defer();
var apiCall = new APIREQUEST.Class();
var fields = _.map(template.meta.fields, function(field) {
if ( field.value && field.type.indexOf("date", 0) === 0 ) {
if (field.value.toString() !== "Invalid Date") {
try {
// return the proper ISO format for date fields
return { "id": field.id, "value": new Date(field.value).toISOString() };
} catch (ex) {
return { "id": field.id, "value": "" };
}
} else {
return { "id": field.id, "value": "" };
}
}
return _.pick(field, ["id", "value"]);
});
apiCall.initialize("SiteTemplates", "publish");
apiCall.addArgument("path", template.path);
apiCall.addArgument("template", template.template);
apiCall.addArgument("docroot", domain.documentroot);
apiCall.addArgument("domain_url", domain.url);
for ( var i = 0, length = fields.length; i < length; i++ ) {
apiCall.addArgument(fields[i].id, fields[i].value);
}
API.promise(apiCall.getRunArguments())
.done(function(response) {
response = response.parsedResponse;
if (response.status) {
deferred.resolve(response);
} else {
deferred.reject(response.error);
}
});
return deferred.promise;
};
return publishService;
}
publishServiceFactory.$inject = ["$q"];
return app.factory("publishService", publishServiceFactory);
});
// Copyright 2023 cPanel, L.L.C. - All rights reserved.
// copyright@cpanel.net
// https://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited
/* eslint-disable no-prototype-builtins */
/* global _: true */
define('app/views/publishController',[
"angular",
"cjt/util/locale",
"uiBootstrap",
"cjt/filters/wrapFilter",
"cjt/directives/actionButtonDirective",
"cjt/directives/jsonFieldDirective",
"cjt/decorators/growlDecorator",
"app/services/publishService",
], function(angular, LOCALE) {
"use strict";
// Retrieve the current application
var app = angular.module("App");
// Setup the controller
var controller = app.controller("publishController", [
"$scope",
"$routeParams",
"publishService",
"$timeout",
"alertService",
"growl",
"$q",
function(
$scope,
$routeParams,
publishService,
$timeout,
alertService,
growl,
$q
) {
$scope.domainList = [];
$scope.templateList = [];
// meta information
$scope.meta = {
// Search/Filter options
filterBy: "*",
filterCompare: "contains",
filterValue: "",
// Pager settings
maxPages: 0,
totalItems: $scope.domainList.length,
currentPage: 1,
pageSize: 10,
pageSizes: [10, 20, 50, 100],
start: 0,
limit: 10,
};
/**
* Clears the selected template object properties
*/
var clearSelectedTemplate = function() {
for (var i = 0, length = $scope.templateList.length; i < length; i++) {
delete $scope.templateList[i].selected;
}
};
/**
* Clears the selected domain object properties
*/
var clearSelectedDomain = function() {
for (var i = 0, length = $scope.domainList.length; i < length; i++) {
delete $scope.domainList[i].selected;
}
};
/**
* Resets the state of the wizard to the initial step
*/
$scope.resetSteps = function() {
$scope.status = {
isDomainSelectOpen: true,
isDomainSelectStep: true,
isTemplateSelectOpen: false,
isTemplateSelectStep: false,
isTemplateFormOpen: false,
isTemplateFormStep: false,
isPublished: false,
};
$scope.selectedDomain = null;
clearSelectedDomain();
$scope.selectedTemplate = null;
clearSelectedTemplate();
};
/**
* Returns the panel class depending on workflow step
*
* @method getPanelClass
* @param {String} step The workflow step
* @returns {String} Returns the panel class name
*/
$scope.getPanelClass = function(step) {
var panelClass = "panel-default",
activeClass = "panel-primary";
if (step === "domain" && $scope.status.isDomainSelectStep) {
panelClass = activeClass;
} else if (step === "template" && $scope.status.isTemplateSelectStep) {
panelClass = activeClass;
} else if (step === "publish" && $scope.status.isTemplateFormStep) {
panelClass = activeClass;
}
return panelClass;
};
/**
* Applies the array of domains to the scoped domain list
*
* @method processDomainList
* @param {Array} resultList The paginated array of domains
*/
var processDomainList = function(resultList) {
var domainList = resultList.data;
// update the total items after search
$scope.meta.totalItems = resultList.meta.paginate.total_records;
$scope.meta.records_before_filter =
resultList.meta.records_before_filter;
var filteredList = domainList;
// filter list based on page size and pagination
if ($scope.meta.totalItems > _.min($scope.meta.pageSizes)) {
var start = ($scope.meta.currentPage - 1) * $scope.meta.pageSize;
$scope.showPager = true;
// table statistics
$scope.meta.start = start + 1;
$scope.meta.limit = start + filteredList.length;
} else {
// hide pager and pagination
$scope.showPager = false;
if (filteredList.length === 0) {
$scope.meta.start = 0;
} else {
// table statistics
$scope.meta.start = 1;
}
$scope.meta.limit = filteredList.length;
}
// set selected flag if the domain is in this page of results
if ($scope.selectedDomain) {
for (var i = 0, length = filteredList.length; i < length; i++) {
if (filteredList[i].domain === $scope.selectedDomain.domain) {
filteredList[i].selected = true;
break;
}
}
}
$scope.domainList = filteredList;
// preselect a single domain only if it's not a filtered result
if ($scope.meta.records_before_filter === 1) {
$scope.selectDomain(0);
}
};
var fetchDomainsList = function() {
return publishService.listDomains($scope.meta).then(
function(response) {
processDomainList(response);
},
function(error) {
growl.error(error);
}
);
};
/**
* Clear the search query
*
* @method clearFilter
*/
$scope.clearFilter = function() {
$scope.meta.filterValue = "";
return $scope.selectPage(1);
};
/**
* Select a specific page
* @param {Number} page Page number
*/
$scope.selectPage = function(page) {
// set the page if requested.
if (page && angular.isNumber(page)) {
$scope.meta.currentPage = page;
}
return fetchDomainsList();
};
/**
* Returns site address for the domain
* @param {Object} domain Object with domain information
*/
$scope.getSiteAddress = function(domain) {
// TODO: get protocol for domain
return "//" + domain.domain;
};
/**
* Returns file manager link for the domain
* @param {Object} domain Object with domain information
*/
$scope.getFileManagerLink = function(domain) {
if (domain) {
var docroot = domain.documentroot.slice(domain.homedir.length + 1);
return (
$scope.deprefix +
$scope.fileManagerObj.url +
"?dir=" +
encodeURIComponent(docroot)
);
} else {
return $scope.fileManagerObj.url;
}
};
/**
* Select a specific domain by it's index in the domain list
* @param {Number} id The index of the domain to select
*/
$scope.selectDomain = function(id) {
$scope.selectedDomain = $scope.domainList[id];
clearSelectedDomain();
$scope.domainList[id].selected = true;
$scope.status.isDomainSelectOpen =
$scope.status.isDomainSelectStep = false;
$scope.status.isTemplateSelectOpen =
$scope.status.isTemplateSelectStep = true;
$scope.status.isTemplateFormOpen =
$scope.status.isTemplateFormStep = false;
clearSelectedTemplate();
if (
$scope.selectedDomain.template_settings.hasOwnProperty("template")
) {
// a template is installed, apply the selected styling to the proper one
for (
var i = 0, length = $scope.templateList.length;
i < length;
i++
) {
var template = $scope.templateList[i];
if (
$scope.selectedDomain.template_settings.template ===
template.template
) {
template.selected = true;
break;
}
}
}
$scope.selectedTemplate = false;
$scope.status.isPublished = false;
// if only one template preselect template
if ($scope.templateList.length === 1) {
$scope.selectTemplate(0);
}
};
/**
* Select a specific template by it's index in the template list
* @param {Number} id The index of the template to select
*/
$scope.selectTemplate = function(id) {
$scope.selectedTemplate = $scope.templateList[id];
clearSelectedTemplate();
$scope.templateList[id].selected = true;
for (
var i = 0, length = $scope.templateList[id].meta.fields.length;
i < length;
i++
) {
// check the selected domain template settings for this field and populate it if so
var field = $scope.templateList[id].meta.fields[i];
if (
$scope.selectedDomain.template_settings.hasOwnProperty(field.id)
) {
field.value = $scope.selectedDomain.template_settings[field.id];
}
}
$scope.status.isTemplateSelectOpen =
$scope.status.isTemplateSelectStep = false;
$scope.status.isTemplateFormOpen =
$scope.status.isTemplateFormStep = true;
$scope.status.isPublished = false;
};
/**
* Check validity of the supplied form field
* @param {Object} field The model for the form field to check
* @return {Boolean} Returns true if there are errors
*/
$scope.hasError = function(field) {
var form = document.publish_form;
if (
form.hasOwnProperty(field.name) &&
typeof form[field.name].checkValidity === "function"
) {
return !form[field.name].checkValidity();
}
return false;
};
/**
* Publish a site by it's selected template and domain along with the supplied form data
* @param {Object} template The selected template object
* @param {Object} domain The selected domain object
* @return {Promise} Promise that will fulfill the request.
*/
$scope.publishTemplate = function(template, domain) {
if (window.mixpanel) {
window.mixpanel.track("SitePublisher-Publish-Clicked", {
template: template.template,
});
}
var errorCount =
document.publish_form.getElementsByClassName("has-error").length;
if (errorCount) {
// prevent form submit on invalid inputs to allow for html5 validation
growl.error(
LOCALE.maketext(
"The form has returned [quant,_1,error,errors]",
errorCount
)
);
return $q.when(false);
}
domain.url = $scope.getSiteAddress(domain);
return publishService.publish(template, domain).then(
function() {
$scope.status.isTemplateFormOpen =
$scope.status.isTemplateFormStep = false;
$scope.status.isPublished = true;
if (window.mixpanel) {
window.mixpanel.track("SitePublisher-Publish-Success", {
template: template.template,
});
}
// update the published domain in the active data set
domain.template_settings = {
is_empty: 0,
path: template.path,
template: template.template,
docroot: domain.documentroot,
};
// persist the template variables bacck to the object without page load
for (
var i = 0, length = template.meta.fields.length;
i < length;
i++
) {
var field = template.meta.fields[i];
domain.template_settings[field.id] = field.value;
}
},
function(error) {
if (window.mixpanel) {
window.mixpanel.track("SitePublisher-Publish-Failed", {
template: template.template,
});
}
growl.error(error);
}
);
};
/**
* Initialize the view
*
* @private
* @method _initializeView
*/
var _initializeView = function() {
$scope.locale = LOCALE;
$scope.resetSteps();
if (PAGE.deprefix) {
$scope.deprefix = PAGE.deprefix;
}
if (PAGE.fileManagerObj) {
$scope.fileManagerObj = PAGE.fileManagerObj;
}
if (PAGE.accountsObj) {
$scope.accountsObj = PAGE.accountsObj;
}
if (PAGE.webdiskObj) {
$scope.webdiskObj = PAGE.webdiskObj;
}
// check for page data in the template if this is a first load
if (app.firstLoad.publish && PAGE.domainList && PAGE.templateList) {
app.firstLoad.publish = false;
// perform a reverse sort on the template list based on metadata throwing null dates to the bottom
$scope.templateList = publishService
.convertResponseToList(PAGE.templateList)
.data.sort(function(a, b) {
var aDate, bDate, aName, bName;
// ensure we have valid dates and parse to millisecond values
if (a.meta.information.hasOwnProperty("date")) {
aDate = Date.parse(a.meta.information.date);
if (isNaN(aDate)) {
aDate = 0;
}
} else {
aDate = 0;
}
if (b.meta.information.hasOwnProperty("date")) {
bDate = Date.parse(b.meta.information.date);
if (isNaN(bDate)) {
bDate = 0;
}
} else {
bDate = 0;
}
// sort latest dates to the top
if (bDate > aDate) {
return 1;
} else if (bDate < aDate) {
return -1;
} else {
// ensure we have valid template names
if (
a.meta.information.hasOwnProperty("name") &&
a.meta.information.name !== null
) {
aName = a.meta.information.name.toLowerCase();
} else {
aName = "";
}
if (
b.meta.information.hasOwnProperty("name") &&
b.meta.information.name !== null
) {
bName = b.meta.information.name.toLowerCase();
} else {
bName = "";
}
// sort equal dates by their names
return aName > bName ? 1 : aName < bName ? -1 : 0;
}
});
processDomainList(
publishService.convertResponseToList(PAGE.domainList)
);
} else {
// Otherwise, retrieve it via ajax
fetchDomainsList();
}
if ($routeParams["domain"]) {
// Allow Deep-linking of a domain
$scope.meta.filterValue = $routeParams["domain"];
$scope.selectPage(1).then(function() {
$scope.domainList.forEach(function(domain, key) {
if (domain.domain === $routeParams["domain"]) {
$scope.selectDomain(key);
}
});
});
}
};
_initializeView();
},
]);
function ExceptionHandler($provide) {
$provide.decorator("$exceptionHandler", [
"$injector",
function($injector) {
return function(exception) {
$injector
.get("growl")
.error(
LOCALE.maketext("A problem has occurred: [_1]", exception.message)
);
};
},
]);
}
ExceptionHandler.$inject = ["$provide"];
app.config(ExceptionHandler);
return controller;
});
/* global define: false, require: false */
define(
'app/index',[
"angular",
"cjt/core",
"cjt/modules",
"ngRoute",
"uiBootstrap"
],
function(angular, CJT) {
return function() {
// First create the application
angular.module("App", [
"ngRoute",
"ui.bootstrap",
"angular-growl",
"cjt2.cpanel"
]);
var app = require(
[
"cjt/bootstrap",
"uiBootstrap",
// Application Modules
"cjt/views/applicationController",
"app/views/publishController"
], function(BOOTSTRAP) {
var app = angular.module("App");
app.firstLoad = {
publish: true,
};
app.value("PAGE", CPANEL.PAGE);
// If using views
app.controller("BaseController", ["$rootScope", "$scope", "$route", "$location",
function($rootScope, $scope, $route, $location) {
$scope.loading = false;
// Convenience functions so we can track changing views for loading purposes
$rootScope.$on("$routeChangeStart", function() {
$scope.loading = true;
});
$rootScope.$on("$routeChangeSuccess", function() {
$scope.loading = false;
});
$rootScope.$on("$routeChangeError", function() {
$scope.loading = false;
});
$scope.current_route_matches = function(key) {
return $location.path().match(key);
};
$scope.go = function(path) {
$location.path(path);
};
}
]);
// viewName
app.config(["$routeProvider", "$locationProvider",
function($routeProvider, $locationProvider) {
// Setup a route - copy this to add additional routes as necessary
$routeProvider.when("/publish", {
controller: "publishController",
templateUrl: CJT.buildFullPath("site_publisher/views/publishView.html.tt"),
resolve: {}
});
// default route
$routeProvider.otherwise({
"redirectTo": "/publish"
});
}
]);
// end of using views
BOOTSTRAP("#content", "App");
});
return app;
};
}
);
Back to Directory
File Manager