// 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