Add subdomain log fix functionality and update templates. Introduce new views and URLs for fixing subdomain logs, enhance log configuration handling in the upgrade script, and update vHost configuration paths for better log management. Include a new menu item for accessing the log fix interface.

This commit is contained in:
Master3395
2025-09-18 22:04:05 +02:00
parent 8ca3ae1b49
commit 6213ff8fdd
10 changed files with 1095 additions and 36 deletions

View File

@@ -1180,6 +1180,9 @@
<a href="{% url 'listChildDomains' %}" class="menu-item">
<span>List Sub/Addon Domains</span>
</a>
<a href="{% url 'fixSubdomainLogs' %}" class="menu-item">
<span>Fix Subdomain Logs</span>
</a>
{% if admin or modifyWebsite %}
<a href="{% url 'modifyWebsite' %}" class="menu-item">
<span>Modify Website</span>

View File

@@ -0,0 +1,190 @@
#!/usr/local/CyberCP/bin/python
"""
Django management command to fix subdomain log configurations
Usage: python manage.py fix_subdomain_logs [--dry-run] [--domain <domain>]
"""
import os
import sys
import django
import re
import shutil
from datetime import datetime
# Add CyberPanel to Python path
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
from django.core.management.base import BaseCommand, CommandError
from websiteFunctions.models import ChildDomains, Websites
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.processUtilities import ProcessUtilities
class Command(BaseCommand):
help = 'Fix subdomain log configurations to use separate log files'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be changed without making changes',
)
parser.add_argument(
'--domain',
type=str,
help='Fix logs for a specific domain only',
)
parser.add_argument(
'--backup',
action='store_true',
help='Create backup of original configurations',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
specific_domain = options['domain']
create_backup = options['backup']
if dry_run:
self.stdout.write(
self.style.WARNING('DRY RUN MODE - No changes will be made')
)
self.stdout.write('Starting subdomain log configuration fix...')
try:
if specific_domain:
# Fix specific domain
self.fix_domain_logs(specific_domain, dry_run, create_backup)
else:
# Fix all child domains
child_domains = ChildDomains.objects.all()
fixed_count = 0
for child_domain in child_domains:
if self.fix_domain_logs(child_domain.domain, dry_run, create_backup):
fixed_count += 1
self.stdout.write(
self.style.SUCCESS(f'Fixed log configurations for {fixed_count} child domains')
)
except Exception as e:
raise CommandError(f'Failed to fix subdomain logs: {str(e)}')
def fix_domain_logs(self, domain_name, dry_run=False, create_backup=False):
"""Fix log configuration for a specific domain"""
try:
# Get child domain info
try:
child_domain = ChildDomains.objects.get(domain=domain_name)
master_domain = child_domain.master.domain
domain_path = child_domain.path
except ChildDomains.DoesNotExist:
self.stdout.write(
self.style.WARNING(f'Domain {domain_name} is not a child domain, skipping')
)
return False
vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf"
if not os.path.exists(vhost_conf_path):
self.stdout.write(
self.style.WARNING(f'VHost config not found for {domain_name}: {vhost_conf_path}')
)
return False
# Read current configuration
with open(vhost_conf_path, 'r') as f:
config_content = f.read()
# Check if fix is needed
if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content:
self.stdout.write(f'{domain_name} already has correct log configuration')
return True
self.stdout.write(f'Fixing log configuration for {domain_name}...')
if dry_run:
self.stdout.write(f' [DRY RUN] Would fix log paths in {vhost_conf_path}')
return True
# Create backup if requested
if create_backup:
backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(vhost_conf_path, backup_path)
self.stdout.write(f' Created backup: {backup_path}')
# Fix the configuration
fixed_content = config_content
# Fix error log path
fixed_content = re.sub(
rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log',
f'errorlog $VH_ROOT/logs/{domain_name}.error_log',
fixed_content
)
# Fix access log path
fixed_content = re.sub(
rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log',
f'accesslog $VH_ROOT/logs/{domain_name}.access_log',
fixed_content
)
# Fix CustomLog paths (for Apache configurations)
fixed_content = re.sub(
rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log',
f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log',
fixed_content
)
# Write the fixed configuration
with open(vhost_conf_path, 'w') as f:
f.write(fixed_content)
# Set proper ownership
ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}')
# Create the log directory if it doesn't exist
log_dir = f"/home/{master_domain}/logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}')
# Create separate log files for the child domain
error_log_path = f"{log_dir}/{domain_name}.error_log"
access_log_path = f"{log_dir}/{domain_name}.access_log"
# Create empty log files if they don't exist
for log_path in [error_log_path, access_log_path]:
if not os.path.exists(log_path):
with open(log_path, 'w') as f:
f.write('')
ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}')
ProcessUtilities.executioner(f'chmod 644 {log_path}')
# Restart LiteSpeed to apply changes
ProcessUtilities.executioner('systemctl restart lsws')
self.stdout.write(f'✓ Fixed log configuration for {domain_name}')
logging.writeToFile(f'Fixed subdomain log configuration for {domain_name}')
return True
except Exception as e:
self.stdout.write(
self.style.ERROR(f'Failed to fix logs for {domain_name}: {str(e)}')
)
logging.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
return False
def show_help_examples(self):
"""Show usage examples"""
self.stdout.write('\nUsage examples:')
self.stdout.write(' python manage.py fix_subdomain_logs --dry-run')
self.stdout.write(' python manage.py fix_subdomain_logs --domain diabetes.example.com')
self.stdout.write(' python manage.py fix_subdomain_logs --backup')
self.stdout.write(' python manage.py fix_subdomain_logs --domain diabetes.example.com --backup --dry-run')

