From 4c24de7453a1ae5a2c84ecd4dc781a3e99ae3830 Mon Sep 17 00:00:00 2001 From: master3395 Date: Tue, 3 Feb 2026 19:50:17 +0100 Subject: [PATCH] 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). --- CyberCP/wsgi.py | 6 + baseTemplate/context_processors.py | 14 + .../templates/baseTemplate/homePage.html | 46 ++ .../templates/baseTemplate/index.html | 6 +- baseTemplate/views.py | 65 +- firewall/firewallManager.py | 657 +++++++++++++++--- firewall/templates/firewall/firewall.html | 292 +++++++- firewall/views.py | 93 ++- manageSSL/views.py | 66 +- plogical/acl.py | 3 +- static/ftp/ftp.js | 349 ++++++++-- static/websiteFunctions/websiteFunctions.js | 106 +-- 12 files changed, 1387 insertions(+), 316 deletions(-) diff --git a/CyberCP/wsgi.py b/CyberCP/wsgi.py index c9cc8c835..03015f999 100644 --- a/CyberCP/wsgi.py +++ b/CyberCP/wsgi.py @@ -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 diff --git a/baseTemplate/context_processors.py b/baseTemplate/context_processors.py index 696f9d840..b3122a0fe 100644 --- a/baseTemplate/context_processors.py +++ b/baseTemplate/context_processors.py @@ -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 } \ No newline at end of file diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index 2a1aef84f..2373f10ae 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -876,6 +876,29 @@ + +
+
+ Show + + per page +
+
+ {$ (sshLoginsPage - 1) * sshLoginsPerPage + 1 $}-{$ (sshLoginsPage * sshLoginsPerPage > sshLoginsTotal ? sshLoginsTotal : sshLoginsPage * sshLoginsPerPage) $} of {$ sshLoginsTotal $} + + +
+
+ @@ -1065,6 +1088,29 @@
+ + +
+
+ Show + + per page +
+
+ {$ (sshLogsPage - 1) * sshLogsPerPage + 1 $}-{$ (sshLogsPage * sshLogsPerPage > sshLogsTotal ? sshLogsTotal : sshLogsPage * sshLogsPerPage) $} of {$ sshLogsTotal $} + + +
+
diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index f00f3828f..ab433a6e5 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -1835,9 +1835,7 @@ Manage SSL - - SSL Reconciliation - + {% comment %}SSL Reconciliation - hidden; URL /manageSSL/sslReconcile still works if needed{% endcomment %} {% endif %} {% if admin or hostnameSSL %} @@ -2190,7 +2188,7 @@ - + diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 172e86731..1c1c637c2 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -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 diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py index f42db8d14..097e5c0f3 100644 --- a/firewall/firewallManager.py +++ b/firewall/firewallManager.py @@ -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) + diff --git a/firewall/templates/firewall/firewall.html b/firewall/templates/firewall/firewall.html index 21b1bab63..6a500128c 100644 --- a/firewall/templates/firewall/firewall.html +++ b/firewall/templates/firewall/firewall.html @@ -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 %} -
+
@@ -1101,9 +1296,22 @@
+ +
+ + +
+
- +
+
@@ -1115,7 +1323,7 @@ - + -
{% trans "IP Address" %}
{$ bannedIP.ip $} @@ -1141,7 +1349,15 @@ +
+ +
- +

{% trans "No Banned IPs" %}

{% trans "All IP addresses are currently allowed. Add banned IPs to block suspicious or malicious traffic." %}

+ +
+ +

{% trans "No matching banned IPs" %}

+

{% trans "No banned IPs match your search. Try a different IP, reason or status (Active/Expired)." %}

+
- -
-
+ +
+
- {% trans "Action failed. Error message:" %} {$ bannedIPErrorMessage $} + {% trans "Action failed. Error message:" %}
- -
+
{% trans "Action completed successfully." %}
- -
+
{% trans "Could not connect to server. Please refresh this page." %}
+ + +
+
+

{% trans "Modify Banned IP" %}

+

+ {% trans "IP Address" %}: {$ modifyBannedIPData.ip $} +

+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/firewall/views.py b/firewall/views.py index 595d7edcb..5e42bcf96 100644 --- a/firewall/views.py +++ b/firewall/views.py @@ -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): diff --git a/manageSSL/views.py b/manageSSL/views.py index e67db2610..2b74fe81c 100644 --- a/manageSSL/views.py +++ b/manageSSL/views.py @@ -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)} diff --git a/plogical/acl.py b/plogical/acl.py index 512ecf3bd..57338174a 100644 --- a/plogical/acl.py +++ b/plogical/acl.py @@ -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 diff --git a/static/ftp/ftp.js b/static/ftp/ftp.js index e63a71d1a..6a7cb75da 100644 --- a/static/ftp/ftp.js +++ b/static/ftp/ftp.js @@ -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; + } + } +}); \ No newline at end of file diff --git a/static/websiteFunctions/websiteFunctions.js b/static/websiteFunctions/websiteFunctions.js index f6c5f9dcb..dd86b2455 100644 --- a/static/websiteFunctions/websiteFunctions.js +++ b/static/websiteFunctions/websiteFunctions.js @@ -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