Sync with live: baseTemplate, firewall, manageSSL, plogical/acl, ftp, websiteFunctions, wsgi

Only files that match current live server; excludes settings.py (deployment-specific), pluginHolder/pluginInstaller (repo ahead), install/cyberpanel scripts (diff), and deleted static files (still on server).
This commit is contained in:
master3395
2026-02-03 19:50:17 +01:00
parent 02c8a9b6ac
commit 4c24de7453
12 changed files with 1387 additions and 316 deletions

View File

@@ -8,7 +8,13 @@ https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""
import os
import sys
# Ensure CyberPanel app path takes precedence over system 'firewall' package
PROJECT_ROOT = '/usr/local/CyberCP'
while PROJECT_ROOT in sys.path:
sys.path.remove(PROJECT_ROOT)
sys.path.insert(0, PROJECT_ROOT)
from django.core.wsgi import get_wsgi_application

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
import os
import time
from .views import VERSION, BUILD
def version_context(request):
@@ -49,4 +52,15 @@ def notification_preferences_context(request):
return {
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}
def firewall_static_context(request):
"""Expose a cache-busting token for firewall static assets."""
firewall_js_path = '/usr/local/CyberCP/static/firewall/firewall.js'
try:
version = int(os.path.getmtime(firewall_js_path))
except OSError:
version = int(time.time())
return {
'FIREWALL_STATIC_VERSION': version
}

View File

@@ -876,6 +876,29 @@
</table>
</div>
<!-- SSH Logins Pagination -->
<div ng-if="!loadingSSHLogins && sshLogins.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="sshLoginsPerPage" ng-change="sshLoginsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ (sshLoginsPage - 1) * sshLoginsPerPage + 1 $}-{$ (sshLoginsPage * sshLoginsPerPage > sshLoginsTotal ? sshLoginsTotal : sshLoginsPage * sshLoginsPerPage) $} of {$ sshLoginsTotal $}</span>
<button ng-click="sshLoginsGoToPage(sshLoginsPage - 1)" ng-disabled="sshLoginsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<button ng-click="sshLoginsGoToPage(sshLoginsPage + 1)" ng-disabled="sshLoginsPage >= sshLoginsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<!-- Dummy data for demonstration -->
<table class="activity-table records-table" ng-if="loadingSSHLogins">
<thead>
@@ -1065,6 +1088,29 @@
</tr>
</tbody>
</table>
<!-- SSH Logs Pagination -->
<div ng-if="!loadingSSHLogs && sshLogs.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="sshLogsPerPage" ng-change="sshLogsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ (sshLogsPage - 1) * sshLogsPerPage + 1 $}-{$ (sshLogsPage * sshLogsPerPage > sshLogsTotal ? sshLogsTotal : sshLogsPage * sshLogsPerPage) $} of {$ sshLogsTotal $}</span>
<button ng-click="sshLogsGoToPage(sshLogsPage - 1)" ng-disabled="sshLogsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<button ng-click="sshLogsGoToPage(sshLogsPage + 1)" ng-disabled="sshLogsPage >= sshLogsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
</div>
<!-- Top Process Tab -->

View File

@@ -1835,9 +1835,7 @@
<a href="{% url 'manageSSL' %}" class="menu-item">
<span>Manage SSL</span>
</a>
<a href="{% url 'sslReconcile' %}" class="menu-item">
<span>SSL Reconciliation</span>
</a>
{% comment %}SSL Reconciliation - hidden; URL /manageSSL/sslReconcile still works if needed{% endcomment %}
{% endif %}
{% if admin or hostnameSSL %}
<a href="{% url 'sslForHostName' %}" class="menu-item">
@@ -2190,7 +2188,7 @@
<script src="{% static 'managePHP/managePHP.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverLogs/serverLogs.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverStatus/serverStatus.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}&fw={{ FIREWALL_STATIC_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'emailPremium/emailPremium.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'manageServices/manageServices.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'CLManager/CLManager.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>

View File