View File

@@ -2611,6 +2611,156 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL
except:
pass
@staticmethod
def fixSubdomainLogConfigurations():
"""Fix subdomain log configurations during upgrade"""
try:
# Check if this fix has already been applied
fix_marker_file = '/usr/local/lscp/logs/subdomain_log_fix_applied'
if os.path.exists(fix_marker_file):
Upgrade.stdOut("Subdomain log fix already applied - skipping")
return
Upgrade.stdOut("=== FIXING SUBDOMAIN LOG CONFIGURATIONS ===")
# Import required modules
import sys
import os
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
try:
import django
django.setup()
from websiteFunctions.models import ChildDomains
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.processUtilities import ProcessUtilities
import re
import shutil
from datetime import datetime
# Get all child domains
child_domains = ChildDomains.objects.all()
if not child_domains:
Upgrade.stdOut("No child domains found - skipping subdomain log fix")
return
Upgrade.stdOut(f"Found {len(child_domains)} child domains to check")
fixed_count = 0
skipped_count = 0
for child_domain in child_domains:
domain_name = child_domain.domain
master_domain = child_domain.master.domain
vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf"
if not os.path.exists(vhost_conf_path):
Upgrade.stdOut(f"⚠️ Skipping {domain_name}: vHost config not found")
skipped_count += 1
continue
try:
# Read current configuration
with open(vhost_conf_path, 'r') as f:
config_content = f.read()
# Check if fix is needed
if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content:
Upgrade.stdOut(f"{domain_name}: Already has correct log configuration")
skipped_count += 1
continue
# Create backup
backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(vhost_conf_path, backup_path)
# Fix the configuration
fixed_content = config_content
# Fix error log path
fixed_content = re.sub(
rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log',
f'errorlog $VH_ROOT/logs/{domain_name}.error_log',
fixed_content
)
# Fix access log path
fixed_content = re.sub(
rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log',
f'accesslog $VH_ROOT/logs/{domain_name}.access_log',
fixed_content
)
# Fix CustomLog paths (for Apache configurations)
fixed_content = re.sub(
rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log',
f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log',
fixed_content
)
# Write the fixed configuration
with open(vhost_conf_path, 'w') as f:
f.write(fixed_content)
# Set proper ownership
ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}')
# Create the log directory if it doesn't exist
log_dir = f"/home/{master_domain}/logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}')
# Create separate log files for the child domain
error_log_path = f"{log_dir}/{domain_name}.error_log"
access_log_path = f"{log_dir}/{domain_name}.access_log"
# Create empty log files if they don't exist
for log_path in [error_log_path, access_log_path]:
if not os.path.exists(log_path):
with open(log_path, 'w') as f:
f.write('')
ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}')
ProcessUtilities.executioner(f'chmod 644 {log_path}')
Upgrade.stdOut(f"✅ Fixed log configuration for {domain_name}")
logging.writeToFile(f'Fixed subdomain log configuration for {domain_name} during upgrade')
fixed_count += 1
except Exception as e:
Upgrade.stdOut(f"❌ Failed to fix {domain_name}: {str(e)}")
logging.writeToFile(f'Error fixing subdomain logs for {domain_name} during upgrade: {str(e)}')
# Restart LiteSpeed to apply changes if any were made
if fixed_count > 0:
Upgrade.stdOut("Restarting LiteSpeed to apply log configuration changes...")
ProcessUtilities.executioner('systemctl restart lsws')
Upgrade.stdOut(f"=== SUBDOMAIN LOG FIX COMPLETE ===")
Upgrade.stdOut(f"Fixed: {fixed_count} domains")
Upgrade.stdOut(f"Skipped: {skipped_count} domains")
# Create marker file to indicate fix has been applied
try:
with open(fix_marker_file, 'w') as f:
f.write(f"Subdomain log fix applied on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Fixed domains: {fixed_count}\n")
f.write(f"Skipped domains: {skipped_count}\n")
except:
pass
except ImportError as e:
Upgrade.stdOut(f"⚠️ Django not available during upgrade: {str(e)}")
Upgrade.stdOut("Subdomain log fix will be applied on next CyberPanel restart")
except Exception as e:
Upgrade.stdOut(f"❌ Error in subdomain log fix: {str(e)}")
logging.writeToFile(f'Error in subdomain log fix during upgrade: {str(e)}')
@staticmethod
def enableServices():
try:
@@ -4396,6 +4546,9 @@ pm.max_spare_servers = 3
# Fix LiteSpeed configuration files if missing
Upgrade.fixLiteSpeedConfig()
# Fix subdomain log configurations
Upgrade.fixSubdomainLogConfigurations()
### General migrations are not needed any more

