mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-02-04 21:59:05 +01:00
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:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user