Merge origin/v2.5.5-dev - keep FTP, dashboard, notification fixes

This commit is contained in:
master3395
2026-01-30 19:46:39 +01:00
3393 changed files with 747628 additions and 5663 deletions

View File

@@ -2,6 +2,19 @@
* Created by usman on 7/26/17.
*/
// Ensure app is available (get existing module or create reference)
// This ensures compatibility with the global app variable from system-status.js
if (typeof app === 'undefined') {
// First try to get window.app (set by system-status.js)
if (typeof window !== 'undefined' && typeof window.app !== 'undefined') {
app = window.app;
} else {
// If window.app doesn't exist, get the existing CyberCP module
// This works because system-status.js should have already created it
app = angular.module('CyberCP');
}
}
// Global function for deleting staging sites
function deleteStagingGlobal(stagingId) {
if (confirm("Are you sure you want to delete this staging site? This action cannot be undone.")) {
@@ -10730,39 +10743,6 @@ $("#modifyWebsiteLoading").hide();
$("#modifyWebsiteButton").hide();
app.controller('modifyWebsitesController', function ($scope, $http) {
// Initialize home directory variables
$scope.homeDirectories = [];
$scope.selectedHomeDirectory = '';
$scope.selectedHomeDirectoryInfo = null;
$scope.currentHomeDirectory = '';
// Load home directories on page load
$scope.loadHomeDirectories = function() {
$http.post('/userManagement/getUserHomeDirectories/', {})
.then(function(response) {
if (response.data.status === 1) {
$scope.homeDirectories = response.data.directories;
}
})
.catch(function(error) {
console.error('Error loading home directories:', error);
});
};
// Update home directory info when selection changes
$scope.updateHomeDirectoryInfo = function() {
if ($scope.selectedHomeDirectory) {
$scope.selectedHomeDirectoryInfo = $scope.homeDirectories.find(function(dir) {
return dir.id == $scope.selectedHomeDirectory;
});
} else {
$scope.selectedHomeDirectoryInfo = null;
}
};
// Initialize home directories
$scope.loadHomeDirectories();
$scope.fetchWebsites = function () {
@@ -10807,7 +10787,6 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
$scope.webpacks = JSON.parse(response.data.packages);
$scope.adminNames = JSON.parse(response.data.adminNames);
$scope.currentAdmin = response.data.currentAdmin;
$scope.currentHomeDirectory = response.data.currentHomeDirectory || 'Default';
$("#webSiteDetailsToBeModified").fadeIn();
$("#websiteModifySuccess").fadeIn();
@@ -10835,7 +10814,6 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
var email = $scope.adminEmail;
var phpVersion = $scope.phpSelection;
var admin = $scope.selectedAdmin;
var homeDirectory = $scope.selectedHomeDirectory;
$("#websiteModifyFailure").hide();
@@ -10852,8 +10830,7 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
packForWeb: packForWeb,
email: email,
phpVersion: phpVersion,
admin: admin,
homeDirectory: homeDirectory
admin: admin
};
var config = {
@@ -13325,16 +13302,6 @@ app.controller('manageAliasController', function ($scope, $http, $timeout, $wind
$scope.manageAliasLoading = true;
$scope.operationSuccess = true;
// Initialize the page to show aliases list
$scope.showAliasesList = function() {
$scope.aliasTable = true;
$scope.addAliasButton = true;
$scope.domainAliasForm = false;
};
// Auto-show aliases list on page load
$scope.showAliasesList();
$scope.createAliasEnter = function ($event) {
var keyCode = $event.which || $event.keyCode;
if (keyCode === 13) {
@@ -13582,64 +13549,6 @@ app.controller('manageAliasController', function ($scope, $http, $timeout, $wind
};
$scope.issueAliasSSL = function (masterDomain, aliasDomain) {
$scope.manageAliasLoading = false;
url = "/websites/issueAliasSSL";
var data = {
masterDomain: masterDomain,
aliasDomain: aliasDomain
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
if (response.data.issueAliasSSL === 1) {
$scope.aliasTable = false;
$scope.addAliasButton = true;
$scope.domainAliasForm = true;
$scope.aliasError = true;
$scope.couldNotConnect = true;
$scope.aliasCreated = true;
$scope.manageAliasLoading = true;
$scope.operationSuccess = false;
$timeout(function () {
$window.location.reload();
}, 3000);
} else {
$scope.aliasTable = false;
$scope.addAliasButton = true;
$scope.domainAliasForm = true;
$scope.aliasError = false;
$scope.couldNotConnect = true;
$scope.aliasCreated = true;
$scope.manageAliasLoading = true;
$scope.operationSuccess = true;
$scope.errorMessage = response.data.error_message;
}
}
function cantLoadInitialDatas(response) {
$scope.aliasTable = false;
$scope.addAliasButton = true;
$scope.domainAliasForm = true;
$scope.aliasError = true;
$scope.couldNotConnect = false;
$scope.aliasCreated = true;
$scope.manageAliasLoading = true;
$scope.operationSuccess = true;
}
};
////// create domain part

View File

@@ -1,5 +1,6 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% load website_filters %}
{% block title %}{% trans "Modify Website - CyberPanel" %}{% endblock %}
{% block header_scripts %}
@@ -479,10 +480,10 @@
<div ng-show="selectedHomeDirectoryInfo" class="info-box" style="margin-top: 10px;">
<i class="fas fa-info-circle"></i>
<div class="info-box-content">
<div class="info-box-title">{{selectedHomeDirectoryInfo.name}}</div>
<div class="info-box-title">{$ selectedHomeDirectoryInfo.name $}</div>
<div class="info-box-text">
{{selectedHomeDirectoryInfo.description || 'No description available'}}<br>
<small>Available Space: {{selectedHomeDirectoryInfo.available_space | filesize}} | Users: {{selectedHomeDirectoryInfo.user_count}}</small>
{$ selectedHomeDirectoryInfo.description || 'No description available' $}<br>
<small>Available Space: {$ selectedHomeDirectoryInfo.available_space | filesize $} | Users: {$ selectedHomeDirectoryInfo.user_count $}</small>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
"""
Custom Django template filters for user management
"""
from django import template
from django.template.defaultfilters import filesizeformat
register = template.Library()
@register.filter(name='filesize')
def filesize_filter(value):
"""
Alias for Django's filesizeformat filter to maintain compatibility
with templates that use |filesize instead of |filesizeformat
"""
if value is None:
return '0 B'
try:
return filesizeformat(value)
except (ValueError, TypeError):
return '0 B'

View File

@@ -36,7 +36,8 @@ from math import ceil
from plogical.alias import AliasManager
from plogical.applicationInstaller import ApplicationInstaller
from plogical import hashPassword, randomPassword
from emailMarketing.emACL import emACL
# emailMarketing removed from INSTALLED_APPS
# from emailMarketing.emACL import emACL
from plogical.processUtilities import ProcessUtilities
from managePHP.phpManager import PHPManager
from ApachController.ApacheVhosts import ApacheVhost
@@ -3729,7 +3730,9 @@ context /cyberpanel_suspension_page.html {
from plogical.processUtilities import ProcessUtilities
marketingStatus = emACL.checkIfEMEnabled(admin.userName)
# emailMarketing removed - always return False for marketing status
# marketingStatus = emACL.checkIfEMEnabled(admin.userName)
marketingStatus = False
Data['marketingStatus'] = marketingStatus
Data['ftpTotal'] = website.package.ftpAccounts
@@ -4023,6 +4026,43 @@ context /cyberpanel_suspension_page.html {
{"error": 1, "domain": "This child domain does not exists"})
return proc.render()
def _get_log_file_path(self, domain_name, log_type):
"""
Get the correct log file path for a domain.
For child domains (sub-domains), logs are stored in the master domain's log directory.
Args:
domain_name: The domain name (could be a child domain)
log_type: 1 for access log, 0 for error log
Returns:
str: The full path to the log file
"""
try:
# Check if this is a child domain
try:
child_domain = ChildDomains.objects.get(domain=domain_name)
master_domain = child_domain.master.domain
log_dir = f"/home/{master_domain}/logs"
except ChildDomains.DoesNotExist:
# Not a child domain, use standard path
log_dir = f"/home/{domain_name}/logs"
# Construct log file path
if log_type == 1:
log_file = f"{domain_name}.access_log"
else:
log_file = f"{domain_name}.error_log"
return f"{log_dir}/{log_file}"
except Exception as e:
# Fallback to standard path if anything fails
logging.CyberCPLogFileWriter.writeToFile(f'Error determining log path for {domain_name}: {str(e)}')
if log_type == 1:
return f"/home/{domain_name}/logs/{domain_name}.access_log"
else:
return f"/home/{domain_name}/logs/{domain_name}.error_log"
def getDataFromLogFile(self, userID=None, data=None):
currentACL = ACLManager.loadedACL(userID)
@@ -4037,10 +4077,8 @@ context /cyberpanel_suspension_page.html {
else:
return ACLManager.loadErrorJson('logstatus', 0)
if logType == 1:
fileName = "/home/" + self.domain + "/logs/" + self.domain + ".access_log"
else:
fileName = "/home/" + self.domain + "/logs/" + self.domain + ".error_log"
# Use helper method to get correct log file path (handles child domains)
fileName = self._get_log_file_path(self.domain, logType)
command = 'ls -la %s' % fileName
result = ProcessUtilities.outputExecutioner(command)
@@ -4050,6 +4088,10 @@ context /cyberpanel_suspension_page.html {
{'status': 0, 'logstatus': 0,
'error_message': "Symlink attack."})
return HttpResponse(final_json)
# Only read from the specific domain's log file - don't fallback to master domain
# This ensures we only show logs for the requested domain, not mixed logs from other domains
# If the log file doesn't exist or is empty, we'll return empty results
## get Logs
website = Websites.objects.get(domain=self.domain)
@@ -4071,25 +4113,47 @@ context /cyberpanel_suspension_page.html {
for items in reversed(data):
if len(items) > 10:
logData = items.split(" ")
domain = logData[5].strip('"')
ipAddress = logData[0].strip('"')
time = (logData[3]).strip("[").strip("]")
resource = logData[6].strip('"')
size = logData[9].replace('"', '')
try:
logData = items.split(" ")
if len(logData) < 10:
continue
# Parse log entry: format is "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
# When split by space: [0]="IP, [1]=-, [2]=-, [3]=[timestamp_part1, [4]=timestamp_part2], [5]="GET, [6]=/path?params, [7]=HTTP/2", [8]=status, [9]=size, [10]="referer", [11+]="user-agent"
ipAddress = logData[0].strip('"')
# Reconstruct timestamp from fields 3 and 4
time = (logData[3] + " " + logData[4]).strip("[").strip("]") if len(logData) > 4 else logData[3].strip("[").strip("]")
# Resource path starts at field 6, reconstruct until we hit HTTP/version field
if len(logData) > 6:
resource_parts = []
i = 6
while i < len(logData) and not logData[i].startswith('HTTP/'):
resource_parts.append(logData[i])
i += 1
resource = " ".join(resource_parts).strip('"')
else:
resource = ""
# Size is typically in field 9 (after status code in field 8)
size = logData[9].replace('"', '') if len(logData) > 9 else "0"
# Note: Log format doesn't include domain name, so domain is determined by which log file is read
# We already ensured we're reading from the correct domain's log file above
dic = {'domain': domain,
'ipAddress': ipAddress,
'time': time,
'resource': resource,
'size': size,
}
dic = {'domain': self.domain, # Use requested domain since it's determined by log file
'ipAddress': ipAddress,
'time': time,
'resource': resource,
'size': size,
}
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
if checker == 0:
json_data = json_data + json.dumps(dic)
checker = 1
else:
json_data = json_data + ',' + json.dumps(dic)
except (IndexError, ValueError) as e:
# Skip malformed log entries
continue
json_data = json_data + ']'
final_json = json.dumps({'status': 1, 'logstatus': 1, 'error_message': "None", "data": json_data})
@@ -4108,7 +4172,9 @@ context /cyberpanel_suspension_page.html {
else:
return ACLManager.loadErrorJson('logstatus', 0)
fileName = "/home/" + self.domain + "/logs/" + self.domain + ".error_log"
# Use helper method to get correct log file path (handles child domains)
# log_type 0 = error log
fileName = self._get_log_file_path(self.domain, 0)
command = 'ls -la %s' % fileName
result = ProcessUtilities.outputExecutioner(command)