View File

@@ -110,13 +110,13 @@ index {
indexFiles index.php, index.html
}
errorlog $VH_ROOT/logs/{masterDomain}.error_log {
errorlog $VH_ROOT/logs/{virtualHostName}.error_log {
useServer 0
logLevel WARN
rollingSize 10M
}
accesslog $VH_ROOT/logs/{masterDomain}.access_log {
accesslog $VH_ROOT/logs/{virtualHostName}.access_log {
useServer 0
logFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
logHeaders 5
@@ -204,7 +204,7 @@ context /.well-known/acme-challenge {
SuexecUserGroup {externalApp} {externalApp}
DocumentRoot {path}
Alias /.well-known/acme-challenge /home/{virtualHostName}/public_html/.well-known/acme-challenge
CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined
CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined
AddHandler application/x-httpd-php{php} .php .php7 .phtml
<IfModule LiteSpeed>
CacheRoot lscache
@@ -474,7 +474,7 @@ pm.max_spare_servers = {pmMaxSpareServers}
"phpVersion": {php},
"custom_conf": {
ServerAdmin {administratorEmail}
CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined
CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined
<IfModule LiteSpeed>
CacheRoot /home/{masterDomain}/lscache
</IfModule>
@@ -489,7 +489,7 @@ pm.max_spare_servers = {pmMaxSpareServers}
"phpVersion": {php},
"custom_conf": {
ServerAdmin {administratorEmail}
CustomLog /home/{masterDomain}/logs/{masterDomain}.access_log combined
CustomLog /home/{virtualHostName}/logs/{virtualHostName}.access_log combined
<IfModule LiteSpeed>
CacheRoot /home/{masterDomain}/lscache
</IfModule>

120
test_subdomain_log_fix.py Normal file
View File

@@ -0,0 +1,120 @@
#!/usr/local/CyberCP/bin/python
"""
Test script for subdomain log fix
This script tests the subdomain log fix functionality
"""
import os
import sys
import django
# Add CyberPanel to Python path
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
from websiteFunctions.models import ChildDomains
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
def test_subdomain_log_configuration():
"""Test if subdomain log configurations are correct"""
print("Testing subdomain log configurations...")
issues_found = 0
child_domains = ChildDomains.objects.all()
if not child_domains:
print("No child domains found.")
return True
for child_domain in child_domains:
domain_name = child_domain.domain
master_domain = child_domain.master.domain
vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf"
if not os.path.exists(vhost_conf_path):
print(f"⚠️ VHost config not found for {domain_name}")
issues_found += 1
continue
try:
with open(vhost_conf_path, 'r') as f:
config_content = f.read()
# Check for incorrect log paths
if f'{master_domain}.error_log' in config_content:
print(f"{domain_name}: Using master domain error log")
issues_found += 1
else:
print(f"{domain_name}: Error log configuration OK")
if f'{master_domain}.access_log' in config_content:
print(f"{domain_name}: Using master domain access log")
issues_found += 1
else:
print(f"{domain_name}: Access log configuration OK")
except Exception as e:
print(f"{domain_name}: Error reading config - {str(e)}")
issues_found += 1
if issues_found == 0:
print("\n🎉 All subdomain log configurations are correct!")
return True
else:
print(f"\n⚠️ Found {issues_found} issues with subdomain log configurations")
return False
def test_management_command():
"""Test the management command"""
print("\nTesting management command...")
try:
from django.core.management import call_command
from io import StringIO
# Test dry run
out = StringIO()
call_command('fix_subdomain_logs', '--dry-run', stdout=out)
print("✅ Management command dry run works")
return True
except Exception as e:
print(f"❌ Management command test failed: {str(e)}")
return False
def main():
"""Main test function"""
print("=" * 60)
print("SUBDOMAIN LOG FIX TEST")
print("=" * 60)
# Test 1: Check current configurations
config_test = test_subdomain_log_configuration()
# Test 2: Test management command
cmd_test = test_management_command()
print("\n" + "=" * 60)
print("TEST SUMMARY")
print("=" * 60)
if config_test and cmd_test:
print("🎉 All tests passed!")
print("\nTo fix any issues found:")
print("1. Via Web Interface: Websites > Fix Subdomain Logs")
print("2. Via CLI: python manage.py fix_subdomain_logs --all")
print("3. For specific domain: python manage.py fix_subdomain_logs --domain example.com")
return True
else:
print("⚠️ Some tests failed. Check the output above.")
return False
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)

View File

@@ -13289,6 +13289,16 @@ 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) {
@@ -13536,6 +13546,64 @@ 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

@@ -175,37 +175,52 @@
<div class="col-sm-12">
<table class="table">
<thead>
<tr>
<th>Domain</th>
<th>Launch</th>
<th>Path</th>
<th>SSL</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="record in childDomains | filter:logSearch">
<td ng-bind="record.childDomain"></td>
<td><a href="{$ record.childLunch $}"><img width="30px" height="30"
class="center-block"
src="{% static 'baseTemplate/assets/image-resources/webPanel.png' %}"></a>
</td>
<td ng-bind="record.path"></td>
<td>
<button type="button"
ng-click="issueSSL(record.childDomain,record.path)"
class="btn ra-50 btn-primary">{% trans "Issue" %}</button>
</td>
<td>
<button type="button"
ng-click="deleteChildDomain(record.childDomain)"
class="btn ra-50 btn-primary">{% trans "Delete" %}</button>
</td>
</tr>
</tbody>
</table>
<!-- Show message when no aliases exist -->
<div ng-hide="{{ noAlias }}" class="alert alert-info">
<p>{% trans "No domain aliases found for this domain." %}</p>
</div>
<!-- Show aliases table when aliases exist -->
<div ng-show="{{ noAlias }}">
<table class="table">
<thead>
<tr>
<th>{% trans "Alias Domain" %}</th>
<th>{% trans "Master Domain" %}</th>
<th>{% trans "Launch" %}</th>
<th>{% trans "SSL" %}</th>
<th>{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
{% for alias in aliases %}
<tr>
<td>{{ alias }}</td>
<td>{{ masterDomain }}</td>
<td>
<a href="http://{{ alias }}" target="_blank">
<img width="30px" height="30" class="center-block"
src="{% static 'baseTemplate/assets/image-resources/webPanel.png' %}">
</a>
</td>
<td>
<button type="button"
ng-click="issueAliasSSL('{{ masterDomain }}', '{{ alias }}')"
class="btn ra-50 btn-primary">{% trans "Issue SSL" %}</button>
</td>
<td>
<button type="button"
ng-click="removeAlias('{{ masterDomain }}', '{{ alias }}')"
class="btn ra-50 btn-danger"
onclick="return confirm('{% trans "Are you sure you want to delete this alias?" %}')">
{% trans "Delete" %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,334 @@
{% extends "baseTemplate/index.html" %}
{% load static %}
{% load i18n %}
{% block title %}Fix Subdomain Logs{% endblock %}
{% block content %}
<div class="content-wrapper">
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1 class="m-0">Fix Subdomain Logs</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{% url 'loadWebsitesHome' %}">Websites</a></li>
<li class="breadcrumb-item active">Fix Subdomain Logs</li>
</ol>
</div>
</div>
</div>
</div>
<section class="content">
<div class="container-fluid">
<!-- Information Card -->
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Subdomain Log Issue Fix</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<h5><i class="icon fas fa-info"></i> Issue Description</h5>
<p>This tool fixes a known issue where child domains (subdomains) share log files with their master domains, causing log contamination. This fix ensures each subdomain has its own separate log files.</p>
</div>
<div class="alert alert-warning">
<h5><i class="icon fas fa-exclamation-triangle"></i> What This Fix Does</h5>
<ul>
<li>Updates vHost configurations to use separate log files for each subdomain</li>
<li>Creates individual log files: <code>subdomain.error_log</code> and <code>subdomain.access_log</code></li>
<li>Restarts LiteSpeed to apply changes</li>
<li>Maintains proper file ownership and permissions</li>
</ul>
</div>
</div>
</div>
<!-- Fix Options -->
<div class="card card-success">
<div class="card-header">
<h3 class="card-title">Fix Options</h3>
</div>
<div class="card-body">
<form id="fixLogsForm">
{% csrf_token %}
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label>Action</label>
<select class="form-control" id="action" name="action" required>
<option value="">Select Action</option>
<option value="fix_all">Fix All Child Domains</option>
<option value="fix_domain">Fix Specific Domain</option>
</select>
</div>
</div>
<div class="col-md-6" id="domainInputGroup" style="display: none;">
<div class="form-group">
<label>Domain Name</label>
<input type="text" class="form-control" id="domain" name="domain" placeholder="e.g., diabetes.example.com">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dryRun" name="dry_run">
<label class="form-check-label" for="dryRun">
Dry Run (Show what would be changed without making changes)
</label>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="createBackup" name="create_backup">
<label class="form-check-label" for="createBackup">
Create Backup of Original Configuration
</label>
</div>
</div>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" id="fixButton">
<i class="fas fa-wrench"></i> Fix Subdomain Logs
</button>
<button type="button" class="btn btn-info" id="checkButton">
<i class="fas fa-search"></i> Check Current Status
</button>
</div>
</form>
</div>
</div>
<!-- Results -->
<div class="card card-default" id="resultsCard" style="display: none;">
<div class="card-header">
<h3 class="card-title">Results</h3>
</div>
<div class="card-body">
<div id="resultsContent"></div>
</div>
</div>
<!-- Child Domains List -->
<div class="card card-info">
<div class="card-header">
<h3 class="card-title">Current Child Domains</h3>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped" id="childDomainsTable">
<thead>
<tr>
<th>Domain</th>
<th>Master Domain</th>
<th>Log Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const actionSelect = document.getElementById('action');
const domainInputGroup = document.getElementById('domainInputGroup');
const domainInput = document.getElementById('domain');
const fixButton = document.getElementById('fixButton');
const checkButton = document.getElementById('checkButton');
const resultsCard = document.getElementById('resultsCard');
const resultsContent = document.getElementById('resultsContent');
const childDomainsTable = document.getElementById('childDomainsTable');
// Show/hide domain input based on action selection
actionSelect.addEventListener('change', function() {
if (this.value === 'fix_domain') {
domainInputGroup.style.display = 'block';
domainInput.required = true;
} else {
domainInputGroup.style.display = 'none';
domainInput.required = false;
}
});
// Load child domains list
loadChildDomains();
// Check current status
checkButton.addEventListener('click', function() {
loadChildDomains();
showResults('Checking current status...', 'info');
});
// Fix logs form submission
document.getElementById('fixLogsForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const action = formData.get('action');
const domain = formData.get('domain');
if (!action) {
showResults('Please select an action', 'error');
return;
}
if (action === 'fix_domain' && !domain.trim()) {
showResults('Please enter a domain name', 'error');
return;
}
fixButton.disabled = true;
fixButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
fetch('{% url "fixSubdomainLogsAction" %}', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
fixButton.disabled = false;
fixButton.innerHTML = '<i class="fas fa-wrench"></i> Fix Subdomain Logs';
if (data.status === 1) {
showResults(data.message, 'success');
loadChildDomains(); // Refresh the list
} else {
showResults(data.error_message, 'error');
}
})
.catch(error => {
fixButton.disabled = false;
fixButton.innerHTML = '<i class="fas fa-wrench"></i> Fix Subdomain Logs';
showResults('Error: ' + error.message, 'error');
});
});
function loadChildDomains() {
fetch('{% url "fetchChildDomainsMain" %}')
.then(response => response.json())
.then(data => {
if (data.status === 1) {
populateChildDomainsTable(data.data);
}
})
.catch(error => {
console.error('Error loading child domains:', error);
});
}
function populateChildDomainsTable(domains) {
const tbody = childDomainsTable.querySelector('tbody');
tbody.innerHTML = '';
domains.forEach(domain => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${domain.domain}</td>
<td>${domain.master}</td>
<td><span class="badge badge-warning">Needs Fix</span></td>
<td>
<button class="btn btn-sm btn-primary" onclick="fixSingleDomain('${domain.domain}')">
<i class="fas fa-wrench"></i> Fix
</button>
</td>
`;
tbody.appendChild(row);
});
}
function fixSingleDomain(domain) {
const formData = new FormData();
formData.append('action', 'fix_domain');
formData.append('domain', domain);
formData.append('dry_run', 'false');
formData.append('create_backup', 'true');
formData.append('csrfmiddlewaretoken', getCookie('csrftoken'));
showResults(`Fixing logs for ${domain}...`, 'info');
fetch('{% url "fixSubdomainLogsAction" %}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 1) {
showResults(data.message, 'success');
loadChildDomains(); // Refresh the list
} else {
showResults(data.error_message, 'error');
}
})
.catch(error => {
showResults('Error: ' + error.message, 'error');
});
}
function showResults(message, type) {
resultsCard.style.display = 'block';
let alertClass = 'alert-info';
let icon = 'fas fa-info';
if (type === 'success') {
alertClass = 'alert-success';
icon = 'fas fa-check';
} else if (type === 'error') {
alertClass = 'alert-danger';
icon = 'fas fa-times';
} else if (type === 'warning') {
alertClass = 'alert-warning';
icon = 'fas fa-exclamation-triangle';
}
resultsContent.innerHTML = `
<div class="alert ${alertClass}">
<i class="${icon}"></i> ${message}
</div>
`;
// Scroll to results
resultsCard.scrollIntoView({ behavior: 'smooth' });
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
});
</script>
{% endblock %}

View File

@@ -207,5 +207,9 @@ urlpatterns = [
path('get_website_resources/', views.get_website_resources, name='get_website_resources'),
# Subdomain Log Fix
path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'),
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'),
]

View File

@@ -3936,6 +3936,7 @@ context /cyberpanel_suspension_page.html {
if is_child:
# Use child domain template
default_config = vhostConfs.olsChildConf
default_config = default_config.replace('{virtualHostName}', virtualHost)
default_config = default_config.replace('{path}', path)
default_config = default_config.replace('{masterDomain}', master_domain)
default_config = default_config.replace('{adminEmails}', admin_email)
@@ -8200,3 +8201,174 @@ StrictHostKeyChecking no
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def fixSubdomainLogs(self, request):
"""Display subdomain log fix interface"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
if ACLManager.currentContextPermission(currentACL, 'websites') == 0:
return ACLManager.loadErrorJson('websites', 0)
return render(request, 'websiteFunctions/fixSubdomainLogs.html', {
'acls': currentACL,
'admin': admin
})
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [fixSubdomainLogs]")
return ACLManager.loadErrorJson('websites', 0)
def fixSubdomainLogsAction(self, request):
"""Execute subdomain log fix"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
if ACLManager.currentContextPermission(currentACL, 'websites') == 0:
return ACLManager.loadErrorJson('websites', 0)
action = request.POST.get('action')
domain = request.POST.get('domain', '').strip()
dry_run = request.POST.get('dry_run', 'false').lower() == 'true'
create_backup = request.POST.get('create_backup', 'false').lower() == 'true'
if action == 'fix_all':
# Fix all child domains
from websiteFunctions.models import ChildDomains
child_domains = ChildDomains.objects.all()
fixed_count = 0
failed_domains = []
for child_domain in child_domains:
if self._fix_single_domain_logs(child_domain.domain, dry_run, create_backup):
fixed_count += 1
else:
failed_domains.append(child_domain.domain)
if failed_domains:
message = f"Fixed {fixed_count} domains. Failed: {', '.join(failed_domains)}"
else:
message = f"Successfully fixed {fixed_count} child domains"
data_ret = {'status': 1, 'message': message}
elif action == 'fix_domain' and domain:
# Fix specific domain
if self._fix_single_domain_logs(domain, dry_run, create_backup):
data_ret = {'status': 1, 'message': f"Successfully fixed logs for {domain}"}
else:
data_ret = {'status': 0, 'error_message': f"Failed to fix logs for {domain}"}
else:
data_ret = {'status': 0, 'error_message': "Invalid action or missing domain"}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(str(msg) + " [fixSubdomainLogsAction]")
data_ret = {'status': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
def _fix_single_domain_logs(self, domain_name, dry_run=False, create_backup=False):
"""Fix log configuration for a single domain"""
try:
import re
import shutil
from datetime import datetime
from websiteFunctions.models import ChildDomains
# Get child domain info
try:
child_domain = ChildDomains.objects.get(domain=domain_name)
master_domain = child_domain.master.domain
domain_path = child_domain.path
except ChildDomains.DoesNotExist:
logging.CyberCPLogFileWriter.writeToFile(f'Domain {domain_name} is not a child domain')
return False
vhost_conf_path = f"/usr/local/lsws/conf/vhosts/{domain_name}/vhost.conf"
if not os.path.exists(vhost_conf_path):
logging.CyberCPLogFileWriter.writeToFile(f'VHost config not found for {domain_name}')
return False
# Read current configuration
with open(vhost_conf_path, 'r') as f:
config_content = f.read()
# Check if fix is needed
if f'{master_domain}.error_log' not in config_content and f'{master_domain}.access_log' not in config_content:
logging.CyberCPLogFileWriter.writeToFile(f'{domain_name} already has correct log configuration')
return True
if dry_run:
logging.CyberCPLogFileWriter.writeToFile(f'[DRY RUN] Would fix log paths for {domain_name}')
return True
# Create backup if requested
if create_backup:
backup_path = f"{vhost_conf_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(vhost_conf_path, backup_path)
logging.CyberCPLogFileWriter.writeToFile(f'Created backup: {backup_path}')
# Fix the configuration
fixed_content = config_content
# Fix error log path
fixed_content = re.sub(
rf'errorlog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.error_log',
f'errorlog $VH_ROOT/logs/{domain_name}.error_log',
fixed_content
)
# Fix access log path
fixed_content = re.sub(
rf'accesslog\s+\$VH_ROOT/logs/{re.escape(master_domain)}\.access_log',
f'accesslog $VH_ROOT/logs/{domain_name}.access_log',
fixed_content
)
# Fix CustomLog paths (for Apache configurations)
fixed_content = re.sub(
rf'CustomLog\s+/home/{re.escape(master_domain)}/logs/{re.escape(master_domain)}\.access_log',
f'CustomLog /home/{domain_name}/logs/{domain_name}.access_log',
fixed_content
)
# Write the fixed configuration
with open(vhost_conf_path, 'w') as f:
f.write(fixed_content)
# Set proper ownership
ProcessUtilities.executioner(f'chown lsadm:lsadm {vhost_conf_path}')
# Create the log directory if it doesn't exist
log_dir = f"/home/{master_domain}/logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
ProcessUtilities.executioner(f'chown -R {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_dir}')
# Create separate log files for the child domain
error_log_path = f"{log_dir}/{domain_name}.error_log"
access_log_path = f"{log_dir}/{domain_name}.access_log"
# Create empty log files if they don't exist
for log_path in [error_log_path, access_log_path]:
if not os.path.exists(log_path):
with open(log_path, 'w') as f:
f.write('')
ProcessUtilities.executioner(f'chown {child_domain.master.externalApp}:{child_domain.master.externalApp} {log_path}')
ProcessUtilities.executioner(f'chmod 644 {log_path}')
# Restart LiteSpeed to apply changes
ProcessUtilities.executioner('systemctl restart lsws')
logging.CyberCPLogFileWriter.writeToFile(f'Fixed subdomain log configuration for {domain_name}')
return True
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error fixing subdomain logs for {domain_name}: {str(e)}')
return False