@@ -716,9 +716,19 @@ def getRecentSSHLogins(request):
import re, time
from collections import OrderedDict
# Run 'last -n 20' to get recent SSH logins
# Pagination params
try:
output = ProcessUtilities.outputExecutioner('last -n 20')
page = max(1, int(request.GET.get('page', 1)))
except (ValueError, TypeError):
page = 1
try:
per_page = min(100, max(5, int(request.GET.get('per_page', 20))))
except (ValueError, TypeError):
per_page = 20
# Run 'last -n 500' to get enough entries for pagination
try:
output = ProcessUtilities.outputExecutioner('last -n 500')
except Exception as e:
return HttpResponse(json.dumps({'error': 'Failed to run last: %s' % str(e)}), content_type='application/json', status=500)
@@ -802,7 +812,19 @@ def getRecentSSHLogins(request):
'is_active': is_active,
'raw': line
})
return HttpResponse(json.dumps({'logins': logins}), content_type='application/json')
total = len(logins)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = min(page, total_pages) if total_pages > 0 else 1
start = (page - 1) * per_page
end = start + per_page
paginated_logins = logins[start:end]
return HttpResponse(json.dumps({
'logins': paginated_logins,
'total': total,
'page': page,
'per_page': per_page,
'total_pages': total_pages
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@@ -816,6 +838,17 @@ def getRecentSSHLogs(request):
currentACL = ACLManager.loadedACL(user_id)
if not currentACL.get('admin', 0):
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
# Pagination params
try:
page = max(1, int(request.GET.get('page', 1)))
except (ValueError, TypeError):
page = 1
try:
per_page = min(100, max(5, int(request.GET.get('per_page', 25))))
except (ValueError, TypeError):
per_page = 25
from plogical.processUtilities import ProcessUtilities
import re
distro = ProcessUtilities.decideDistro()
@@ -824,7 +857,7 @@ def getRecentSSHLogs(request):
else:
log_path = '/var/log/secure'
try:
output = ProcessUtilities.outputExecutioner(f'tail -n 100 {log_path}')
output = ProcessUtilities.outputExecutioner(f'tail -n 500 {log_path}')
except Exception as e:
return HttpResponse(json.dumps({'error': f'Failed to read log: {str(e)}'}), content_type='application/json', status=500)
lines = output.split('\n')
@@ -862,7 +895,21 @@ def getRecentSSHLogs(request):
'raw': line,
'ip_address': ip_address
})
return HttpResponse(json.dumps({'logs': logs}), content_type='application/json')
# Reverse so newest logs appear first (page 1 = most recent)
logs.reverse()
total = len(logs)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = min(page, total_pages) if total_pages > 0 else 1
start = (page - 1) * per_page
end = start + per_page
paginated_logs = logs[start:end]
return HttpResponse(json.dumps({
'logs': paginated_logs,
'total': total,
'page': page,
'per_page': per_page,
'total_pages': total_pages
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@@ -1325,7 +1372,9 @@ def blockIPAddress(request):
try:
import os
import time
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
primary_file = '/usr/local/CyberCP/data/banned_ips.json'
legacy_file = '/etc/cyberpanel/banned_ips.json'
banned_ips_file = primary_file if os.path.exists(primary_file) else legacy_file if os.path.exists(legacy_file) else primary_file
banned_ips = []
if os.path.exists(banned_ips_file):
@@ -1359,10 +1408,10 @@ def blockIPAddress(request):
banned_ips.append(new_banned_ip)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
os.makedirs(os.path.dirname(primary_file), exist_ok=True)
# Save to file
with open(banned_ips_file, 'w') as f:
with open(primary_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
except Exception as e:
# Log but don't fail the request if JSON update fails

View File

@@ -4,10 +4,13 @@ import os.path
import sys
import django
PROJECT_ROOT = '/usr/local/CyberCP'
while PROJECT_ROOT in sys.path:
sys.path.remove(PROJECT_ROOT)
sys.path.insert(0, PROJECT_ROOT)
from loginSystem.models import Administrator
from plogical.httpProc import httpProc
sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
import json
@@ -30,10 +33,40 @@ class FirewallManager:
imunifyPath = '/usr/bin/imunify360-agent'
CLPath = '/etc/sysconfig/cloudlinux'
imunifyAVPath = '/etc/sysconfig/imunify360/integration.conf'
BANNED_IPS_PRIMARY_FILE = '/usr/local/CyberCP/data/banned_ips.json'
BANNED_IPS_LEGACY_FILE = '/etc/cyberpanel/banned_ips.json'
def __init__(self, request = None):
self.request = request
def _load_banned_ips_store(self):
"""
Load banned IPs from the primary store, falling back to legacy path.
Returns (data, path_used)
"""
for path in [self.BANNED_IPS_PRIMARY_FILE, self.BANNED_IPS_LEGACY_FILE]:
if os.path.exists(path):
try:
with open(path, 'r') as f:
return json.load(f), path
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to read banned IPs from {path}: {str(e)}')
return [], self.BANNED_IPS_PRIMARY_FILE
def _save_banned_ips_store(self, data):
"""
Persist banned IPs to the primary store in a writable location.
"""
target = self.BANNED_IPS_PRIMARY_FILE
try:
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, 'w') as f:
json.dump(data, f, indent=2)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to write banned IPs to {target}: {str(e)}')
raise
return target
def securityHome(self, request = None, userID = None):
proc = httpProc(request, 'firewall/index.html',
None, 'admin')
@@ -1810,49 +1843,87 @@ class FirewallManager:
def getBannedIPs(self, userID=None):
"""
Get list of banned IP addresses from database
Get list of banned IP addresses from database, or fall back to JSON file.
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
# Import BannedIP model and Django Q
from firewall.models import BannedIP
from django.db.models import Q
# Get all active banned IPs that haven't expired
current_time = int(time.time())
banned_ips_queryset = BannedIP.objects.filter(
active=True
).filter(
Q(expires__isnull=True) | Q(expires__gt=current_time)
).order_by('-banned_on')
active_banned_ips = []
for banned_ip in banned_ips_queryset:
# Format the data for frontend
ip_data = {
'id': banned_ip.id,
'ip': banned_ip.ip_address,
'reason': banned_ip.reason,
'duration': banned_ip.duration,
'banned_on': banned_ip.get_banned_on_display(),
'expires': banned_ip.get_expires_display(),
'active': not banned_ip.is_expired() and banned_ip.active
}
# Only include truly active bans
if ip_data['active']:
active_banned_ips.append(ip_data)
try:
from firewall.models import BannedIP
from django.db.models import Q
current_time = int(time.time())
banned_ips_queryset = BannedIP.objects.filter(
active=True
).filter(
Q(expires__isnull=True) | Q(expires__gt=current_time)
).order_by('-banned_on')
for banned_ip in banned_ips_queryset:
ip_data = {
'id': banned_ip.id,
'ip': banned_ip.ip_address,
'reason': banned_ip.reason,
'duration': banned_ip.duration,
'banned_on': banned_ip.get_banned_on_display(),
'expires': banned_ip.get_expires_display(),
'active': not banned_ip.is_expired() and banned_ip.active
}
if ip_data['active']:
active_banned_ips.append(ip_data)
except (ImportError, AttributeError) as e:
# Fall back to JSON file when BannedIP model unavailable
import plogical.CyberCPLogFileWriter as _log
_log.CyberCPLogFileWriter.writeToFile('getBannedIPs: using JSON fallback (%s)' % str(e))
active_banned_ips = []
# If DB returns nothing (or model not available), merge in JSON fallback
if not active_banned_ips:
banned_ips, _ = self._load_banned_ips_store()
for b in banned_ips:
if not b.get('active', True):
continue
exp = b.get('expires')
if exp == 'Never' or exp is None:
expires_display = 'Never'
elif isinstance(exp, (int, float)):
from datetime import datetime
try:
expires_display = datetime.fromtimestamp(exp).strftime('%Y-%m-%d %H:%M:%S')
except Exception:
expires_display = 'Never'
else:
expires_display = str(exp)
banned_on = b.get('banned_on')
if isinstance(banned_on, (int, float)):
from datetime import datetime
try:
banned_on = datetime.fromtimestamp(banned_on).strftime('%Y-%m-%d %H:%M:%S')
except Exception:
banned_on = 'N/A'
else:
banned_on = str(banned_on) if banned_on else 'N/A'
active_banned_ips.append({
'id': b.get('id'),
'ip': b.get('ip', ''),
'reason': b.get('reason', ''),
'duration': b.get('duration', 'permanent'),
'banned_on': banned_on,
'expires': expires_display,
'active': True
})
final_dic = {'status': 1, 'bannedIPs': active_banned_ips}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
except BaseException as msg:
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'Error in getBannedIPs: {str(msg)}')
logging.CyberCPLogFileWriter.writeToFile('Error in getBannedIPs: %s' % str(msg))
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
@@ -1872,8 +1943,7 @@ class FirewallManager:
if not ip or not reason:
final_dic = {'status': 0, 'error_message': 'IP address and reason are required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(json.dumps(final_dic), content_type='application/json')
# Validate IP address format
import ipaddress
@@ -1881,8 +1951,7 @@ class FirewallManager:
ipaddress.ip_address(ip.split('/')[0]) # Handle CIDR notation
except ValueError:
final_dic = {'status': 0, 'error_message': 'Invalid IP address format'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(json.dumps(final_dic), content_type='application/json')
# Calculate expiration time
current_time = time.time()
@@ -1899,21 +1968,13 @@ class FirewallManager:
expires = current_time + duration_seconds
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except:
banned_ips = []
banned_ips, _ = self._load_banned_ips_store()
# Check if IP is already banned
for banned_ip in banned_ips:
if banned_ip.get('ip') == ip and banned_ip.get('active', True):
final_dic = {'status': 0, 'error_message': f'IP address {ip} is already banned'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
final_dic = {'status': 0, 'error_message': 'IP address %s is already banned' % ip}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
# Add new banned IP
new_banned_ip = {
@@ -1927,12 +1988,8 @@ class FirewallManager:
}
banned_ips.append(new_banned_ip)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
# Save to file
with open(banned_ips_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
self._save_banned_ips_store(banned_ips)
# Apply firewall rule to block the IP using firewalld
try:
@@ -1942,8 +1999,7 @@ class FirewallManager:
capture_output=True, text=True, timeout=10)
if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout):
final_dic = {'status': 0, 'error_message': 'Firewalld is not active. Please enable firewalld service.'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(json.dumps(final_dic), content_type='application/json')
# Add firewalld rich rule to block the IP
rich_rule = f'rule family=ipv4 source address={ip} drop'
@@ -1958,42 +2014,37 @@ class FirewallManager:
if reload_result.returncode == 0:
logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to reload firewalld for {ip}: {reload_result.stderr}')
final_dic = {'status': 0, 'error_message': f'Failed to reload firewall rules: {reload_result.stderr}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile('Failed to reload firewalld for %s: %s' % (ip, reload_result.stderr))
final_dic = {'status': 0, 'error_message': 'Failed to reload firewall rules: %s' % reload_result.stderr}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
else:
# Check if rule already exists (this is not an error)
if 'ALREADY_ENABLED' in result.stderr or 'already exists' in result.stderr.lower():
logging.CyberCPLogFileWriter.writeToFile(f'IP {ip} already blocked in firewalld')
if result.stderr and ('ALREADY_ENABLED' in result.stderr or 'already exists' in result.stderr.lower()):
logging.CyberCPLogFileWriter.writeToFile('IP %s already blocked in firewalld' % ip)
else:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to add firewalld rule for {ip}: {result.stderr}')
final_dic = {'status': 0, 'error_message': f'Failed to add firewall rule: {result.stderr}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, result.stderr or ''))
final_dic = {'status': 0, 'error_message': 'Failed to add firewall rule: %s' % (result.stderr or 'Unknown error')}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
except subprocess.TimeoutExpired:
logging.CyberCPLogFileWriter.writeToFile(f'Timeout adding firewalld rule for {ip}')
logging.CyberCPLogFileWriter.writeToFile('Timeout adding firewalld rule for %s' % ip)
final_dic = {'status': 0, 'error_message': 'Firewall command timed out'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(json.dumps(final_dic), content_type='application/json')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to add firewalld rule for {ip}: {str(e)}')
final_dic = {'status': 0, 'error_message': f'Firewall command failed: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, str(e)))
final_dic = {'status': 0, 'error_message': 'Firewall command failed: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
final_dic = {'status': 1, 'message': f'IP address {ip} has been banned successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
final_dic = {'status': 1, 'message': 'IP address %s has been banned successfully' % ip}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(json.dumps(final_dic), content_type='application/json')
def removeBannedIP(self, userID=None, data=None):
"""
Remove/unban an IP address
Remove/unban an IP address.
Supports both BannedIP database model and JSON file storage.
"""
try:
admin = Administrator.objects.get(pk=userID)
@@ -2001,21 +2052,65 @@ class FirewallManager:
return ACLManager.loadError()
banned_ip_id = data.get('id')
requested_ip = (data.get('ip') or '').strip()
ip_to_unban = None
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
if isinstance(banned_ip_id, str) and banned_ip_id.isdigit():
banned_ip_id = int(banned_ip_id)
elif isinstance(banned_ip_id, float):
banned_ip_id = int(banned_ip_id)
except Exception:
pass
# Try database (BannedIP model) first - ids are typically small integers
try:
from firewall.models import BannedIP
except Exception as e:
BannedIP = None
logging.CyberCPLogFileWriter.writeToFile(f'Warning: BannedIP model import failed, using JSON fallback: {str(e)}')
if BannedIP is not None:
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except:
banned_ips = []
banned_ip = None
if banned_ip_id not in (None, ''):
try:
banned_ip = BannedIP.objects.get(pk=banned_ip_id)
except BannedIP.DoesNotExist:
banned_ip = None
if banned_ip is None and requested_ip:
banned_ip = BannedIP.objects.filter(ip_address=requested_ip).first()
if banned_ip is None:
raise BannedIP.DoesNotExist()
ip_to_unban = banned_ip.ip_address
banned_ip.active = False
banned_ip.save()
# Remove firewalld rule
try:
import subprocess
firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'],
capture_output=True, text=True, timeout=10)
if firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout:
rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop'
subprocess.run(['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule],
capture_output=True, text=True, timeout=30)
subprocess.run(['firewall-cmd', '--reload'], capture_output=True, text=True, timeout=30)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning removing firewalld rule: {str(e)}')
final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
except BannedIP.DoesNotExist:
pass
# Fall back to JSON file storage
banned_ips, _ = self._load_banned_ips_store()
# Find and update the banned IP
ip_to_unban = None
for banned_ip in banned_ips:
if banned_ip.get('id') == banned_ip_id:
id_match = banned_ip_id not in (None, '') and str(banned_ip.get('id')) == str(banned_ip_id)
ip_match = requested_ip and str(banned_ip.get('ip', '')).strip() == requested_ip
if id_match or ip_match:
banned_ip['active'] = False
banned_ip['unbanned_on'] = time.time()
ip_to_unban = banned_ip['ip']
@@ -2024,11 +2119,10 @@ class FirewallManager:
if not ip_to_unban:
final_dic = {'status': 0, 'error_message': 'Banned IP not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
self._save_banned_ips_store(banned_ips)
# Remove firewalld rule to unblock the IP
try:
@@ -2067,16 +2161,17 @@ class FirewallManager:
final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
def deleteBannedIP(self, userID=None, data=None):
"""
Permanently delete a banned IP record
Permanently delete a banned IP record.
Supports both BannedIP database model and JSON file storage.
"""
try:
admin = Administrator.objects.get(pk=userID)
@@ -2084,22 +2179,53 @@ class FirewallManager:
return ACLManager.loadError()
banned_ip_id = data.get('id')
requested_ip = (data.get('ip') or '').strip()
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
banned_ips = []
if os.path.exists(banned_ips_file):
try:
if isinstance(banned_ip_id, str) and banned_ip_id.isdigit():
banned_ip_id = int(banned_ip_id)
elif isinstance(banned_ip_id, float):
banned_ip_id = int(banned_ip_id)
except Exception:
pass
# Try database (BannedIP model) first
try:
from firewall.models import BannedIP
except Exception as e:
BannedIP = None
logging.CyberCPLogFileWriter.writeToFile(f'Warning: BannedIP model import failed, using JSON fallback: {str(e)}')
if BannedIP is not None:
try:
with open(banned_ips_file, 'r') as f:
banned_ips = json.load(f)
except:
banned_ips = []
banned_ip = None
if banned_ip_id not in (None, ''):
try:
banned_ip = BannedIP.objects.get(pk=banned_ip_id)
except BannedIP.DoesNotExist:
banned_ip = None
if banned_ip is None and requested_ip:
banned_ip = BannedIP.objects.filter(ip_address=requested_ip).first()
if banned_ip is None:
raise BannedIP.DoesNotExist()
ip_to_delete = banned_ip.ip_address
banned_ip.delete()
logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
final_dic = {'status': 1, 'message': f'Banned IP record for {ip_to_delete} has been deleted successfully'}
return HttpResponse(json.dumps(final_dic), content_type='application/json')
except BannedIP.DoesNotExist:
pass
# Fall back to JSON file storage
banned_ips, _ = self._load_banned_ips_store()
# Find and remove the banned IP
ip_to_delete = None
updated_banned_ips = []
for banned_ip in banned_ips:
if banned_ip.get('id') == banned_ip_id:
id_match = banned_ip_id not in (None, '') and str(banned_ip.get('id')) == str(banned_ip_id)
ip_match = requested_ip and str(banned_ip.get('ip', '')).strip() == requested_ip
if id_match or ip_match:
ip_to_delete = banned_ip['ip']
else:
updated_banned_ips.append(banned_ip)
@@ -2107,22 +2233,147 @@ class FirewallManager:
if not ip_to_delete:
final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
json.dump(updated_banned_ips, f, indent=2)
self._save_banned_ips_store(updated_banned_ips)
logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
final_dic = {'status': 1, 'message': f'Banned IP record for {ip_to_delete} has been deleted successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
def modifyBannedIP(self, userID=None, data=None):
"""
Modify an existing banned IP record (reason, duration).
Supports both BannedIP database model and JSON file storage.
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
banned_ip_id = data.get('id')
requested_ip = (data.get('ip') or '').strip()
reason = data.get('reason', '').strip()
duration = data.get('duration', '').strip()
try:
if isinstance(banned_ip_id, str) and banned_ip_id.isdigit():
banned_ip_id = int(banned_ip_id)
elif isinstance(banned_ip_id, float):
banned_ip_id = int(banned_ip_id)
except Exception:
pass
if banned_ip_id in (None, '') and not requested_ip:
final_dic = {'status': 0, 'error_message': 'Banned IP ID or IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
if not reason:
final_dic = {'status': 0, 'error_message': 'Reason is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
# Try database (BannedIP model) first - ids are typically small integers
try:
from firewall.models import BannedIP
except Exception as e:
BannedIP = None
logging.CyberCPLogFileWriter.writeToFile(f'Warning: BannedIP model import failed, using JSON fallback: {str(e)}')
if BannedIP is not None:
try:
banned_ip = None
if banned_ip_id not in (None, ''):
try:
banned_ip = BannedIP.objects.get(pk=banned_ip_id)
except BannedIP.DoesNotExist:
banned_ip = None
if banned_ip is None and requested_ip:
banned_ip = BannedIP.objects.filter(ip_address=requested_ip).first()
if banned_ip is None:
raise BannedIP.DoesNotExist()
if not banned_ip.active:
final_dic = {'status': 0, 'error_message': 'Cannot modify an inactive/expired ban'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
banned_ip.reason = reason
if duration:
banned_ip.duration = duration
duration_map = {
'1h': 3600,
'24h': 86400,
'7d': 604800,
'30d': 2592000,
'permanent': None
}
if duration == 'permanent':
banned_ip.expires = None
else:
duration_seconds = duration_map.get(duration, 86400)
banned_ip.expires = int(time.time()) + duration_seconds
banned_ip.save()
logging.CyberCPLogFileWriter.writeToFile(f'Modified banned IP {banned_ip.ip_address} (id={banned_ip_id})')
final_dic = {'status': 1, 'message': f'Banned IP {banned_ip.ip_address} has been updated successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
except BannedIP.DoesNotExist:
pass
# Fall back to JSON file storage (ids are timestamps)
banned_ips, _ = self._load_banned_ips_store()
updated = False
for banned_ip in banned_ips:
id_match = banned_ip_id not in (None, '') and str(banned_ip.get('id')) == str(banned_ip_id)
ip_match = requested_ip and str(banned_ip.get('ip', '')).strip() == requested_ip
if (id_match or ip_match) and banned_ip.get('active', True):
banned_ip['reason'] = reason
if duration:
banned_ip['duration'] = duration
if duration == 'permanent':
banned_ip['expires'] = 'Never'
else:
duration_map = {
'1h': 3600,
'24h': 86400,
'7d': 604800,
'30d': 2592000
}
duration_seconds = duration_map.get(duration, 86400)
banned_ip['expires'] = time.time() + duration_seconds
updated = True
break
if not updated:
final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
self._save_banned_ips_store(banned_ips)
ip_addr = next((b.get('ip') for b in banned_ips if (str(b.get('id')) == str(banned_ip_id)) or (requested_ip and str(b.get('ip', '')).strip() == requested_ip)), 'unknown')
logging.CyberCPLogFileWriter.writeToFile(f'Modified banned IP {ip_addr} (id={banned_ip_id}) in JSON')
final_dic = {'status': 1, 'message': f'Banned IP {ip_addr} has been updated successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
except BaseException as msg:
logging.CyberCPLogFileWriter.writeToFile(f'Error in modifyBannedIP: {str(msg)}')
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
def exportFirewallRules(self, userID=None):
"""
@@ -2282,6 +2533,200 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def exportBannedIPs(self, userID=None):
"""
Export banned IPs to a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadErrorJson('exportStatus', 0)
banned_records = []
# Try database model first
try:
from firewall.models import BannedIP
except Exception as e:
BannedIP = None
logging.CyberCPLogFileWriter.writeToFile(f'Warning: BannedIP model import failed, using JSON fallback: {str(e)}')
if BannedIP is not None:
try:
for banned_ip in BannedIP.objects.all().order_by('-banned_on'):
banned_records.append({
'id': banned_ip.id,
'ip': banned_ip.ip_address,
'reason': banned_ip.reason,
'duration': banned_ip.duration,
'banned_on': banned_ip.get_banned_on_display(),
'expires': banned_ip.get_expires_display(),
'active': banned_ip.active and not banned_ip.is_expired()
})
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error exporting banned IPs from DB: {str(e)}')
# If DB is unavailable/empty, fall back to JSON file
if not banned_records:
banned_ips, _ = self._load_banned_ips_store()
for b in banned_ips:
banned_records.append({
'id': b.get('id'),
'ip': b.get('ip', ''),
'reason': b.get('reason', ''),
'duration': b.get('duration', 'permanent'),
'banned_on': b.get('banned_on', 'N/A'),
'expires': b.get('expires', 'Never'),
'active': b.get('active', True)
})
export_data = {
'version': '1.0',
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_banned_ips': len(banned_records),
'banned_ips': banned_records
}
json_content = json.dumps(export_data, indent=2)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs exported successfully. Total: {len(banned_records)}")
response = HttpResponse(json_content, content_type='application/json')
response['Content-Disposition'] = f'attachment; filename=\"banned_ips_export_{int(time.time())}.json\"'
return response
except BaseException as msg:
final_dic = {'exportStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def importBannedIPs(self, userID=None, data=None):
"""
Import banned IPs from a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] != 1:
return ACLManager.loadErrorJson('importStatus', 0)
request_files = getattr(self.request, 'FILES', None)
if request_files and 'import_file' in request_files:
import_file = self.request.FILES['import_file']
import_data = json.loads(import_file.read().decode('utf-8'))
else:
import_file_path = data.get('import_file_path', '') if data else ''
if not import_file_path or not os.path.exists(import_file_path):
final_dic = {'importStatus': 0, 'error_message': 'Import file not found or invalid path'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
with open(import_file_path, 'r') as f:
import_data = json.load(f)
if 'banned_ips' not in import_data or not isinstance(import_data.get('banned_ips'), list):
final_dic = {'importStatus': 0, 'error_message': 'Invalid import file format. Missing banned_ips array.'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
imported_count = 0
skipped_count = 0
error_count = 0
errors = []
# Try database model first
try:
from firewall.models import BannedIP
except Exception as e:
BannedIP = None
logging.CyberCPLogFileWriter.writeToFile(f'Warning: BannedIP model import failed, using JSON fallback: {str(e)}')
# Prepare JSON fallback store if needed
banned_ips_json = []
if BannedIP is None:
banned_ips_json, _ = self._load_banned_ips_store()
import ipaddress
for item in import_data.get('banned_ips', []):
try:
ip = (item.get('ip') or '').strip()
reason = (item.get('reason') or '').strip()
duration = (item.get('duration') or 'permanent').strip()
active = bool(item.get('active', True))
if not ip or not reason:
skipped_count += 1
continue
try:
ipaddress.ip_address(ip.split('/')[0])
except ValueError:
error_count += 1
errors.append(f"Invalid IP: {ip}")
continue
if BannedIP is not None:
existing = BannedIP.objects.filter(ip_address=ip).first()
if existing:
skipped_count += 1
continue
new_ban = BannedIP(
ip_address=ip,
reason=reason,
duration=duration,
active=active
)
if duration == 'permanent':
new_ban.expires = None
else:
duration_map = {'1h': 3600, '24h': 86400, '7d': 604800, '30d': 2592000}
duration_seconds = duration_map.get(duration, 86400)
new_ban.expires = int(time.time()) + duration_seconds
new_ban.save()
imported_count += 1
else:
# JSON fallback storage
exists = any(str(b.get('ip', '')).strip() == ip for b in banned_ips_json)
if exists:
skipped_count += 1
continue
banned_ips_json.append({
'id': int(time.time() * 1000),
'ip': ip,
'reason': reason,
'duration': duration,
'banned_on': int(time.time()),
'expires': 'Never' if duration == 'permanent' else int(time.time()) + {
'1h': 3600, '24h': 86400, '7d': 604800, '30d': 2592000
}.get(duration, 86400),
'active': active
})
imported_count += 1
except Exception as e:
error_count += 1
errors.append(f"IP '{item.get('ip', 'unknown')}': {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Error importing banned IP {item.get('ip', 'unknown')}: {str(e)}")
if BannedIP is None:
self._save_banned_ips_store(banned_ips_json)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs import completed. Imported: {imported_count}, Skipped: {skipped_count}, Errors: {error_count}")
final_dic = {
'importStatus': 1,
'error_message': "None",
'imported_count': imported_count,
'skipped_count': skipped_count,
'error_count': error_count,
'errors': errors
}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'importStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)

View File

@@ -9,6 +9,10 @@
margin: 0 auto;
padding: 2rem;
}
[ng-cloak], .ng-cloak {
display: none !important;
}
.page-header {
text-align: center;
@@ -219,6 +223,34 @@
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.panel-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.btn-panel {
background: rgba(255, 255, 255, 0.15);
color: var(--text-light, white);
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 0.5rem 0.75rem;
border-radius: 8px;
font-weight: 500;
font-size: 0.8rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.4rem;
transition: all 0.2s ease;
}
.btn-panel:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-1px);
}
.panel-title {
@@ -326,6 +358,12 @@
border: 1px solid var(--border-color, #e8e9ff);
}
.table-responsive {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.rules-table thead {
background: var(--bg-tertiary, #f8f9ff);
}
@@ -356,6 +394,12 @@
background: var(--bg-hover, #f8f9ff);
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.rule-id {
font-weight: 600;
color: var(--text-muted, #64748b);
@@ -791,8 +835,147 @@
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
}
.btn-modify {
background: var(--accent-color, #5b5fcf);
color: var(--bg-secondary, white);
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.8rem;
}
.btn-modify:hover {
background: var(--accent-hover, #4f46e5);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(91, 95, 207, 0.3);
}
.modify-modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
align-items: center;
justify-content: center;
}
.modify-modal-overlay.show {
display: flex;
}
.modify-modal {
background: var(--bg-secondary, white);
border-radius: 12px;
padding: 2rem;
max-width: 480px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modify-modal h3 {
margin: 0 0 1.5rem 0;
font-size: 1.25rem;
color: var(--text-primary, #1e293b);
}
.modify-modal .form-group {
margin-bottom: 1rem;
}
.modify-modal .form-label {
display: block;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--text-secondary, #64748b);
}
.modify-modal .form-control {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color, #e2e8f0);
border-radius: 6px;
}
.modify-modal-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
.modify-modal-actions .btn-cancel {
background: #e2e8f0;
color: #64748b;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
}
.modify-modal-actions .btn-save {
background: var(--accent-color, #5b5fcf);
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
}
/* Responsive Design for Banned IPs */
@media (max-width: 768px) {
.status-content {
flex-direction: column;
align-items: stretch;
}
.control-btn {
width: 100%;
}
.rule-form {
grid-template-columns: 1fr;
gap: 1rem;
}
.rules-table-section {
padding: 1rem;
overflow-x: auto;
}
.rules-table {
min-width: 720px;
}
.banned-search-section input {
max-width: 100% !important;
width: 100%;
}
.panel-header {
flex-direction: column;
align-items: flex-start;
}
.panel-actions {
width: 100%;
}
.btn-panel {
width: 100%;
justify-content: center;
}
.banned-form {
grid-template-columns: 1fr;
gap: 1rem;
@@ -828,7 +1011,7 @@
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<div class="modern-container" ng-controller="firewallController">
<div class="modern-container" ng-controller="firewallController" ng-cloak>
<!-- Page Header -->
<div class="page-header">
<div class="header-content">
@@ -982,7 +1165,8 @@
<!-- Rules Table -->
<div ng-hide="rulesDetails" class="rules-table-section">
<table class="rules-table" ng-if="rules.length > 0">
<div class="table-responsive">
<table class="rules-table" ng-if="rules.length > 0">
<thead>
<tr>
<th>{% trans "ID" %}</th>
@@ -1019,7 +1203,8 @@
</td>
</tr>
</tbody>
</table>
</table>
</div>
<!-- Empty State -->
<div ng-if="rules.length == 0" class="empty-state">
@@ -1057,7 +1242,17 @@
</div>
{% trans "Banned IP Addresses" %}
</div>
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div class="panel-actions">
<button type="button" class="btn-panel" ng-click="exportBannedIPs()">
<i class="fas fa-file-export"></i>
{% trans "Export Banned IPs" %}
</button>
<button type="button" class="btn-panel" ng-click="importBannedIPs()">
<i class="fas fa-file-import"></i>
{% trans "Import Banned IPs" %}
</button>
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
</div>
</div>
<!-- Add Banned IP Section -->
@@ -1101,9 +1296,22 @@
</form>
</div>
<!-- Search Banned IPs -->
<div class="banned-search-section" style="margin: 1rem 2rem 0;">
<label class="form-label" style="display: block; margin-bottom: 0.5rem; font-weight: 600;">
<i class="fas fa-search"></i> {% trans "Search banned IPs" %}
</label>
<input type="text"
class="form-control"
ng-model="bannedIPSearch"
placeholder="{% trans 'Search by IP address, reason or status (Active/Expired)...' %}"
style="max-width: 400px;">
</div>
<!-- Banned IPs List -->
<div class="banned-list-section" ng-init="activeTab === 'banned' && populateBannedIPs()">
<table class="banned-table" ng-if="bannedIPs && bannedIPs.length > 0">
<div class="table-responsive">
<table class="banned-table" ng-if="bannedIPs && bannedIPs.length > 0">
<thead>
<tr>
<th>{% trans "IP Address" %}</th>
@@ -1115,7 +1323,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="bannedIP in bannedIPs">
<tr ng-repeat="bannedIP in bannedIPs | filter:searchBannedIPFilter as filteredBannedList">
<td class="ip-address">
<i class="fas fa-globe"></i>
{$ bannedIP.ip $}
@@ -1141,7 +1349,15 @@
</td>
<td class="actions">
<button type="button"
ng-click="removeBannedIP(bannedIP.id, bannedIP.ip)"
ng-click="$parent.openModifyModal(bannedIP); $event.stopPropagation()"
class="btn-modify"
title="{% trans 'Modify Ban' %}"
ng-if="bannedIP.active">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
</button>
<button type="button"
ng-click="$parent.removeBannedIP(bannedIP.id, bannedIP.ip); $event.stopPropagation()"
class="btn-unban"
title="{% trans 'Unban IP' %}"
ng-if="bannedIP.active">
@@ -1149,7 +1365,7 @@
{% trans "Unban" %}
</button>
<button type="button"
ng-click="deleteBannedIP(bannedIP.id, bannedIP.ip)"
ng-click="$parent.deleteBannedIP(bannedIP.id, bannedIP.ip); $event.stopPropagation()"
class="btn-delete"
title="{% trans 'Delete Record' %}">
<i class="fas fa-trash"></i>
@@ -1157,33 +1373,73 @@
</td>
</tr>
</tbody>
</table>
</table>
</div>
<!-- Empty State -->
<!-- Empty State: no banned IPs at all -->
<div ng-if="!bannedIPs || bannedIPs.length == 0" class="empty-state">
<i class="fas fa-shield-check empty-icon"></i>
<h3 class="empty-title">{% trans "No Banned IPs" %}</h3>
<p class="empty-text">{% trans "All IP addresses are currently allowed. Add banned IPs to block suspicious or malicious traffic." %}</p>
</div>
<!-- Empty State: search returned no matches -->
<div ng-if="bannedIPs && bannedIPs.length > 0 && (bannedIPSearch || '').length > 0 && ((bannedIPs | filter:searchBannedIPFilter).length === 0)" class="empty-state">
<i class="fas fa-search empty-icon"></i>
<h3 class="empty-title">{% trans "No matching banned IPs" %}</h3>
<p class="empty-text">{% trans "No banned IPs match your search. Try a different IP, reason or status (Active/Expired)." %}</p>
</div>
</div>
<!-- Messages -->
<div style="padding: 0 2rem 2rem;">
<div ng-show="bannedIPActionFailed === false" class="alert alert-danger">
<!-- Messages: only one visible at a time; use ng-if so nothing shows until we have a message -->
<div style="padding: 0 2rem 2rem;" ng-if="bannedIPActionFailed === false || bannedIPActionSuccess === false || bannedIPCouldNotConnect === false">
<div ng-if="bannedIPActionFailed === false" class="alert alert-danger">
<i class="fas fa-exclamation-circle alert-icon"></i>
<span>{% trans "Action failed. Error message:" %} {$ bannedIPErrorMessage $}</span>
<span>{% trans "Action failed. Error message:" %} <span ng-bind="bannedIPErrorMessage"></span></span>
</div>
<div ng-show="bannedIPActionSuccess === false" class="alert alert-success">
<div ng-if="bannedIPActionFailed !== false && bannedIPActionSuccess === false" class="alert alert-success">
<i class="fas fa-check-circle alert-icon"></i>
<span>{% trans "Action completed successfully." %}</span>
</div>
<div ng-show="bannedIPCouldNotConnect === false" class="alert alert-danger">
<div ng-if="bannedIPActionFailed !== false && bannedIPCouldNotConnect === false" class="alert alert-danger">
<i class="fas fa-exclamation-circle alert-icon"></i>
<span>{% trans "Could not connect to server. Please refresh this page." %}</span>
</div>
</div>
<!-- Modify Banned IP Modal -->
<div id="modifyBannedIPModal" class="modify-modal-overlay" ng-class="{'show': showModifyModal}" ng-click="closeModifyModal()">
<div class="modify-modal" ng-click="$event.stopPropagation()">
<h3>{% trans "Modify Banned IP" %}</h3>
<p style="margin: 0 0 1rem 0; color: var(--text-secondary, #64748b);">
<strong>{% trans "IP Address" %}:</strong> {$ modifyBannedIPData.ip $}
</p>
<div class="form-group">
<label class="form-label">{% trans "Reason" %}</label>
<input type="text"
class="form-control"
ng-model="modifyBannedIPData.reason"
placeholder="{% trans 'e.g., Suspicious activity' %}">
</div>
<div class="form-group">
<label class="form-label">{% trans "Duration" %}</label>
<select ng-model="modifyBannedIPData.duration" class="form-control">
<option value="1h">{% trans "1 Hour" %}</option>
<option value="24h">{% trans "24 Hours" %}</option>
<option value="7d">{% trans "7 Days" %}</option>
<option value="30d">{% trans "30 Days" %}</option>
<option value="permanent">{% trans "Permanent" %}</option>
</select>
</div>
<div class="modify-modal-actions">
<button type="button" class="btn-cancel" ng-click="closeModifyModal()">
{% trans "Cancel" %}
</button>
<button type="button" class="btn-save" ng-click="saveModifyBannedIP()">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -671,33 +671,110 @@ def addBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.addBannedIP(userID, json.loads(request.body))
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8')
request_data = json.loads(body) if body and body.strip() else {}
except json.JSONDecodeError as e:
final_dic = {'status': 0, 'error_message': 'Invalid JSON in request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
except Exception as e:
final_dic = {'status': 0, 'error_message': 'Error parsing request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
result = fm.addBannedIP(userID, request_data)
return result
except KeyError:
return redirect(loadLoginPage)
final_dic = {'status': 0, 'error_message': 'Session expired. Please refresh the page and try again.'}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403)
except Exception as e:
import traceback
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as _log
error_trace = traceback.format_exc()
_log.writeToFile('Error in addBannedIP view: %s\n%s' % (str(e), error_trace))
final_dic = {'status': 0, 'error_message': 'Server error: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=500)
def modifyBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.modifyBannedIP(userID, json.loads(request.body))
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8')
request_data = json.loads(body) if body and body.strip() else {}
except json.JSONDecodeError as e:
final_dic = {'status': 0, 'error_message': 'Invalid JSON in request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
except Exception as e:
final_dic = {'status': 0, 'error_message': 'Error parsing request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
return fm.modifyBannedIP(userID, request_data)
except KeyError:
return redirect(loadLoginPage)
final_dic = {'status': 0, 'error_message': 'Session expired. Please refresh the page and try again.'}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403)
except Exception as e:
import traceback
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as _log
error_trace = traceback.format_exc()
_log.writeToFile('Error in modifyBannedIP view: %s\n%s' % (str(e), error_trace))
final_dic = {'status': 0, 'error_message': 'Server error: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=500)
def removeBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.removeBannedIP(userID, json.loads(request.body))
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8')
request_data = json.loads(body) if body and body.strip() else {}
except json.JSONDecodeError as e:
final_dic = {'status': 0, 'error_message': 'Invalid JSON in request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
except Exception as e:
final_dic = {'status': 0, 'error_message': 'Error parsing request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
return fm.removeBannedIP(userID, request_data)
except KeyError:
return redirect(loadLoginPage)
final_dic = {'status': 0, 'error_message': 'Session expired. Please refresh the page and try again.'}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403)
except Exception as e:
import traceback
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as _log
error_trace = traceback.format_exc()
_log.writeToFile('Error in removeBannedIP view: %s\n%s' % (str(e), error_trace))
final_dic = {'status': 0, 'error_message': 'Server error: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=500)
def deleteBannedIP(request):
try:
userID = request.session['userID']
fm = FirewallManager()
return fm.deleteBannedIP(userID, json.loads(request.body))
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8')
request_data = json.loads(body) if body and body.strip() else {}
except json.JSONDecodeError as e:
final_dic = {'status': 0, 'error_message': 'Invalid JSON in request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
except Exception as e:
final_dic = {'status': 0, 'error_message': 'Error parsing request: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=400)
return fm.deleteBannedIP(userID, request_data)
except KeyError:
return redirect(loadLoginPage)
final_dic = {'status': 0, 'error_message': 'Session expired. Please refresh the page and try again.'}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403)
except Exception as e:
import traceback
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as _log
error_trace = traceback.format_exc()
_log.writeToFile('Error in deleteBannedIP view: %s\n%s' % (str(e), error_trace))
final_dic = {'status': 0, 'error_message': 'Server error: %s' % str(e)}
return HttpResponse(json.dumps(final_dic), content_type='application/json', status=500)
def exportFirewallRules(request):

View File

@@ -12,23 +12,41 @@ from plogical.acl import ACLManager
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
from plogical.sslReconcile import SSLReconcile
from plogical.sslUtilities import sslUtilities
from loginSystem.models import Administrator
import json
def sslReconcile(request):
"""SSL Reconciliation interface"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
userID = request.session['userID']
admin = Administrator.objects.get(pk=userID)
currentACL = ACLManager.loadedACL(userID)
# Principal admin (userName == 'admin') always allowed
if getattr(admin, 'userName', None) == 'admin':
return render(request, 'manageSSL/sslReconcile.html', {
'acls': currentACL,
'admin': admin
})
# Allow if has sslReconcile, or full admin, or manageSSL permission
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
return ACLManager.loadErrorJson('sslReconcile', 0)
if currentACL.get('admin') != 1 and currentACL.get('manageSSL') != 1:
return ACLManager.loadErrorJson('sslReconcile', 0)
return render(request, 'manageSSL/sslReconcile.html', {
'acls': currentACL,
'admin': admin
})
except KeyError:
data_ret = {
'status': 0,
'errorMessage': 'Session expired or not logged in. Please log in again.',
'error_message': 'Session expired or not logged in. Please log in again.',
'sslReconcile': 0
}
return HttpResponse(json.dumps(data_ret))
except BaseException as msg:
logging.writeToFile(str(msg) + " [sslReconcile]")
return ACLManager.loadErrorJson('sslReconcile', 0)
@@ -37,11 +55,14 @@ def sslReconcile(request):
def reconcileAllSSL(request):
"""Reconcile SSL for all domains"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
userID = request.session['userID']
admin = Administrator.objects.get(pk=userID)
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
return ACLManager.loadErrorJson('sslReconcile', 0)
if getattr(admin, 'userName', None) != 'admin':
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
if currentACL.get('admin') != 1 and currentACL.get('manageSSL') != 1:
return ACLManager.loadErrorJson('sslReconcile', 0)
# Run SSL reconciliation
success = SSLReconcile.reconcile_all()
@@ -54,6 +75,9 @@ def reconcileAllSSL(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except KeyError:
data_ret = {'reconcileStatus': 0, 'error_message': 'Session expired or not logged in. Please log in again.'}
return HttpResponse(json.dumps(data_ret))
except BaseException as msg:
logging.writeToFile(str(msg) + " [reconcileAllSSL]")
data_ret = {'reconcileStatus': 0, 'error_message': str(msg)}
@@ -64,11 +88,14 @@ def reconcileAllSSL(request):
def reconcileDomainSSL(request):
"""Reconcile SSL for a specific domain"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
userID = request.session['userID']
admin = Administrator.objects.get(pk=userID)
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
return ACLManager.loadErrorJson('sslReconcile', 0)
if getattr(admin, 'userName', None) != 'admin':
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
if currentACL.get('admin') != 1 and currentACL.get('manageSSL') != 1:
return ACLManager.loadErrorJson('sslReconcile', 0)
domain = request.POST.get('domain')
if not domain:
@@ -87,6 +114,9 @@ def reconcileDomainSSL(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except KeyError:
data_ret = {'reconcileStatus': 0, 'error_message': 'Session expired or not logged in. Please log in again.'}
return HttpResponse(json.dumps(data_ret))
except BaseException as msg:
logging.writeToFile(str(msg) + " [reconcileDomainSSL]")
data_ret = {'reconcileStatus': 0, 'error_message': str(msg)}
@@ -97,11 +127,14 @@ def reconcileDomainSSL(request):
def fixACMEContexts(request):
"""Fix ACME challenge contexts for all domains"""
try:
currentACL = ACLManager.loadedACL(request.user.pk)
admin = ACLManager.loadedAdmin(request.user.pk)
userID = request.session['userID']
admin = Administrator.objects.get(pk=userID)
currentACL = ACLManager.loadedACL(userID)
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
return ACLManager.loadErrorJson('sslReconcile', 0)
if getattr(admin, 'userName', None) != 'admin':
if ACLManager.currentContextPermission(currentACL, 'sslReconcile') == 0:
if currentACL.get('admin') != 1 and currentACL.get('manageSSL') != 1:
return ACLManager.loadErrorJson('sslReconcile', 0)
from websiteFunctions.models import Websites
@@ -128,6 +161,9 @@ def fixACMEContexts(request):
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
except KeyError:
data_ret = {'reconcileStatus': 0, 'error_message': 'Session expired or not logged in. Please log in again.'}
return HttpResponse(json.dumps(data_ret))
except BaseException as msg:
logging.writeToFile(str(msg) + " [fixACMEContexts]")
data_ret = {'reconcileStatus': 0, 'error_message': str(msg)}

View File

@@ -32,7 +32,7 @@ class ACLManager:
'"createEmail": 1, "listEmails": 1, "deleteEmail": 1, "emailForwarding": 1, "changeEmailPassword": 1, ' \
'"dkimManager": 1, "createFTPAccount": 1, "deleteFTPAccount": 1, "listFTPAccounts": 1, "createBackup": 1,' \
' "restoreBackup": 1, "addDeleteDestinations": 1, "scheduleBackups": 1, "remoteBackups": 1, "googleDriveBackups": 1, "manageSSL": 1, ' \
'"hostnameSSL": 1, "mailServerSSL": 1 }'
'"hostnameSSL": 1, "mailServerSSL": 1, "sslReconcile": 1 }'
ResellerACL = '{"adminStatus":0, "versionManagement": 1, "createNewUser": 1, "listUsers": 1, "deleteUser": 1 , "resellerCenter": 1, ' \
'"changeUserACL": 0, "createWebsite": 1, "modifyWebsite": 1, "suspendWebsite": 1, "deleteWebsite": 1, ' \
@@ -246,6 +246,7 @@ class ACLManager:
finalResponse['manageSSL'] = config['manageSSL']
finalResponse['hostnameSSL'] = config['hostnameSSL']
finalResponse['mailServerSSL'] = config['mailServerSSL']
finalResponse['sslReconcile'] = config.get('sslReconcile', 0)
return finalResponse

View File

@@ -6,8 +6,7 @@
/* Java script code to create account */
app.controller('createFTPAccount', function ($scope, $http) {
// Initialize all ng-hide variables to hide alerts on page load
$scope.ftpLoading = false;
$scope.ftpDetails = true;
$scope.canNotCreateFTP = true;
@@ -16,8 +15,10 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.generatedPasswordView = true;
$(document).ready(function () {
$(".ftpDetails").hide();
$(".ftpPasswordView").hide();
$( ".ftpDetails" ).hide();
$( ".ftpPasswordView" ).hide();
// Only use select2 if it's actually a function (avoids errors when Rocket Loader defers scripts)
if (typeof $ !== 'undefined' && $ && typeof $.fn !== 'undefined' && typeof $.fn.select2 === 'function') {
try {
var $sel = $('.create-ftp-acct-select');
@@ -26,7 +27,6 @@ app.controller('createFTPAccount', function ($scope, $http) {
$sel.on('select2:select', function (e) {
var data = e.params.data;
$scope.ftpDomain = data.text;
$scope.ftpDetails = false;
$scope.$apply();
$(".ftpDetails").show();
});
@@ -42,15 +42,13 @@ app.controller('createFTPAccount', function ($scope, $http) {
function initNativeSelect() {
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
$scope.ftpDomain = $(this).val();
$scope.ftpDetails = !($scope.ftpDomain && $scope.ftpDomain !== "");
$scope.$apply();
if ($scope.ftpDomain && $scope.ftpDomain !== "") $(".ftpDetails").show();
else $(".ftpDetails").hide();
$(".ftpDetails").show();
});
}
});
$scope.showFTPDetails = function () {
$scope.showFTPDetails = function() {
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
$(".ftpDetails").show();
$scope.ftpDetails = false;
@@ -62,7 +60,7 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.createFTPAccount = function () {
$scope.ftpLoading = true;
$scope.ftpLoading = true; // Show loading while creating
$scope.ftpDetails = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
@@ -73,18 +71,59 @@ app.controller('createFTPAccount', function ($scope, $http) {
var ftpPassword = $scope.ftpPassword;
var path = $scope.ftpPath;
if (typeof path === 'undefined' || path == null) path = "";
else path = String(path).trim();
// Enhanced path validation
if (typeof path === 'undefined' || path === null) {
path = "";
} else {
path = path.trim();
}
// Client-side path validation
if (path && path !== "") {
// Check for dangerous characters
var dangerousChars = /[;&|$`'"<>*?~]/;
if (dangerousChars.test(path)) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path contains dangerous characters";
return;
}
// Check for path traversal attempts
if (path.indexOf("..") !== -1 || path.indexOf("~") !== -1) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path cannot contain '..' or '~'";
return;
}
// Check if path starts with slash (should be relative)
if (path.startsWith("/")) {
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = "Invalid path: Path must be relative (not starting with '/')";
return;
}
}
var url = "/ftp/submitFTPCreation";
var data = {
ftpDomain: ftpDomain,
ftpUserName: ftpUserName,
passwordByPass: ftpPassword,
path: path,
path: path || '',
api: '0',
enableCustomQuota: $scope.enableCustomQuota || false,
customQuotaSize: $scope.customQuotaSize || 0,
};
var url = "/ftp/submitFTPCreation";
var config = {
headers: {
@@ -96,14 +135,12 @@ app.controller('createFTPAccount', function ($scope, $http) {
function ListInitialDatas(response) {
if (response.data.creatFTPStatus === 1) {
$scope.ftpLoading = false;
if (response.data && response.data.creatFTPStatus === 1) {
$scope.ftpLoading = false; // Hide loading on success
$scope.successfullyCreatedFTP = false;
$scope.canNotCreateFTP = true;
$scope.couldNotConnect = true;
$scope.createdFTPUsername = ftpDomain + "_" + ftpUserName;
$scope.createdFTPUsername = (response.data.createdFTPUsername != null && response.data.createdFTPUsername !== '') ? response.data.createdFTPUsername : (ftpDomain + '_' + ftpUserName);
if (typeof PNotify !== 'undefined') {
new PNotify({ title: 'Success!', text: 'FTP account successfully created.', type: 'success' });
}
@@ -112,20 +149,22 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = response.data.error_message;
$scope.errorMessage = (response.data && response.data.error_message) ? response.data.error_message : 'Unknown error';
if (typeof PNotify !== 'undefined') {
new PNotify({ title: 'Operation Failed!', text: response.data.error_message, type: 'error' });
new PNotify({ title: 'Operation Failed!', text: $scope.errorMessage, type: 'error' });
}
}
}
function cantLoadInitialDatas(response) {
$scope.ftpLoading = false;
$scope.couldNotConnect = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
if (typeof PNotify !== 'undefined') {
new PNotify({ title: 'Operation Failed!', text: 'Could not connect to server, please refresh this page', type: 'error' });
if ($scope.successfullyCreatedFTP !== false) {
$scope.couldNotConnect = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
if (typeof PNotify !== 'undefined') {
new PNotify({ title: 'Operation Failed!', text: 'Could not connect to server, please refresh this page', type: 'error' });
}
}
}
@@ -151,8 +190,11 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.generatedPasswordView = true;
};
$scope.toggleCustomQuota = function () {
if (!$scope.enableCustomQuota) $scope.customQuotaSize = 0;
// Quota management functions
$scope.toggleCustomQuota = function() {
if (!$scope.enableCustomQuota) {
$scope.customQuotaSize = 0;
}
};
});
@@ -214,32 +256,24 @@ app.controller('deleteFTPAccount', function ($scope, $http) {
} else {
$scope.ftpAccountsOfDomain = true;
$scope.deleteFTPButton = true;
$scope.deleteFailure = true;
$scope.deleteFailure = false;
$scope.deleteSuccess = true;
$scope.couldNotConnect = false;
$scope.couldNotConnect = true;
$scope.deleteFTPButtonInit = true;
$scope.errorMessage = (response.data && (response.data.error_message || response.data.errorMessage)) || 'Unknown error';
}
}
function cantLoadInitialDatas(response) {
$scope.ftpAccountsOfDomain = true;
$scope.deleteFTPButton = true;
$scope.deleteFailure = true;
$scope.deleteSuccess = true;
$scope.couldNotConnect = false;
$scope.deleteFTPButtonInit = true;
}
};
$scope.deleteFTPAccount = function () {
@@ -330,9 +364,10 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = true;
$scope.ftpLoading = false;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
$scope.quotaManagementBox = true;
$scope.notificationsBox = true;
var globalFTPUsername = "";
@@ -346,7 +381,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Don't show loading when opening password dialog
$scope.changePasswordBox = false;
$scope.notificationsBox = true;
$scope.ftpUsername = ftpUsername;
@@ -356,7 +391,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.changePasswordBtn = function () {
$scope.ftpLoading = false;
$scope.ftpLoading = true; // Show loading while changing password
url = "/ftp/changePassword";
@@ -382,13 +417,13 @@ app.controller('listFTPAccounts', function ($scope, $http) {
if (response.data.changePasswordStatus == 1) {
$scope.notificationsBox = false;
$scope.passwordChanged = false;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading when done
$scope.domainFeteched = $scope.selectedDomain;
} else {
$scope.notificationsBox = false;
$scope.canNotChangePassword = false;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading on error
$scope.canNotChangePassword = false;
$scope.errorMessage = response.data.error_message;
}
@@ -398,7 +433,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.notificationsBox = false;
$scope.couldNotConnect = false;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading on connection error
}
@@ -409,7 +444,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false;
$scope.ftpLoading = true; // Show loading while fetching
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -444,7 +479,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading when done
$scope.ftpAccounts = false;
$scope.changePasswordBox = true;
@@ -456,7 +491,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading on error
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -471,7 +506,7 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = false;
$scope.ftpLoading = true;
$scope.ftpLoading = false; // Hide loading on connection error
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -493,4 +528,216 @@ app.controller('listFTPAccounts', function ($scope, $http) {
$scope.generatedPasswordView = true;
};
// Quota management functions
$scope.manageQuota = function (record) {
$scope.recordsFetched = true;
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false;
$scope.quotaManagementBox = false;
$scope.notificationsBox = true;
$scope.ftpUsername = record.user;
globalFTPUsername = record.user;
// Set current quota info
$scope.currentQuotaInfo = record.quotasize;
$scope.packageQuota = record.package_quota;
$scope.enableCustomQuotaEdit = record.custom_quota_enabled;
$scope.customQuotaSizeEdit = record.custom_quota_size || 0;
};
$scope.toggleCustomQuotaEdit = function() {
if (!$scope.enableCustomQuotaEdit) {
$scope.customQuotaSizeEdit = 0;
}
};
$scope.updateQuotaBtn = function () {
$scope.ftpLoading = true;
url = "/ftp/updateFTPQuota";
var data = {
ftpUserName: globalFTPUsername,
customQuotaSize: parseInt($scope.customQuotaSizeEdit) || 0,
enableCustomQuota: $scope.enableCustomQuotaEdit || false,
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
if (response.data.updateQuotaStatus == 1) {
$scope.notificationsBox = false;
$scope.quotaUpdated = false;
$scope.ftpLoading = false;
$scope.domainFeteched = $scope.selectedDomain;
// Refresh the records to show updated quota
populateCurrentRecords();
// Show success notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'FTP quota updated successfully.',
type: 'success'
});
}
} else {
$scope.notificationsBox = false;
$scope.quotaUpdateFailed = false;
$scope.ftpLoading = false;
$scope.errorMessage = response.data.error_message;
// Show error notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
});
}
}
}
function cantLoadInitialDatas(response) {
$scope.notificationsBox = false;
$scope.couldNotConnect = false;
$scope.ftpLoading = false;
// Show error notification
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error!',
text: 'Could not connect to server.',
type: 'error'
});
}
}
};
});
app.controller('Resetftpconf', function ($scope, $http, $timeout, $window){
$scope.Loading = true;
$scope.NotifyBox = true;
$scope.InstallBox = true;
$scope.installationDetailsForm = false;
$scope.alertType = '';
$scope.errorMessage = '';
$scope.resetftp = function () {
$scope.Loading = false;
$scope.installationDetailsForm = true;
$scope.InstallBox = false;
$scope.alertType = '';
$scope.NotifyBox = true;
var url = "/ftp/resetftpnow";
var data = {};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
if (response.data && response.data.status === 1) {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.alertType = '';
$scope.statusfile = response.data.tempStatusPath;
$timeout(getRequestStatus, 1000);
} else {
$scope.errorMessage = (response.data && (response.data.error_message || response.data.errorMessage)) || 'Unknown error';
$scope.alertType = 'failedToStart';
$scope.NotifyBox = false;
$scope.InstallBox = true;
$scope.Loading = false;
}
}
function cantLoadInitialData(response) {
$scope.errorMessage = (response && response.data && (response.data.error_message || response.data.errorMessage)) || 'Could not connect to server. Please refresh this page.';
$scope.alertType = 'couldNotConnect';
$scope.NotifyBox = false;
$scope.InstallBox = true;
$scope.Loading = false;
try {
new PNotify({ title: 'Error!', text: $scope.errorMessage, type: 'error' });
} catch (e) {}
}
}
var statusPollPromise = null;
function getRequestStatus() {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.alertType = '';
var url = "/ftp/getresetstatus";
var data = { statusfile: $scope.statusfile };
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
if (!response.data) return;
if (response.data.abort === 0) {
$scope.alertType = '';
$scope.requestData = response.data.requestStatus || '';
statusPollPromise = $timeout(getRequestStatus, 1000);
} else {
if (statusPollPromise) {
$timeout.cancel(statusPollPromise);
statusPollPromise = null;
}
$scope.NotifyBox = false;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.requestData = response.data.requestStatus || '';
if (response.data.installed === 0) {
$scope.alertType = 'resetFailed';
$scope.errorMessage = response.data.error_message || 'Reset failed';
} else {
$scope.alertType = 'success';
$timeout(function () { $window.location.reload(); }, 3000);
}
}
}
function cantLoadInitialDatas(response) {
if (statusPollPromise) {
$timeout.cancel(statusPollPromise);
statusPollPromise = null;
}
$scope.alertType = 'couldNotConnect';
$scope.errorMessage = (response && response.data && (response.data.error_message || response.data.errorMessage)) || 'Could not connect to server. Please refresh this page.';
$scope.NotifyBox = false;
$scope.InstallBox = true;
$scope.Loading = false;
}
}
});

View File

@@ -10743,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 () {
@@ -10820,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();
@@ -10848,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();
@@ -10865,8 +10830,7 @@ app.controller('modifyWebsitesController', function ($scope, $http) {
packForWeb: packForWeb,
email: email,
phpVersion: phpVersion,
admin: admin,
homeDirectory: homeDirectory
admin: admin
};
var config = {
@@ -13338,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) {
@@ -13595,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