Merge pull request #1665 from master3395/v2.5.5-dev

V2.5.5 dev
This commit is contained in:
Master3395
2026-01-30 20:35:27 +01:00
committed by GitHub
27 changed files with 1709 additions and 4502 deletions

View File

@@ -1271,6 +1271,99 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
};
// Ban IP from SSH Logs
$scope.banIPFromSSHLog = function(ipAddress) {
if (!ipAddress) {
new PNotify({
title: 'Error',
text: 'No IP address provided',
type: 'error',
delay: 5000
});
return;
}
if ($scope.blockingIP === ipAddress) {
return; // Already processing
}
if ($scope.blockedIPs[ipAddress]) {
new PNotify({
title: 'Info',
text: `IP address ${ipAddress} is already banned`,
type: 'info',
delay: 3000
});
return;
}
$scope.blockingIP = ipAddress;
// Use the Banned IPs system
var data = {
ip: ipAddress,
reason: 'Suspicious activity detected from SSH logs',
duration: 'permanent'
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post('/firewall/addBannedIP', data, config).then(function (response) {
$scope.blockingIP = null;
if (response.data && response.data.status === 1) {
// Mark IP as blocked
$scope.blockedIPs[ipAddress] = true;
// Show success notification
new PNotify({
title: 'IP Address Banned',
text: `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`,
type: 'success',
delay: 5000
});
// Refresh SSH logs to update the UI
$scope.refreshSSHLogs();
} else {
// Show error notification
var errorMsg = 'Failed to ban IP address';
if (response.data && response.data.error_message) {
errorMsg = response.data.error_message;
} else if (response.data && response.data.error) {
errorMsg = response.data.error;
}
new PNotify({
title: 'Error',
text: errorMsg,
type: 'error',
delay: 5000
});
}
}, function (err) {
$scope.blockingIP = null;
var errorMessage = 'Failed to ban IP address';
if (err.data && err.data.error_message) {
errorMessage = err.data.error_message;
} else if (err.data && err.data.error) {
errorMessage = err.data.error;
} else if (err.data && err.data.message) {
errorMessage = err.data.message;
}
new PNotify({
title: 'Error',
text: errorMessage,
type: 'error',
delay: 5000
});
});
};
// Ban IP from SSH Logs
$scope.banIPFromSSHLog = function(ipAddress) {
if (!ipAddress) {

View File

@@ -132,6 +132,16 @@
cursor: pointer;
}
a.insight-link {
text-decoration: none;
color: inherit;
display: block;
}
a.insight-link:hover {
color: inherit;
}
.insight-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(91,95,207,0.1);
@@ -690,38 +700,38 @@
<!-- Overview Section -->
<div class="overview-section">
<h2 class="section-title">OVERVIEW</h2>
<div class="stats-grid" ng-controller="systemStatusInfo" ng-cloak>
<div class="stats-grid" ng-controller="systemStatusInfo">
<div class="stat-card">
<div class="stat-card-header">
<span class="stat-card-title">CPU USAGE</span>
<span class="stat-card-value" ng-bind="cpuUsage || 0">0</span><span>%</span>
<span class="stat-card-value">({$ cpuUsage $}%)</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" ng-style="{'width': (cpuUsage || 0) + '%'}"></div>
<div class="progress-bar" style="width: {$ cpuUsage $}%;"></div>
</div>
<div class="stat-card-info">You've <span ng-bind="cpuCores || 0">0</span> Core(s) CPU.</div>
<div class="stat-card-info">You've {$ cpuCores $} Core(s) CPU.</div>
</div>
<div class="stat-card">
<div class="stat-card-header">
<span class="stat-card-title">MEMORY USAGE</span>
<span class="stat-card-value" ng-bind="ramUsage || 0">0</span><span>%</span>
<span class="stat-card-value">({$ ramUsage $}%)</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" ng-style="{'width': (ramUsage || 0) + '%'}"></div>
<div class="progress-bar" style="width: {$ ramUsage $}%;"></div>
</div>
<div class="stat-card-info">You've <span ng-bind="ramTotalMB || 0">0</span> MB Memory.</div>
<div class="stat-card-info">You've {$ ramTotalMB $} MB Memory.</div>
</div>
<div class="stat-card">
<div class="stat-card-header">
<span class="stat-card-title">DISK USAGE</span>
<span class="stat-card-value" ng-bind="diskUsage || 0">0</span><span>%</span>
<span class="stat-card-value">({$ diskUsage $}%)</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" ng-style="{'width': (diskUsage || 0) + '%'}"></div>
<div class="progress-bar" style="width: {$ diskUsage $}%;"></div>
</div>
<div class="stat-card-info">You've <span ng-bind="diskFreeGB || 0">0</span> GB remaining out of <span ng-bind="diskTotalGB || 0">0</span> GB.</div>
<div class="stat-card-info">You've {$ diskFreeGB $} GB remaining out of {$ diskTotalGB $} GB.</div>
</div>
</div>
</div>
@@ -729,54 +739,54 @@
<!-- Insights Section -->
<div class="insights-section">
<h2 class="section-title">INSIGHTS</h2>
<div class="insights-grid" ng-cloak>
<div class="insight-card">
<div class="insights-grid">
<a href="{% url 'listUsers' %}" class="insight-card insight-link">
<div class="insight-icon">
<i class="fas fa-users"></i>
</div>
<div class="insight-value" ng-bind="totalUsers || 0">0</div>
<div class="insight-value">{$ totalUsers || 0 $}</div>
<div class="insight-label">Users</div>
</div>
</a>
<div class="insight-card">
<a href="{% url 'listWebsites' %}" class="insight-card insight-link">
<div class="insight-icon">
<i class="fas fa-globe"></i>
</div>
<div class="insight-value" ng-bind="totalSites || 0">0</div>
<div class="insight-value">{$ totalSites || 0 $}</div>
<div class="insight-label">Websites</div>
</div>
</a>
<div class="insight-card">
<a href="{% url 'ListWPSites' %}" class="insight-card insight-link">
<div class="insight-icon" style="background: #5b87da;">
<i class="fab fa-wordpress"></i>
</div>
<div class="insight-value" ng-bind="totalWPSites || 0">0</div>
<div class="insight-value">{$ totalWPSites || 0 $}</div>
<div class="insight-label">WordPress</div>
</div>
</a>
<div class="insight-card">
<a href="{% url 'listDBs' %}" class="insight-card insight-link">
<div class="insight-icon">
<i class="fas fa-database"></i>
</div>
<div class="insight-value" ng-bind="totalDBs || 0">0</div>
<div class="insight-value">{$ totalDBs || 0 $}</div>
<div class="insight-label">Databases</div>
</div>
</a>
<div class="insight-card">
<a href="{% url 'listEmails' %}" class="insight-card insight-link">
<div class="insight-icon">
<i class="fas fa-envelope"></i>
</div>
<div class="insight-value" ng-bind="totalEmails || 0">0</div>
<div class="insight-value">{$ totalEmails || 0 $}</div>
<div class="insight-label">Emails</div>
</div>
</a>
<div class="insight-card">
<a href="{% url 'listFTPAccounts' %}" class="insight-card insight-link">
<div class="insight-icon">
<i class="fas fa-folder"></i>
</div>
<div class="insight-value" ng-bind="totalFTPUsers || 0">0</div>
<div class="insight-value">{$ totalFTPUsers || 0 $}</div>
<div class="insight-label">FTP Users</div>
</div>
</a>
</div>
</div>
@@ -972,17 +982,16 @@
<strong style="font-size: 12px; color: #1e293b;">Recommendation:</strong>
<p style="margin: 4px 0 0 0; font-size: 12px; color: #475569; white-space: pre-line;">{$ alert.recommendation $}</p>
</div>
<!-- Add to Firewall Button for Brute Force Attacks and Root Login Attempts -->
<div ng-if="(alert.title === 'Brute Force Attack Detected' && alert.details && alert.details['IP Address']) || (alert.title === 'Root Login Attempts Detected' && alert.details && alert.details['Top IP'])" style="margin-top: 12px;">
<button ng-click="blockIPAddress(alert.details['IP Address'] || alert.details['Top IP']); $event.stopPropagation(); return false;"
<!-- Add to Firewall Button for all alerts with IP addresses -->
<div ng-if="alert.details && (alert.details['IP Address'] || alert.details['Top IP'])" style="margin-top: 12px;">
<button ng-click="blockIPAddress(alert.details['IP Address'] || alert.details['Top IP']); $event.stopPropagation()"
ng-disabled="blockingIP === (alert.details['IP Address'] || alert.details['Top IP'])"
style="background: #dc2626; color: white; border: none; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;"
onmouseover="this.style.background='#b91c1c'"
onmouseout="this.style.background='#dc2626'"
type="button">
onmouseout="this.style.background='#dc2626'">
<i class="fas fa-ban" ng-if="blockingIP !== (alert.details['IP Address'] || alert.details['Top IP'])"></i>
<i class="fas fa-spinner fa-spin" ng-if="blockingIP === (alert.details['IP Address'] || alert.details['Top IP'])"></i>
<span ng-if="blockingIP !== (alert.details['IP Address'] || alert.details['Top IP'])">Ban IP</span>
<span ng-if="blockingIP !== (alert.details['IP Address'] || alert.details['Top IP'])">Ban IP Permanently</span>
<span ng-if="blockingIP === (alert.details['IP Address'] || alert.details['Top IP'])">Banning...</span>
</button>
<a href="/firewall/" target="_blank" style="margin-left: 10px; color: #5b5fcf; font-size: 12px; text-decoration: none;">
@@ -1324,31 +1333,67 @@
});
}
const formData = {
'csrfmiddlewaretoken': getCookie('csrftoken'),
'ip_address': ipAddress,
'reason': 'Brute force attack detected from dashboard'
};
$.post('/base/blockIPAddress', formData, function(data) {
if (data.status === 1) {
showNotification('success', data.message);
// Refresh the page to update the blocked IPs list
setTimeout(() => {
location.reload();
}, 1000);
} else {
showNotification('error', data.message);
}
}).fail(function() {
showNotification('error', 'Failed to block IP address. Please try again.');
}).always(function() {
// Clear loading state
if (typeof angular !== 'undefined' && angular.element(document.body).scope()) {
var scope = angular.element(document.body).scope();
scope.$apply(function() {
scope.blockingIP = null;
});
$.ajax({
url: '/base/blockIPAddress',
type: 'POST',
contentType: 'application/json',
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
data: JSON.stringify({
'ip_address': ipAddress,
'reason': 'Security alert detected from dashboard'
}),
success: function(data) {
// Handle both success and error responses
if (data.status === 1) {
showNotification('success', data.message || 'IP address blocked successfully');
// Refresh the page to update the blocked IPs list
setTimeout(() => {
location.reload();
}, 1000);
} else {
// Handle error response - check for both 'error' and 'error_message' fields
var errorMsg = data.error || data.error_message || data.message || 'Failed to block IP address';
showNotification('error', errorMsg);
}
},
error: function(xhr, status, error) {
// Handle network errors and parse JSON errors
console.error('Ban IP error:', xhr, status, error);
var errorMsg = 'Failed to block IP address. Please try again.';
// Log full response for debugging
console.log('Response status:', xhr.status);
console.log('Response text:', xhr.responseText);
if (xhr.responseJSON) {
errorMsg = xhr.responseJSON.error || xhr.responseJSON.error_message || xhr.responseJSON.message || errorMsg;
console.log('Parsed error from JSON:', errorMsg);
} else if (xhr.responseText) {
try {
var errorData = JSON.parse(xhr.responseText);
errorMsg = errorData.error || errorData.error_message || errorData.message || errorMsg;
console.log('Parsed error from text:', errorMsg);
} catch(e) {
console.error('Failed to parse error response:', e);
// If parsing fails, try to extract error from response text
if (xhr.responseText.includes('error')) {
errorMsg = xhr.responseText.substring(0, 200);
}
}
}
showNotification('error', errorMsg);
},
complete: function() {
// Clear loading state
if (typeof angular !== 'undefined' && angular.element(document.body).scope()) {
var scope = angular.element(document.body).scope();
scope.$apply(function() {
scope.blockingIP = null;
});
}
}
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -830,6 +830,7 @@ def getRecentSSHLogs(request):
if not currentACL.get('admin', 0):
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
from plogical.processUtilities import ProcessUtilities
import re
distro = ProcessUtilities.decideDistro()
if distro in [ProcessUtilities.ubuntu, ProcessUtilities.ubuntu20]:
log_path = '/var/log/auth.log'
@@ -841,6 +842,9 @@ def getRecentSSHLogs(request):
return HttpResponse(json.dumps({'error': f'Failed to read log: {str(e)}'}), content_type='application/json', status=500)
lines = output.split('\n')
logs = []
# IP address regex patterns (IPv4)
ipv4_pattern = r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b'
for line in lines:
if not line.strip():
continue
@@ -851,7 +855,26 @@ def getRecentSSHLogs(request):
else:
timestamp = ''
message = line
logs.append({'timestamp': timestamp, 'message': message, 'raw': line})
# Extract IP address from the log line
ip_address = None
ip_matches = re.findall(ipv4_pattern, line)
if ip_matches:
# Filter out localhost and common non-external IPs
for ip in ip_matches:
if ip not in ['127.0.0.1', '0.0.0.0', '::1'] and not ip.startswith('192.168.') and not ip.startswith('10.') and not ip.startswith('172.'):
ip_address = ip
break
# If no external IP found, use the first match anyway (might be needed for internal attacks)
if not ip_address and ip_matches:
ip_address = ip_matches[0]
logs.append({
'timestamp': timestamp,
'message': message,
'raw': line,
'ip_address': ip_address
})
return HttpResponse(json.dumps({'logs': logs}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@@ -1193,7 +1216,30 @@ def blockIPAddress(request):
'error': 'Premium feature required'
}), content_type='application/json', status=403)
data = json.loads(request.body)
# Parse request body - Django request.body is always bytes
try:
if not request.body:
return HttpResponse(json.dumps({
'status': 0,
'error': 'Request body is empty'
}), content_type='application/json', status=400)
body_str = request.body.decode('utf-8')
if not body_str or body_str.strip() == '':
return HttpResponse(json.dumps({
'status': 0,
'error': 'Request body is empty'
}), content_type='application/json', status=400)
data = json.loads(body_str)
except (json.JSONDecodeError, UnicodeDecodeError) as e:
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'JSON decode error in blockIPAddress: {str(e)}, body: {request.body[:200] if request.body else "empty"}')
return HttpResponse(json.dumps({
'status': 0,
'error': f'Invalid request format: {str(e)}'
}), content_type='application/json', status=400)
ip_address = data.get('ip_address', '').strip()
if not ip_address:
@@ -1288,6 +1334,54 @@ def blockIPAddress(request):
error_message = f'Firewall command failed: {str(e)}'
if success:
# Add to banned IPs JSON file for consistency with firewall page
try:
import os
import time
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 = []
# Check if IP is already banned
ip_already_banned = False
for banned_ip in banned_ips:
if banned_ip.get('ip') == ip_address and banned_ip.get('active', True):
ip_already_banned = True
break
if not ip_already_banned:
# Get reason from request data
reason = data.get('reason', 'Security alert detected from dashboard')
# Add new banned IP
new_banned_ip = {
'id': int(time.time()),
'ip': ip_address,
'reason': reason,
'duration': 'permanent',
'banned_on': time.time(),
'expires': 'Never',
'active': True
}
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)
except Exception as e:
# Log but don't fail the request if JSON update fails
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to update banned_ips.json: {str(e)}')
# Log the action
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'IP address {ip_address} blocked via CyberPanel dashboard by user {user_id}')
@@ -1303,7 +1397,18 @@ def blockIPAddress(request):
'error': error_message or 'Failed to block IP address'
}), content_type='application/json', status=500)
except json.JSONDecodeError as e:
import plogical.CyberCPLogFileWriter as logging
logging.CyberCPLogFileWriter.writeToFile(f'JSON decode error in blockIPAddress: {str(e)}, body: {request.body}')
return HttpResponse(json.dumps({
'status': 0,
'error': f'Invalid JSON in request: {str(e)}'
}), content_type='application/json', status=400)
except Exception as e:
import plogical.CyberCPLogFileWriter as logging
import traceback
error_trace = traceback.format_exc()
logging.CyberCPLogFileWriter.writeToFile(f'Error in blockIPAddress: {str(e)}\n{error_trace}')
return HttpResponse(json.dumps({
'status': 0,
'error': f'Server error: {str(e)}'

View File

@@ -11,7 +11,6 @@ sys.path.append('/usr/local/CyberCP')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
django.setup()
import json
import tempfile
from plogical.acl import ACLManager
import plogical.CyberCPLogFileWriter as logging
from plogical.virtualHostUtilities import virtualHostUtilities
@@ -140,107 +139,6 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyRule(self, userID=None, data=None):
"""
Modify an existing firewall rule
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('modify_status', 0)
ruleID = data.get('id')
newRuleName = data.get('ruleName', '').strip()
newRuleProtocol = data.get('ruleProtocol', '').strip()
newRulePort = data.get('rulePort', '').strip()
newRuleIP = data.get('ruleIP', '').strip()
# Validate inputs
if not newRuleName:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule name is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleProtocol or newRuleProtocol not in ['tcp', 'udp']:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Valid protocol (tcp/udp) is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRulePort:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Port is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
if not newRuleIP:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Get existing rule
try:
existingRule = FirewallRules.objects.get(id=ruleID)
except FirewallRules.DoesNotExist:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'Rule not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Check if anything changed
changed = False
if (existingRule.name != newRuleName or
existingRule.proto != newRuleProtocol or
existingRule.port != newRulePort or
existingRule.ipAddress != newRuleIP):
changed = True
if changed:
# Check if new rule already exists (different ID)
existingDuplicate = FirewallRules.objects.filter(
name=newRuleName,
proto=newRuleProtocol,
port=newRulePort,
ipAddress=newRuleIP
).exclude(id=ruleID).first()
if existingDuplicate:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': 'A rule with these settings already exists'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Delete old firewall rule
try:
FirewallUtilities.deleteRule(existingRule.proto, existingRule.port, existingRule.ipAddress)
logging.CyberCPLogFileWriter.writeToFile(f'Removed old firewall rule: {existingRule.proto}/{existingRule.port}/{existingRule.ipAddress}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old firewall rule: {str(e)}')
# Add new firewall rule
try:
FirewallUtilities.addRule(newRuleProtocol, newRulePort, newRuleIP)
logging.CyberCPLogFileWriter.writeToFile(f'Added new firewall rule: {newRuleProtocol}/{newRulePort}/{newRuleIP}')
except Exception as e:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': f'Failed to add firewall rule: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update database record
existingRule.name = newRuleName
existingRule.proto = newRuleProtocol
existingRule.port = newRulePort
existingRule.ipAddress = newRuleIP
existingRule.save()
final_dic = {'status': 1, 'modify_status': 1, 'error_message': "None"}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'modify_status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteRule(self, userID = None, data = None):
try:
@@ -1912,97 +1810,49 @@ class FirewallManager:
def getBannedIPs(self, userID=None):
"""
Get list of banned IP addresses
Get list of banned IP addresses from database
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
# For now, we'll use a simple file-based storage
# In production, you might want to use a database
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
# Import BannedIP model and Django Q
from firewall.models import BannedIP
from django.db.models import Q
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 = []
# 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')
# Filter out expired bans and format data consistently
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
# Get original values
expires_val = banned_ip.get('expires')
banned_on_val = banned_ip.get('banned_on', current_time)
# Convert banned_on to timestamp if it's a string
if isinstance(banned_on_val, str):
try:
# Try parsing formatted date string
banned_on_val = time.mktime(time.strptime(banned_on_val, '%Y-%m-%d %H:%M:%S'))
except:
banned_on_val = current_time
elif not isinstance(banned_on_val, (int, float)):
banned_on_val = current_time
# Check if expired
is_expired = False
if expires_val == 'Never' or expires_val is None:
expires_timestamp = None
expires_display = 'Never'
elif isinstance(expires_val, str):
if expires_val == 'Never':
expires_timestamp = None
expires_display = 'Never'
else:
try:
expires_timestamp = time.mktime(time.strptime(expires_val, '%Y-%m-%d %H:%M:%S'))
expires_display = expires_val
except:
expires_timestamp = None
expires_display = 'Never'
else:
expires_timestamp = expires_val
if expires_val > current_time:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
else:
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_val))
is_expired = True
# Determine if active
if expires_timestamp is None:
is_active = True
else:
is_active = expires_timestamp > current_time and not is_expired
# Format banned_on for display
banned_on_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_on_val))
# Create formatted entry
formatted_ip = {
'id': banned_ip.get('id'),
'ip': banned_ip.get('ip'),
'reason': banned_ip.get('reason', ''),
'duration': banned_ip.get('duration', 'never'),
'banned_on': banned_on_display, # String for display
'banned_on_timestamp': banned_on_val * 1000, # Milliseconds for AngularJS date filter
'expires': expires_display, # String for display
'expires_timestamp': expires_timestamp * 1000 if expires_timestamp else None, # Milliseconds for AngularJS
'active': is_active
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
}
active_banned_ips.append(formatted_ip)
# Only include truly active bans
if ip_data['active']:
active_banned_ips.append(ip_data)
final_dic = {'status': 1, 'bannedIPs': active_banned_ips}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
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)}')
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
@@ -2014,7 +1864,7 @@ class FirewallManager:
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadErrorJson('status', 0)
return ACLManager.loadError()
ip = data.get('ip', '').strip()
reason = data.get('reason', '').strip()
@@ -2077,50 +1927,64 @@ class FirewallManager:
}
banned_ips.append(new_banned_ip)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
# Apply firewall rule to block the IP using ProcessUtilities (handles sudo)
# Save to file
with open(banned_ips_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Apply firewall rule to block the IP using firewalld
try:
# Check if rule already exists to avoid duplicate rules
check_command = f"iptables -C INPUT -s {ip} -j DROP 2>&1"
check_result = ProcessUtilities.executioner(check_command, None, True)
import subprocess
# Verify firewalld is active
firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'],
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)
# If rule doesn't exist (check returns non-zero), add it
if check_result != 0:
# Add iptables rule to block the IP using ProcessUtilities (handles sudo)
command = f"iptables -A INPUT -s {ip} -j DROP"
result = ProcessUtilities.executioner(command, None, True)
if result == 0:
logging.CyberCPLogFileWriter.writeToFile(f'Successfully added iptables rule to ban IP {ip} with reason: {reason}')
# Add firewalld rich rule to block the IP
rich_rule = f'rule family=ipv4 source address={ip} drop'
add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule]
# Execute the add rule command
result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Reload firewall rules
reload_cmd = ['firewall-cmd', '--reload']
reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30)
if reload_result.returncode == 0:
logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to add iptables rule for {ip} (exit code: {result}), but IP was added to banned list')
# Don't fail the entire operation - IP is still in banned list
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)
else:
logging.CyberCPLogFileWriter.writeToFile(f'IPTables rule already exists for {ip}, skipping')
# 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')
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)
except subprocess.TimeoutExpired:
logging.CyberCPLogFileWriter.writeToFile(f'Timeout adding firewalld rule for {ip}')
final_dic = {'status': 0, 'error_message': 'Firewall command timed out'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Error adding iptables rule for {ip}: {str(e)}, but IP was added to banned list')
# Don't fail the entire operation - IP is still in banned list
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)
final_dic = {'status': 1, 'message': f'IP address {ip} has been banned successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json, content_type='application/json')
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
@@ -2162,37 +2026,44 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule
# Remove firewalld rule to unblock the IP
try:
# Remove iptables rule to unblock the IP
if '/' in ip_to_unban:
# CIDR notation
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_unban, '-j', 'DROP'], check=False)
import subprocess
# Verify firewalld is active
firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'],
capture_output=True, text=True, timeout=10)
if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout):
# Firewalld not active, but still mark as unbanned in JSON
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Firewalld not active when unbanned IP {ip_to_unban}')
else:
# Single IP
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_unban, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}')
except subprocess.CalledProcessError as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove iptables rule for {ip_to_unban}: {str(e)}')
# Remove firewalld rich rule
rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop'
remove_rule_cmd = ['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule]
# Execute the remove rule command
result = subprocess.run(remove_rule_cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Reload firewall rules
reload_cmd = ['firewall-cmd', '--reload']
reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30)
if reload_result.returncode == 0:
logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to reload firewalld after unbanning {ip_to_unban}: {reload_result.stderr}')
else:
# Rule might not exist, which is okay
if 'NOT_ENABLED' in result.stderr or 'not found' in result.stderr.lower():
logging.CyberCPLogFileWriter.writeToFile(f'IP {ip_to_unban} rule not found in firewalld (may have been removed already)')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to remove firewalld rule for {ip_to_unban}: {result.stderr}')
except subprocess.TimeoutExpired:
logging.CyberCPLogFileWriter.writeToFile(f'Timeout removing firewalld rule for {ip_to_unban}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove firewalld rule for {ip_to_unban}: {str(e)}')
final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'}
final_json = json.dumps(final_dic)
@@ -2203,153 +2074,6 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def modifyBannedIP(self, userID=None, data=None):
"""
Modify a banned IP address (update reason and/or expiration)
"""
try:
admin = Administrator.objects.get(pk=userID)
if admin.acl.adminStatus != 1:
return ACLManager.loadError()
banned_ip_id = data.get('id')
new_ip = data.get('ip', '').strip()
reason = data.get('reason', '').strip()
duration = data.get('duration', 'never')
if not new_ip:
final_dic = {'status': 0, 'error_message': 'IP address is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Validate IP address format
import ipaddress
try:
ipaddress.ip_address(new_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)
if not reason:
final_dic = {'status': 0, 'error_message': 'Reason is required'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# 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 = []
# Find and update the banned IP
old_ip = None
found = False
for banned_ip in banned_ips:
if banned_ip.get('id') == banned_ip_id:
found = True
old_ip = banned_ip['ip']
# Check if new IP is already banned (and not the same record)
ip_changed = (new_ip != old_ip)
if ip_changed:
for other_banned_ip in banned_ips:
if other_banned_ip.get('id') != banned_ip_id and other_banned_ip.get('ip') == new_ip and other_banned_ip.get('active', True):
final_dic = {'status': 0, 'error_message': f'IP address {new_ip} is already banned'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Update IP address if changed
if ip_changed:
# Remove old iptables rule
try:
if '/' in old_ip:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
else:
subprocess.run(['iptables', '-D', 'INPUT', '-s', old_ip, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Removed iptables rule for old IP {old_ip}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Warning: Could not remove old iptables rule for {old_ip}: {str(e)}')
# Add new iptables rule
try:
if '/' in new_ip:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', new_ip, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for new IP {new_ip}')
except subprocess.CalledProcessError as e:
final_dic = {'status': 0, 'error_message': f'Failed to add firewall rule for IP {new_ip}: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
banned_ip['ip'] = new_ip
# Update reason
banned_ip['reason'] = reason
# Update expiration if duration changed
if duration == 'never' or duration == 'permanent':
banned_ip['expires'] = 'Never'
banned_ip['duration'] = 'never'
else:
# Calculate new expiration time
current_time = time.time()
duration_map = {
'1h': 3600,
'6h': 21600,
'12h': 43200,
'24h': 86400,
'48h': 172800,
'7d': 604800,
'30d': 2592000
}
duration_seconds = duration_map.get(duration, 86400)
banned_ip['expires'] = current_time + duration_seconds
banned_ip['duration'] = duration
break
if not found:
final_dic = {'status': 0, 'error_message': 'Banned IP record not found'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
ip_display = new_ip if new_ip != old_ip else old_ip
change_msg = f'IP changed from {old_ip} to {new_ip}' if new_ip != old_ip else f'IP unchanged ({old_ip})'
logging.CyberCPLogFileWriter.writeToFile(f'Modified banned IP record: {change_msg}, reason={reason}, duration={duration}')
final_dic = {'status': 1, 'message': f'Banned IP has been modified successfully'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def deleteBannedIP(self, userID=None, data=None):
"""
Permanently delete a banned IP record
@@ -2385,36 +2109,9 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
# Write to temp file in /tmp (web server user has write permissions here)
temp_dir = tempfile.gettempdir()
temp_file = os.path.join(temp_dir, f'banned_ips_{int(time.time())}.json')
with open(temp_file, 'w') as f:
# Save updated banned IPs
with open(banned_ips_file, 'w') as f:
json.dump(updated_banned_ips, f, indent=2)
# Ensure /etc/cyberpanel directory exists with proper permissions
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command, None, True)
# Move temp file to final location and set permissions using ProcessUtilities
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command, None, True)
# Remove iptables rule to unban the IP
try:
# Remove iptables rule to unblock the IP
if '/' in ip_to_delete:
# CIDR notation
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
else:
# Single IP
subprocess.run(['iptables', '-D', 'INPUT', '-s', ip_to_delete, '-j', 'DROP'], check=False)
logging.CyberCPLogFileWriter.writeToFile(f'Deleted and unbanned IP {ip_to_delete}')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove iptables rule for {ip_to_delete}: {str(e)}')
logging.CyberCPLogFileWriter.writeToFile(f'Deleted banned IP record for {ip_to_delete}')
@@ -2585,253 +2282,6 @@ class FirewallManager:
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
def exportBannedIPs(self, userID=None):
"""
Export all banned IPs to a JSON file
"""
try:
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
return ACLManager.loadErrorJson('exportStatus', 0)
# Load banned IPs from file
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 Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error reading banned IPs file: {str(e)}")
banned_ips = []
# Filter out expired bans for export (optional - you might want to include all)
current_time = time.time()
active_banned_ips = []
for banned_ip in banned_ips:
expires_val = banned_ip.get('expires')
expires_timestamp = banned_ip.get('expires_timestamp')
# Include if never expires or not yet expired
if expires_val == 'Never' or expires_timestamp is None:
active_banned_ips.append(banned_ip)
elif isinstance(expires_timestamp, (int, float)) and expires_timestamp > current_time:
active_banned_ips.append(banned_ip)
# Create export data with metadata
export_data = {
'version': '1.0',
'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_banned_ips': len(active_banned_ips),
'banned_ips': active_banned_ips
}
# Create JSON response with file download
json_content = json.dumps(export_data, indent=2)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs exported successfully. Total IPs: {len(active_banned_ips)}")
# Return file as download
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:
pass
else:
return ACLManager.loadErrorJson('importStatus', 0)
# Handle file upload
if hasattr(self.request, 'FILES') and 'import_file' in self.request.FILES:
import_file = self.request.FILES['import_file']
# Read file content
import_data = json.loads(import_file.read().decode('utf-8'))
else:
# Fallback to file path method
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)
# Read and parse the import file
with open(import_file_path, 'r') as f:
import_data = json.load(f)
# Validate the import data structure
if 'banned_ips' not in import_data:
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 = []
# Load existing banned IPs
banned_ips_file = '/etc/cyberpanel/banned_ips.json'
existing_banned_ips = []
if os.path.exists(banned_ips_file):
try:
with open(banned_ips_file, 'r') as f:
existing_banned_ips = json.load(f)
except:
existing_banned_ips = []
# Create a set of existing IPs for quick lookup
existing_ips = {banned_ip.get('ip') for banned_ip in existing_banned_ips}
# Import IP address validation
import ipaddress
for banned_ip_data in import_data['banned_ips']:
try:
ip_address = banned_ip_data.get('ip', '').strip()
reason = banned_ip_data.get('reason', '').strip()
# Validate IP address
if not ip_address:
error_count += 1
errors.append(f"Invalid entry: Missing IP address")
continue
try:
ipaddress.ip_address(ip_address.split('/')[0]) # Handle CIDR notation
except ValueError:
error_count += 1
errors.append(f"IP '{ip_address}': Invalid IP address format")
continue
# Check if IP already exists
if ip_address in existing_ips:
skipped_count += 1
continue
# Validate reason
if not reason:
reason = 'Imported from backup'
# Get duration or calculate from expires
duration = banned_ip_data.get('duration', 'never')
expires = banned_ip_data.get('expires', 'Never')
expires_timestamp = banned_ip_data.get('expires_timestamp')
# Calculate expiration if needed
if expires == 'Never' or expires_timestamp is None:
expires_timestamp = None
expires_display = 'Never'
duration = 'never'
elif expires_timestamp and isinstance(expires_timestamp, (int, float)):
expires_display = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_timestamp))
else:
expires_display = expires if isinstance(expires, str) else 'Never'
expires_timestamp = None
# Create banned IP entry
banned_on = banned_ip_data.get('banned_on', time.time())
if isinstance(banned_on, str):
try:
banned_on = time.mktime(time.strptime(banned_on, '%Y-%m-%d %H:%M:%S'))
except:
banned_on = time.time()
new_banned_ip = {
'id': banned_ip_data.get('id', randint(100000, 999999)),
'ip': ip_address,
'reason': reason,
'banned_on': banned_on,
'banned_on_timestamp': banned_on if isinstance(banned_on, (int, float)) else time.time(),
'expires': expires_display,
'expires_timestamp': expires_timestamp,
'duration': duration,
'active': True
}
# Add iptables rule
try:
if '/' in ip_address:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
else:
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip_address, '-j', 'DROP'], check=True)
logging.CyberCPLogFileWriter.writeToFile(f'Added iptables rule for imported IP {ip_address}')
except subprocess.CalledProcessError as e:
error_count += 1
errors.append(f"IP '{ip_address}': Failed to add firewall rule - {str(e)}")
continue
# Add to existing list
existing_banned_ips.append(new_banned_ip)
existing_ips.add(ip_address)
imported_count += 1
except Exception as e:
error_count += 1
errors.append(f"IP '{banned_ip_data.get('ip', 'Unknown')}': {str(e)}")
logging.CyberCPLogFileWriter.writeToFile(f"Error importing banned IP {banned_ip_data.get('ip', 'Unknown')}: {str(e)}")
# Save updated banned IPs
if imported_count > 0 or error_count > 0:
try:
# Create temp file
temp_file = f'/tmp/banned_ips_{randint(100000, 999999)}.json'
with open(temp_file, 'w') as f:
json.dump(existing_banned_ips, f, indent=2)
# Ensure directory exists
command = f'mkdir -p {os.path.dirname(banned_ips_file)} && chmod 755 {os.path.dirname(banned_ips_file)}'
ProcessUtilities.executioner(command)
# Move temp file to final location
command = f'mv {temp_file} {banned_ips_file}'
ProcessUtilities.executioner(command)
# Set permissions
command = f'chmod 644 {banned_ips_file} && chown root:root {banned_ips_file}'
ProcessUtilities.executioner(command)
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f"Error saving imported banned IPs: {str(e)}")
final_dic = {'importStatus': 0, 'error_message': f'Failed to save imported IPs: {str(e)}'}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
logging.CyberCPLogFileWriter.writeToFile(f"Banned IPs import completed. Imported: {imported_count}, Skipped: {skipped_count}, Errors: {error_count}")
final_dic = {
'importStatus': 1,
'error_message': "None",
'imported_count': imported_count,
'skipped_count': skipped_count,
'error_count': error_count,
'errors': errors
}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
except BaseException as msg:
final_dic = {'importStatus': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)

View File

@@ -1,4 +1,8 @@
from django.db import models
from django.utils import timezone
from django.utils.timezone import now
import time
# Create your models here.
@@ -7,3 +11,57 @@ class FirewallRules(models.Model):
proto = models.CharField(max_length=10)
port = models.CharField(max_length=25)
ipAddress = models.CharField(max_length=30,default="0.0.0.0/0")
class BannedIP(models.Model):
"""
Model to store banned IP addresses
"""
ip_address = models.GenericIPAddressField(unique=True, db_index=True, verbose_name="IP Address")
reason = models.CharField(max_length=255, verbose_name="Ban Reason")
duration = models.CharField(max_length=50, default='permanent', verbose_name="Duration")
banned_on = models.DateTimeField(auto_now_add=True, verbose_name="Banned On")
expires = models.BigIntegerField(null=True, blank=True, verbose_name="Expires Timestamp")
# expires can be: null (never expires), or Unix timestamp
active = models.BooleanField(default=True, db_index=True, verbose_name="Active")
class Meta:
db_table = 'firewall_bannedips'
verbose_name = "Banned IP"
verbose_name_plural = "Banned IPs"
indexes = [
models.Index(fields=['ip_address', 'active']),
models.Index(fields=['active', 'expires']),
]
def __str__(self):
return f"{self.ip_address} - {self.reason}"
def is_expired(self):
"""
Check if the ban has expired
Returns True if expired, False if still active
"""
if self.expires is None:
# Never expires
return False
current_time = int(time.time())
return self.expires <= current_time
def get_expires_display(self):
"""
Get human-readable expiration date
"""
if self.expires is None:
return "Never"
return timezone.datetime.fromtimestamp(self.expires).strftime('%Y-%m-%d %H:%M:%S')
def get_banned_on_display(self):
"""
Get human-readable banned on date
"""
if self.banned_on:
if hasattr(self.banned_on, 'strftime'):
return self.banned_on.strftime('%Y-%m-%d %H:%M:%S')
return str(self.banned_on)
return timezone.now().strftime('%Y-%m-%d %H:%M:%S')

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
overflow: visible; /* Allow modal to show above container */
}
.page-header {
@@ -612,63 +611,7 @@
border-radius: 16px;
box-shadow: 0 4px 12px var(--shadow-medium, rgba(0,0,0,0.15));
border: 1px solid var(--border-color, #e8e9ff);
overflow: visible; /* Changed from hidden to allow modal to show */
position: relative;
z-index: 1;
}
/* Modal Styles - Ensure it's above everything */
#modifyBannedIPModal, #modifyRuleModal {
z-index: 99999 !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
}
#modifyBannedIPModal.show, #modifyRuleModal.show {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
align-items: center !important;
justify-content: center !important;
}
#modifyBannedIPModal.fade.show, #modifyRuleModal.fade.show {
opacity: 1 !important;
}
#modifyBannedIPModal .modal-dialog, #modifyRuleModal .modal-dialog {
z-index: 100000 !important;
position: relative !important;
margin: 1.75rem auto !important;
max-width: 500px !important;
width: 90% !important;
}
.modal-backdrop {
z-index: 99998 !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background-color: rgba(0, 0, 0, 0.5) !important;
}
/* Ensure modal is always on top */
body.modal-open #modifyBannedIPModal {
display: flex !important;
}
/* Make sure modal is not hidden by parent containers */
.modern-container #modifyBannedIPModal,
.banned-ips-panel #modifyBannedIPModal,
.modern-container #modifyRuleModal,
.rules-panel #modifyRuleModal {
position: fixed !important;
z-index: 99999 !important;
overflow: hidden;
}
.add-banned-section {
@@ -827,114 +770,18 @@
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.btn-modify {
background: #2196f3 !important;
color: var(--bg-secondary, white) !important;
border: none !important;
padding: 0.75rem 1.5rem !important;
border-radius: 6px !important;
font-weight: 600 !important;
cursor: pointer !important;
transition: all 0.3s ease;
display: flex !important;
align-items: center !important;
gap: 0.5rem !important;
font-size: 0.95rem !important;
visibility: visible !important;
opacity: 1 !important;
min-width: 100px !important;
min-height: 40px !important;
position: relative !important;
z-index: 10 !important;
}
.btn-modify:hover {
background: #1976d2;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3);
}
.export-import-buttons {
display: flex;
gap: 0.75rem;
align-items: center;
}
.btn-export, .btn-import {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: 6px;
font-weight: 500;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
}
.btn-export {
background: #10b981;
color: white;
}
.btn-export:hover:not(:disabled) {
background: #059669;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.btn-import {
background: #3b82f6;
color: white;
}
.btn-import:hover:not(:disabled) {
background: #2563eb;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
}
.btn-export:disabled, .btn-import:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Ensure actions column and buttons are always visible */
.banned-table td.actions {
display: flex !important;
visibility: visible !important;
}
.banned-table td.actions {
display: flex !important;
gap: 0.5rem !important;
align-items: center !important;
}
.banned-table td.actions .btn-modify {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
width: auto !important;
height: auto !important;
}
.btn-delete {
background: var(--danger-color, #ef4444);
color: var(--bg-secondary, white);
border: none;
padding: 0.75rem 1.5rem !important;
padding: 0.5rem;
border-radius: 6px;
font-weight: 600 !important;
cursor: pointer;
transition: all 0.3s ease;
display: flex !important;
width: 32px;
height: 32px;
display: flex;
align-items: center;
gap: 0.5rem !important;
font-size: 0.95rem !important;
min-width: 100px !important;
min-height: 40px !important;
justify-content: center;
}
@@ -1048,7 +895,7 @@
{% trans "Firewall Rules" %}
</button>
<button type="button"
ng-click="activeTab = 'banned'"
ng-click="activeTab = 'banned'; populateBannedIPs();"
ng-class="{'tab-active': activeTab === 'banned'}"
class="tab-button">
<i class="fas fa-ban"></i>
@@ -1162,14 +1009,7 @@
<td>
<span class="port-number">{$ rule.port $}</span>
</td>
<td class="actions" style="display: flex !important; gap: 0.5rem !important; align-items: center !important;">
<button type="button"
ng-click="handleModifyRuleClick(rule, $event)"
class="btn-modify"
title="{% trans 'Modify Rule' %}">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
</button>
<td>
<button type="button"
ng-click="deleteRule(rule.id, rule.proto, rule.port, rule.ipAddress)"
class="btn-delete"
@@ -1217,25 +1057,7 @@
</div>
{% trans "Banned IP Addresses" %}
</div>
<div style="display: flex; align-items: center; gap: 1rem;">
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
<div class="export-import-buttons">
<button type="button"
ng-click="exportBannedIPs()"
class="btn-export"
ng-disabled="bannedIPsLoading">
<i class="fas fa-download"></i>
{% trans "Export Banned IPs" %}
</button>
<button type="button"
ng-click="importBannedIPs()"
class="btn-import"
ng-disabled="bannedIPsLoading">
<i class="fas fa-upload"></i>
{% trans "Import Banned IPs" %}
</button>
</div>
</div>
<div ng-show="bannedIPsLoading" class="loading-spinner"></div>
</div>
<!-- Add Banned IP Section -->
@@ -1280,8 +1102,8 @@
</div>
<!-- Banned IPs List -->
<div class="banned-list-section">
<table class="banned-table" ng-if="bannedIPs.length > 0">
<div class="banned-list-section" ng-init="activeTab === 'banned' && populateBannedIPs()">
<table class="banned-table" ng-if="bannedIPs && bannedIPs.length > 0">
<thead>
<tr>
<th>{% trans "IP Address" %}</th>
@@ -1293,7 +1115,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="bannedIP in bannedIPs" style="position: relative; z-index: 1;">
<tr ng-repeat="bannedIP in bannedIPs">
<td class="ip-address">
<i class="fas fa-globe"></i>
{$ bannedIP.ip $}
@@ -1303,13 +1125,12 @@
</td>
<td class="banned-date ng-binding">
<i class="fas fa-calendar"></i>
<span ng-if="bannedIP.banned_on_timestamp">{$ bannedIP.banned_on_timestamp | date:'MMM dd, yyyy HH:mm' $}</span>
<span ng-if="!bannedIP.banned_on_timestamp">{$ bannedIP.banned_on $}</span>
{$ bannedIP.banned_on $}
</td>
<td class="expires-date">
<i class="fas fa-clock"></i>
<span ng-if="bannedIP.expires === 'Never' || !bannedIP.expires_timestamp">{% trans "Never" %}</span>
<span ng-if="bannedIP.expires !== 'Never' && bannedIP.expires_timestamp">{$ bannedIP.expires_timestamp | date:'MMM dd, yyyy HH:mm' $}</span>
<span ng-if="bannedIP.expires === 'Never'">{% trans "Never" %}</span>
<span ng-if="bannedIP.expires !== 'Never'">{$ bannedIP.expires $}</span>
</td>
<td class="status">
<span ng-class="{'status-active': bannedIP.active, 'status-expired': !bannedIP.active}"
@@ -1318,24 +1139,20 @@
{$ bannedIP.active ? 'Active' : 'Expired' $}
</span>
</td>
<td class="actions" style="display: flex !important; gap: 0.5rem !important; align-items: center !important; position: relative !important; z-index: 100 !important;">
<td class="actions">
<button type="button"
ng-click="handleModifyButtonClick(bannedIP, $event)"
class="btn-modify"
style="display: flex !important; visibility: visible !important; opacity: 1 !important; cursor: pointer !important; pointer-events: auto !important; z-index: 1000 !important; position: relative !important; background: #2196f3 !important; color: white !important; padding: 0.75rem 1.5rem !important; border-radius: 6px !important; border: none !important; font-weight: 600 !important; min-width: 100px !important; min-height: 40px !important;"
data-ip="{$ bannedIP.ip $}"
data-id="{$ bannedIP.id $}"
title="{% trans 'Modify Ban' %}">
<i class="fas fa-edit"></i>
{% trans "Modify" %}
ng-click="removeBannedIP(bannedIP.id, bannedIP.ip)"
class="btn-unban"
title="{% trans 'Unban IP' %}"
ng-if="bannedIP.active">
<i class="fas fa-unlock"></i>
{% trans "Unban" %}
</button>
<button type="button"
ng-click="deleteBannedIP(bannedIP.id, bannedIP.ip)"
class="btn-delete"
style="padding: 0.75rem 1.5rem !important; font-size: 0.95rem !important; min-width: 100px !important; min-height: 40px !important; font-weight: 600 !important;"
title="{% trans 'Delete Record and Unban IP' %}">
title="{% trans 'Delete Record' %}">
<i class="fas fa-trash"></i>
{% trans "Delete" %}
</button>
</td>
</tr>
@@ -1343,7 +1160,7 @@
</table>
<!-- Empty State -->
<div ng-if="bannedIPs.length == 0" class="empty-state">
<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>
@@ -1352,157 +1169,22 @@
<!-- Messages -->
<div style="padding: 0 2rem 2rem;">
<div ng-hide="bannedIPActionFailed" class="alert alert-danger">
<div ng-show="bannedIPActionFailed === false" class="alert alert-danger">
<i class="fas fa-exclamation-circle alert-icon"></i>
<span>{% trans "Action failed. Error message:" %} {$ bannedIPErrorMessage $}</span>
</div>
<div ng-hide="bannedIPActionSuccess" class="alert alert-success">
<div ng-show="bannedIPActionSuccess === false" class="alert alert-success">
<i class="fas fa-check-circle alert-icon"></i>
<span>{% trans "Action completed successfully." %}</span>
</div>
<div ng-hide="bannedIPCouldNotConnect" class="alert alert-danger">
<div ng-show="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>
</div>
<!-- Modify Firewall Rule Modal -->
<div class="modal fade" id="modifyRuleModal" tabindex="-1" role="dialog" aria-labelledby="modifyRuleModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modifyRuleModalLabel">
<i class="fas fa-edit"></i> {% trans "Modify Firewall Rule" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="closeModifyRuleModal()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="modifyRuleForm">
<input type="hidden" id="modifyRuleId" name="rule_id">
<div class="form-group">
<label for="modifyRuleName">
<i class="fas fa-tag"></i> {% trans "Rule Name" %}
</label>
<input type="text" class="form-control" id="modifyRuleName" name="rule_name" placeholder="{% trans 'e.g., Allow SSH' %}">
<small class="form-text text-muted">{% trans "Enter a descriptive name for this rule" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleProtocol">
<i class="fas fa-network-wired"></i> {% trans "Protocol" %}
</label>
<select class="form-control" id="modifyRuleProtocol" name="protocol">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
<small class="form-text text-muted">{% trans "Select the network protocol" %}</small>
</div>
<div class="form-group">
<label for="modifyRuleIP">
<i class="fas fa-globe"></i> {% trans "IP Address" %}
</label>
<input type="text" class="form-control" id="modifyRuleIP" name="ip_address" placeholder="{% trans '0.0.0.0/0 for all IPs' %}">
<small class="form-text text-muted">{% trans "Enter IP address or CIDR notation (e.g., 192.168.1.0/24)" %}</small>
</div>
<div class="form-group">
<label for="modifyRulePort">
<i class="fas fa-plug"></i> {% trans "Port" %}
</label>
<input type="text" class="form-control" id="modifyRulePort" name="port" placeholder="{% trans 'e.g., 80 or 8000-8010' %}">
<small class="form-text text-muted">{% trans "Enter port number or range" %}</small>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Note:" %}</strong> {% trans "Modifying a rule will update the firewall configuration. The old rule will be removed and replaced with the new settings." %}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="closeModifyRuleModal()">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</button>
<button type="button" class="btn btn-primary" ng-click="modifyRule()">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
<!-- Modify Banned IP Modal - Inside controller for AngularJS scope access -->
<div class="modal fade" id="modifyBannedIPModal" tabindex="-1" role="dialog" aria-labelledby="modifyBannedIPModalLabel" style="display: none;">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modifyBannedIPModalLabel">
<i class="fas fa-edit"></i> {% trans "Modify Banned IP" %}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" ng-click="closeModifyModal()" onclick="if(window.closeModifyModalGlobal) { window.closeModifyModalGlobal(); } else { var modal = document.getElementById('modifyBannedIPModal'); if(modal) { modal.classList.remove('show'); modal.removeAttribute('aria-hidden'); modal.setAttribute('aria-hidden', 'true'); modal.style.display = 'none'; document.body.classList.remove('modal-open'); document.body.style.overflow = ''; var backdrops = document.querySelectorAll('.modal-backdrop'); for(var i=0; i<backdrops.length; i++) backdrops[i].remove(); } } return false;">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="modifyBannedIPForm">
<input type="hidden" id="modifyBannedIPId" name="ban_id">
<div class="form-group">
<label for="modifyBannedIPAddress">
<i class="fas fa-globe"></i> {% trans "IP Address" %}
</label>
<input type="text" class="form-control" id="modifyBannedIPAddress" name="ip_address" placeholder="{% trans 'Enter IP address' %}">
<small class="form-text text-muted">{% trans "Enter the IP address to ban" %}</small>
</div>
<div class="form-group">
<label for="modifyBannedIPReason">
<i class="fas fa-comment-alt"></i> {% trans "Reason" %}
</label>
<input type="text" class="form-control" id="modifyBannedIPReason" name="reason" placeholder="{% trans 'Enter ban reason' %}">
<small class="form-text text-muted">{% trans "Reason for banning this IP address" %}</small>
</div>
<div class="form-group">
<label for="modifyBannedIPDuration">
<i class="fas fa-clock"></i> {% trans "Duration" %}
</label>
<select class="form-control" id="modifyBannedIPDuration" name="duration">
<option value="1h">{% trans "1 Hour" %}</option>
<option value="6h">{% trans "6 Hours" %}</option>
<option value="12h">{% trans "12 Hours" %}</option>
<option value="24h">{% trans "24 Hours" %}</option>
<option value="48h">{% trans "48 Hours" %}</option>
<option value="7d">{% trans "7 Days" %}</option>
<option value="30d">{% trans "30 Days" %}</option>
<option value="never" selected>{% trans "Never (Permanent)" %}</option>
</select>
<small class="form-text text-muted">{% trans "How long should this IP remain banned?" %}</small>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>{% trans "Note:" %}</strong> {% trans "Modifying a ban will update the IP address, reason, and expiration time. Changing the IP address will update the firewall rule." %}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" ng-click="closeModifyModal()" onclick="if(window.closeModifyModalGlobal) { window.closeModifyModalGlobal(); } else { var modal = document.getElementById('modifyBannedIPModal'); if(modal) { modal.classList.remove('show'); modal.removeAttribute('aria-hidden'); modal.setAttribute('aria-hidden', 'true'); modal.style.display = 'none'; document.body.classList.remove('modal-open'); document.body.style.overflow = ''; var backdrops = document.querySelectorAll('.modal-backdrop'); for(var i=0; i<backdrops.length; i++) backdrops[i].remove(); } } return false;">
<i class="fas fa-times"></i> {% trans "Cancel" %}
</button>
<button type="button" class="btn btn-primary" ng-click="modifyBannedIP()" onclick="if(window.modifyBannedIPGlobal) { window.modifyBannedIPGlobal(); } else { alert('Error: Cannot save changes. Please refresh the page.'); } return false;">
<i class="fas fa-save"></i> {% trans "Save Changes" %}
</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -17,7 +17,7 @@ except:
import plogical.CyberCPLogFileWriter as logging
try:
from loginSystem.views import loadLoginPage
from websiteFunctions.models import Websites
from websiteFunctions.models import Websites, ChildDomains
from plogical.ftpUtilities import FTPUtilities
from plogical.acl import ACLManager
except:
@@ -102,13 +102,14 @@ class FTPManager:
result = FTPUtilities.submitFTPCreation(domainName, userName, password, path, admin.userName, api, customQuotaSize, enableCustomQuota)
if result[0] == 1:
data_ret = {'status': 1, 'creatFTPStatus': 1, 'error_message': 'None'}
created_username = (admin.userName + "_" + userName) if api == '0' else userName
data_ret = {'status': 1, 'creatFTPStatus': 1, 'error_message': 'None', 'createdFTPUsername': created_username}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
else:
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': result[1]}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
except BaseException as msg:
# Enhanced error handling with better user feedback
@@ -135,7 +136,7 @@ class FTPManager:
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': error_message}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
def deleteFTPAccount(self):
userID = self.request.session['userID']
@@ -161,23 +162,30 @@ class FTPManager:
return ACLManager.loadErrorJson('fetchStatus', 0)
data = json.loads(self.request.body)
domain = data['ftpDomain']
domain = data.get('ftpDomain') or data.get('selectedDomain', '')
if not domain or not str(domain).strip():
return HttpResponse(json.dumps({'fetchStatus': 0, 'error_message': 'No domain selected'}), content_type='application/json')
admin = Administrator.objects.get(pk=userID)
if ACLManager.checkOwnership(domain, admin, currentACL) == 1:
pass
else:
return ACLManager.loadErrorJson()
return ACLManager.loadErrorJson('fetchStatus', 0)
website = Websites.objects.get(domain=domain)
try:
child = ChildDomains.objects.get(domain=domain)
website = child.master
except ChildDomains.DoesNotExist:
website = Websites.objects.get(domain=domain)
ftpAccounts = website.users_set.all()
ftpAccounts = website.users_set.values_list('user', flat=True)
json_data = "["
checker = 0
for items in ftpAccounts:
dic = {"userName": items.user}
for userName in ftpAccounts:
dic = {"userName": userName}
if checker == 0:
json_data = json_data + json.dumps(dic)
@@ -187,12 +195,12 @@ class FTPManager:
json_data = json_data + ']'
final_json = json.dumps({'fetchStatus': 1, 'error_message': "None", "data": json_data})
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
except BaseException as msg:
data_ret = {'fetchStatus': 0, 'error_message': str(msg)}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
def submitFTPDelete(self):
try:
@@ -808,9 +816,15 @@ class FTPManager:
logging.CyberCPLogFileWriter.statusWriter(self.extraArgs['tempStatusPath'], 'Completed [200].')
except BaseException as msg:
final_dic = {'status': 0, 'error_message': str(msg)}
final_json = json.dumps(final_dic)
return HttpResponse(final_json)
err_msg = str(msg)
try:
logging.CyberCPLogFileWriter.statusWriter(
self.extraArgs['tempStatusPath'],
'[ERROR] %s [404].' % err_msg
)
except Exception:
pass
return 0
def main():

View File

@@ -18,21 +18,32 @@ app.controller('createFTPAccount', function ($scope, $http) {
$( ".ftpDetails" ).hide();
$( ".ftpPasswordView" ).hide();
// Check if select2 is available
if ($.fn.select2) {
$('.create-ftp-acct-select').select2();
$('.create-ftp-acct-select').on('select2:select', function (e) {
var data = e.params.data;
$scope.ftpDomain = data.text;
$( ".ftpDetails" ).show();
});
// 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');
if ($sel.length) {
$sel.select2();
$sel.on('select2:select', function (e) {
var data = e.params.data;
$scope.ftpDomain = data.text;
$scope.$apply();
$(".ftpDetails").show();
});
} else {
initNativeSelect();
}
} catch (err) {
initNativeSelect();
}
} else {
// Fallback for regular select
$('.create-ftp-acct-select').on('change', function (e) {
initNativeSelect();
}
function initNativeSelect() {
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
$scope.ftpDomain = $(this).val();
$scope.$apply();
$( ".ftpDetails" ).show();
$(".ftpDetails").show();
});
}
});
@@ -108,7 +119,8 @@ app.controller('createFTPAccount', function ($scope, $http) {
ftpDomain: ftpDomain,
ftpUserName: ftpUserName,
passwordByPass: ftpPassword,
path: path,
path: path || '',
api: '0',
enableCustomQuota: $scope.enableCustomQuota || false,
customQuotaSize: $scope.customQuotaSize || 0,
};
@@ -123,52 +135,36 @@ app.controller('createFTPAccount', function ($scope, $http) {
function ListInitialDatas(response) {
if (response.data.creatFTPStatus === 1) {
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;
// Also show PNotify if available
$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'
});
new PNotify({ title: 'Success!', text: 'FTP account successfully created.', type: 'success' });
}
} else {
$scope.ftpLoading = false; // Hide loading on error
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = response.data.error_message;
// Also show PNotify if available
$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; // Hide loading on connection error
$scope.couldNotConnect = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
// Also show PNotify if available
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
$scope.ftpLoading = false;
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' });
}
}
}
@@ -260,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 () {
@@ -370,7 +358,7 @@ app.controller('deleteFTPAccount', function ($scope, $http) {
/* Java script code to delete ftp account ends here */
app.controller('listFTPAccounts', function ($scope, $http, ) {
app.controller('listFTPAccounts', function ($scope, $http) {
$scope.recordsFetched = true;
$scope.passwordChanged = true;
@@ -639,151 +627,117 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
app.controller('Resetftpconf', function ($scope, $http, $timeout){
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;
url = "/ftp/resetftpnow";
var data = {
};
var url = "/ftp/resetftpnow";
var data = {};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
if (response.data.status === 1) {
function ListInitialData(response) {
if (response.data && response.data.status === 1) {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
$scope.statusfile = response.data.tempStatusPath
$scope.alertType = '';
$scope.statusfile = response.data.tempStatusPath;
$timeout(getRequestStatus, 1000);
} else {
$scope.errorMessage = response.data.error_message;
$scope.errorMessage = (response.data && (response.data.error_message || response.data.errorMessage)) || 'Unknown error';
$scope.alertType = 'failedToStart';
$scope.NotifyBox = false;
$scope.InstallBox = true;
$scope.Loading = true;
$scope.failedToStartInallation = false;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.Loading = false;
}
}
function cantLoadInitialData(response) {
$scope.cyberhosting = true;
new PNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
});
$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.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
url = "/ftp/getresetstatus";
var data = {
statusfile: $scope.statusfile
};
$scope.alertType = '';
var url = "/ftp/getresetstatus";
var data = { statusfile: $scope.statusfile };
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
'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.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
$scope.requestData = response.data.requestStatus;
$timeout(getRequestStatus, 1000);
$scope.alertType = '';
$scope.requestData = response.data.requestStatus || '';
statusPollPromise = $timeout(getRequestStatus, 1000);
} else {
// Notifications
$timeout.cancel();
if (statusPollPromise) {
$timeout.cancel(statusPollPromise);
statusPollPromise = null;
}
$scope.NotifyBox = false;
$scope.InstallBox = false;
$scope.Loading = true;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.requestData = response.data.requestStatus;
$scope.Loading = false;
$scope.requestData = response.data.requestStatus || '';
if (response.data.installed === 0) {
$scope.installationFailed = false;
$scope.errorMessage = response.data.error_message;
$scope.alertType = 'resetFailed';
$scope.errorMessage = response.data.error_message || 'Reset failed';
} else {
$scope.modSecSuccessfullyInstalled = false;
$timeout(function () {
$window.location.reload();
}, 3000);
$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 = false;
$scope.Loading = true;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = false;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
$scope.InstallBox = true;
$scope.Loading = false;
}
}
});

View File

@@ -308,24 +308,22 @@
</button>
</div>
<!-- Alert Messages -->
<div ng-hide="NotifyBox">
<div ng-hide="failedToStartInallation" class="alert alert-danger">
<!-- Alert Messages (only one shown at a time) -->
<div ng-show="!NotifyBox && alertType">
<div ng-if="alertType === 'failedToStart'" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
{% trans "Failed to start reset process. Error message:" %} {$ errorMessage $}
{% trans "Failed to start reset process. Error message:" %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<div ng-if="alertType === 'couldNotConnect'" class="alert alert-danger">
<i class="fas fa-times-circle"></i>
{% trans "Could not connect. Please refresh this page." %}
<span ng-if="errorMessage" ng-bind="errorMessage"></span>
</div>
<div ng-hide="installationFailed" class="alert alert-danger">
<div ng-if="alertType === 'resetFailed'" class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>
{% trans "Reset process failed." %} {$ errorMessage $}
{% trans "Reset process failed." %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="modSecSuccessfullyInstalled" class="alert alert-success">
<div ng-if="alertType === 'success'" class="alert alert-success">
<i class="fas fa-check-circle"></i>
{% trans "Successfully completed reset process." %}
</div>

View File

@@ -575,7 +575,7 @@
</div>
<div style="margin-top: 2rem;">
<button type="button" ng-click="createFTPAccount()" class="btn-primary">
<button type="button" ng-click="createFTPAccount()" ng-disabled="ftpLoading" class="btn-primary">
<i class="fas fa-plus-circle"></i>
{% trans "Create FTP Account" %}
</button>

View File

@@ -18,4 +18,7 @@ urlpatterns = [
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
path('getFTPQuotaUsage', views.getFTPQuotaUsage, name='getFTPQuotaUsage'),
path('migrateFTPQuotas', views.migrateFTPQuotas, name='migrateFTPQuotas'),
# FTP Quota Management page (at /ftp/quotaManagement to avoid websites/<domain> catch-all)
path('quotaManagement', views.ftpQuotaManagementPage, name='ftpQuotaManagementPage'),
]

View File

@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import json
import os
import time
from random import randint
@@ -20,6 +21,15 @@ def loadFTPHome(request):
except KeyError:
return redirect(loadLoginPage)
def ftpQuotaManagementPage(request):
"""Render the FTP Quota Management page (served from /ftp/ to avoid websites/<domain> conflict)."""
try:
proc = httpProc(request, 'websiteFunctions/ftpQuotaManagement.html', {}, 'admin')
return proc.render()
except KeyError:
return redirect(loadLoginPage)
def createFTPAccount(request):
try:
@@ -113,29 +123,43 @@ def ResetFTPConfigurations(request):
def resetftpnow(request):
try:
from plogical.virtualHostUtilities import virtualHostUtilities
userID = request.session['userID']
currentACL = ACLManager.loadedACL(userID)
if currentACL['admin'] == 1:
pass
else:
if currentACL['admin'] != 1:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
tempStatusPath = "/home/cyberpanel/" + str(randint(1000, 9999))
execPath = f"/usr/local/CyberCP/bin/python /usr/local/CyberCP/ftp/ftpManager.py ResetFTPConfigurations --tempStatusPath {tempStatusPath}"
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8') if body else '{}'
data = json.loads(body) if body and body.strip() else {}
except (json.JSONDecodeError, ValueError):
data = {}
tempStatusPath = os.path.join('/tmp', 'cyberpanel_ftp_reset_' + str(randint(10000, 99999)))
try:
with open(tempStatusPath, 'w') as f:
f.write("Starting FTP reset...,0\n")
except OSError as e:
data_ret = {'status': 0, 'error_message': 'Cannot create status file: ' + str(e), 'tempStatusPath': ''}
return HttpResponse(json.dumps(data_ret), content_type='application/json')
python_path = '/usr/local/CyberCP/bin/python'
if not os.path.exists(python_path):
for p in ('/usr/bin/python3', '/usr/bin/python'):
if os.path.exists(p):
python_path = p
break
else:
python_path = 'python3'
execPath = f"{python_path} /usr/local/CyberCP/ftp/ftpManager.py ResetFTPConfigurations --tempStatusPath {tempStatusPath}"
ProcessUtilities.popenExecutioner(execPath)
time.sleep(2)
data_ret = {'status': 1, 'error_message': "None",
'tempStatusPath': tempStatusPath}
data_ret = {'status': 1, 'error_message': "None", 'tempStatusPath': tempStatusPath}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
except KeyError:
return redirect(loadLoginPage)
@@ -152,42 +176,56 @@ def getresetstatus(request):
else:
return ACLManager.loadErrorJson('FilemanagerAdmin', 0)
data = json.loads(request.body)
statusfile = data['statusfile']
installStatus = ProcessUtilities.outputExecutioner("sudo cat " + statusfile)
try:
body = request.body
if isinstance(body, bytes):
body = body.decode('utf-8') if body else '{}'
data = json.loads(body) if body and body.strip() else {}
except (json.JSONDecodeError, ValueError, TypeError):
data = {}
statusfile = data.get('statusfile', '')
if not statusfile:
return HttpResponse(json.dumps({'abort': 1, 'installed': 0, 'error_message': 'Missing status file', 'requestStatus': ''}), content_type='application/json')
result = ProcessUtilities.outputExecutioner("cat " + statusfile)
if result is None:
installStatus = ""
elif isinstance(result, tuple):
installStatus = result[1] if len(result) > 1 else ""
else:
installStatus = str(result) if result else ""
if installStatus.find("[200]") > -1:
command = 'sudo rm -f ' + statusfile
command = 'rm -f ' + statusfile
ProcessUtilities.executioner(command)
final_json = json.dumps({
return HttpResponse(json.dumps({
'error_message': "None",
'requestStatus': installStatus,
'abort': 1,
'installed': 1,
})
return HttpResponse(final_json)
}), content_type='application/json')
elif installStatus.find("[404]") > -1:
command = 'sudo rm -f ' + statusfile
command = 'rm -f ' + statusfile
ProcessUtilities.executioner(command)
err_msg = installStatus.replace('[404]', '').strip() if installStatus else 'Reset failed'
if not err_msg or err_msg == ',':
err_msg = 'FTP configuration reset failed'
final_json = json.dumps({
'abort': 1,
'installed': 0,
'error_message': "None",
'error_message': err_msg,
'requestStatus': installStatus,
})
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
else:
final_json = json.dumps({
return HttpResponse(json.dumps({
'abort': 0,
'error_message': "None",
'requestStatus': installStatus,
})
return HttpResponse(final_json)
'requestStatus': installStatus or '',
}), content_type='application/json')
except KeyError:
return redirect(loadLoginPage)

View File

@@ -131,9 +131,10 @@ class FTPUtilities:
## need to get gid and uid
try:
website = ChildDomains.objects.get(domain=domainName)
externalApp = website.master.externalApp
except:
child = ChildDomains.objects.get(domain=domainName)
website = child.master
externalApp = child.master.externalApp
except ChildDomains.DoesNotExist:
website = Websites.objects.get(domain=domainName)
externalApp = website.externalApp

View File

@@ -6,7 +6,8 @@
/* 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;
@@ -15,29 +16,41 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.generatedPasswordView = true;
$(document).ready(function () {
$( ".ftpDetails" ).hide();
$( ".ftpPasswordView" ).hide();
// Check if select2 is available
if ($.fn.select2) {
$('.create-ftp-acct-select').select2();
$('.create-ftp-acct-select').on('select2:select', function (e) {
var data = e.params.data;
$scope.ftpDomain = data.text;
$( ".ftpDetails" ).show();
});
$(".ftpDetails").hide();
$(".ftpPasswordView").hide();
if (typeof $ !== 'undefined' && $ && typeof $.fn !== 'undefined' && typeof $.fn.select2 === 'function') {
try {
var $sel = $('.create-ftp-acct-select');
if ($sel.length) {
$sel.select2();
$sel.on('select2:select', function (e) {
var data = e.params.data;
$scope.ftpDomain = data.text;
$scope.ftpDetails = false;
$scope.$apply();
$(".ftpDetails").show();
});
} else {
initNativeSelect();
}
} catch (err) {
initNativeSelect();
}
} else {
// Fallback for regular select
$('.create-ftp-acct-select').on('change', function (e) {
initNativeSelect();
}
function initNativeSelect() {
$('.create-ftp-acct-select').off('select2:select').on('change', function () {
$scope.ftpDomain = $(this).val();
$scope.ftpDetails = !($scope.ftpDomain && $scope.ftpDomain !== "");
$scope.$apply();
$( ".ftpDetails" ).show();
if ($scope.ftpDomain && $scope.ftpDomain !== "") $(".ftpDetails").show();
else $(".ftpDetails").hide();
});
}
});
$scope.showFTPDetails = function() {
$scope.showFTPDetails = function () {
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
$(".ftpDetails").show();
$scope.ftpDetails = false;
@@ -49,7 +62,7 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.createFTPAccount = function () {
$scope.ftpLoading = true; // Show loading while creating
$scope.ftpLoading = true;
$scope.ftpDetails = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
@@ -60,49 +73,8 @@ app.controller('createFTPAccount', function ($scope, $http) {
var ftpPassword = $scope.ftpPassword;
var path = $scope.ftpPath;
// 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";
if (typeof path === 'undefined' || path == null) path = "";
else path = String(path).trim();
var data = {
ftpDomain: ftpDomain,
@@ -112,6 +84,7 @@ app.controller('createFTPAccount', function ($scope, $http) {
enableCustomQuota: $scope.enableCustomQuota || false,
customQuotaSize: $scope.customQuotaSize || 0,
};
var url = "/ftp/submitFTPCreation";
var config = {
headers: {
@@ -123,52 +96,36 @@ app.controller('createFTPAccount', function ($scope, $http) {
function ListInitialDatas(response) {
if (response.data.creatFTPStatus === 1) {
$scope.ftpLoading = false; // Hide loading on success
$scope.ftpLoading = false;
$scope.successfullyCreatedFTP = false;
$scope.canNotCreateFTP = true;
$scope.couldNotConnect = true;
$scope.createdFTPUsername = ftpDomain + "_" + ftpUserName;
// Also show PNotify if available
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Success!',
text: 'FTP account successfully created.',
type: 'success'
});
new PNotify({ title: 'Success!', text: 'FTP account successfully created.', type: 'success' });
}
} else {
$scope.ftpLoading = false; // Hide loading on error
$scope.ftpLoading = false;
$scope.canNotCreateFTP = false;
$scope.successfullyCreatedFTP = true;
$scope.couldNotConnect = true;
$scope.errorMessage = response.data.error_message;
// Also show PNotify if available
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Operation Failed!',
text: response.data.error_message,
type: 'error'
});
new PNotify({ title: 'Operation Failed!', text: response.data.error_message, type: 'error' });
}
}
}
function cantLoadInitialDatas(response) {
$scope.ftpLoading = false; // Hide loading on connection error
$scope.ftpLoading = false;
$scope.couldNotConnect = false;
$scope.canNotCreateFTP = true;
$scope.successfullyCreatedFTP = true;
// Also show PNotify if available
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Operation Failed!',
text: 'Could not connect to server, please refresh this page',
type: 'error'
});
new PNotify({ title: 'Operation Failed!', text: 'Could not connect to server, please refresh this page', type: 'error' });
}
}
@@ -194,11 +151,8 @@ app.controller('createFTPAccount', function ($scope, $http) {
$scope.generatedPasswordView = true;
};
// Quota management functions
$scope.toggleCustomQuota = function() {
if (!$scope.enableCustomQuota) {
$scope.customQuotaSize = 0;
}
$scope.toggleCustomQuota = function () {
if (!$scope.enableCustomQuota) $scope.customQuotaSize = 0;
};
});
@@ -370,16 +324,15 @@ app.controller('deleteFTPAccount', function ($scope, $http) {
/* Java script code to delete ftp account ends here */
app.controller('listFTPAccounts', function ($scope, $http, ) {
app.controller('listFTPAccounts', function ($scope, $http) {
$scope.recordsFetched = true;
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false;
$scope.ftpLoading = true;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
$scope.quotaManagementBox = true;
$scope.notificationsBox = true;
var globalFTPUsername = "";
@@ -393,7 +346,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false; // Don't show loading when opening password dialog
$scope.ftpLoading = true;
$scope.changePasswordBox = false;
$scope.notificationsBox = true;
$scope.ftpUsername = ftpUsername;
@@ -403,7 +356,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.changePasswordBtn = function () {
$scope.ftpLoading = true; // Show loading while changing password
$scope.ftpLoading = false;
url = "/ftp/changePassword";
@@ -429,13 +382,13 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
if (response.data.changePasswordStatus == 1) {
$scope.notificationsBox = false;
$scope.passwordChanged = false;
$scope.ftpLoading = false; // Hide loading when done
$scope.ftpLoading = true;
$scope.domainFeteched = $scope.selectedDomain;
} else {
$scope.notificationsBox = false;
$scope.canNotChangePassword = false;
$scope.ftpLoading = false; // Hide loading on error
$scope.ftpLoading = true;
$scope.canNotChangePassword = false;
$scope.errorMessage = response.data.error_message;
}
@@ -445,7 +398,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
function cantLoadInitialDatas(response) {
$scope.notificationsBox = false;
$scope.couldNotConnect = false;
$scope.ftpLoading = false; // Hide loading on connection error
$scope.ftpLoading = true;
}
@@ -456,7 +409,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = true; // Show loading while fetching
$scope.ftpLoading = false;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -491,7 +444,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false; // Hide loading when done
$scope.ftpLoading = true;
$scope.ftpAccounts = false;
$scope.changePasswordBox = true;
@@ -503,7 +456,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = true;
$scope.ftpLoading = false; // Hide loading on error
$scope.ftpLoading = true;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -518,7 +471,7 @@ app.controller('listFTPAccounts', function ($scope, $http, ) {
$scope.passwordChanged = true;
$scope.canNotChangePassword = true;
$scope.couldNotConnect = false;
$scope.ftpLoading = false; // Hide loading on connection error
$scope.ftpLoading = true;
$scope.ftpAccounts = true;
$scope.changePasswordBox = true;
@@ -540,250 +493,4 @@ 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){
$scope.Loading = true;
$scope.NotifyBox = true;
$scope.InstallBox = true;
$scope.resetftp = function () {
$scope.Loading = false;
$scope.installationDetailsForm = true;
$scope.InstallBox = false;
url = "/ftp/resetftpnow";
var data = {
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
function ListInitialData(response) {
if (response.data.status === 1) {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
$scope.statusfile = response.data.tempStatusPath
$timeout(getRequestStatus, 1000);
} else {
$scope.errorMessage = response.data.error_message;
$scope.NotifyBox = false;
$scope.InstallBox = true;
$scope.Loading = true;
$scope.failedToStartInallation = false;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
}
}
function cantLoadInitialData(response) {
$scope.cyberhosting = true;
new PNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
});
}
}
function getRequestStatus() {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
url = "/ftp/getresetstatus";
var data = {
statusfile: $scope.statusfile
};
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
}
};
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
function ListInitialDatas(response) {
if (response.data.abort === 0) {
$scope.NotifyBox = true;
$scope.InstallBox = false;
$scope.Loading = false;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
$scope.requestData = response.data.requestStatus;
$timeout(getRequestStatus, 1000);
} else {
// Notifications
$timeout.cancel();
$scope.NotifyBox = false;
$scope.InstallBox = false;
$scope.Loading = true;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = true;
$scope.requestData = response.data.requestStatus;
if (response.data.installed === 0) {
$scope.installationFailed = false;
$scope.errorMessage = response.data.error_message;
} else {
$scope.modSecSuccessfullyInstalled = false;
$timeout(function () {
$window.location.reload();
}, 3000);
}
}
}
function cantLoadInitialDatas(response) {
$scope.NotifyBox = false;
$scope.InstallBox = false;
$scope.Loading = true;
$scope.failedToStartInallation = true;
$scope.couldNotConnect = false;
$scope.modSecSuccessfullyInstalled = true;
$scope.installationFailed = true;
}
}
});

View File

@@ -2,6 +2,15 @@
* Created by usman on 8/5/17.
*/
/* Safe notification - use PNotify if available, else fallback to alert */
function safePNotify(opts) {
if (typeof PNotify !== 'undefined') {
new PNotify(opts);
} else {
var msg = (opts.title || '') + (opts.text ? ': ' + opts.text : '');
alert(msg || JSON.stringify(opts));
}
}
/* Java script code to create account */
app.controller('createUserCtr', function ($scope, $http) {
@@ -51,7 +60,7 @@ app.controller('createUserCtr', function ($scope, $http) {
$scope.acctsLimit = true;
$scope.webLimits = true;
$scope.userCreated = true;
$scope.userCreationFailed = true;
$scope.userCreationFailed = false; // false = don't show error alert on load
$scope.couldNotConnect = true;
$scope.userCreationLoading = true;
$scope.combinedLength = true;
@@ -60,20 +69,24 @@ app.controller('createUserCtr', function ($scope, $http) {
$scope.webLimits = false;
$scope.userCreated = true;
$scope.userCreationFailed = true;
$scope.userCreationFailed = false; // hide error until we know the result
$scope.couldNotConnect = true;
$scope.userCreationLoading = false;
$scope.combinedLength = true;
var firstName = $scope.firstName;
var lastName = $scope.lastName;
var firstName = $scope.firstName || '';
var lastName = $scope.lastName || '';
var email = $scope.email;
var selectedACL = $scope.selectedACL;
var websitesLimits = $scope.websitesLimits;
var userName = $scope.userName;
var password = $scope.password;
if (firstName.length + lastName.length > 20) {
$scope.combinedLength = false;
$scope.userCreationLoading = true;
return;
}
var url = "/users/submitUserCreation";
@@ -91,7 +104,8 @@ app.controller('createUserCtr', function ($scope, $http) {
var config = {
headers: {
'X-CSRFToken': getCookie('csrftoken')
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json'
}
};
@@ -102,42 +116,31 @@ app.controller('createUserCtr', function ($scope, $http) {
if (response.data.createStatus == 1) {
$scope.userCreated = false;
$scope.userCreationFailed = true;
$scope.userCreated = false; // show success
$scope.userCreationFailed = false; // hide error
$scope.couldNotConnect = true;
$scope.userCreationLoading = true;
$scope.userName = userName;
} else {
$scope.acctsLimit = false;
$scope.webLimits = false;
$scope.userCreated = true;
$scope.userCreationFailed = false;
$scope.userCreationFailed = true; // true = show error alert
$scope.couldNotConnect = true;
$scope.userCreationLoading = true;
$scope.errorMessage = response.data.error_message;
$scope.errorMessage = (response.data && (response.data.error_message || response.data.message || response.data.errorMessage)) || 'Unknown error';
}
}
function cantLoadInitialDatas(response) {
$scope.acctsLimit = false;
$scope.webLimits = false;
$scope.userCreated = true;
$scope.userCreationFailed = true;
$scope.couldNotConnect = false;
$scope.userCreationFailed = false; // hide server error, show connection error instead
$scope.couldNotConnect = false; // show "Could not connect" message
$scope.userCreationLoading = true;
}
@@ -181,10 +184,10 @@ app.controller('modifyUser', function ($scope, $http) {
$scope.acctDetailsFetched = true;
$scope.userAccountsLimit = true;
$scope.userModified = true;
$scope.canotModifyUser = true;
$scope.canotModifyUser = false; // false = don't show error alert on load
$scope.couldNotConnect = true;
$scope.canotFetchDetails = true;
$scope.detailsFetched = true;
$scope.canotFetchDetails = false; // false = don't show fetch error on load
$scope.detailsFetched = false; // false = don't show "details loaded" on load
$scope.accountTypeView = true;
$scope.websitesLimit = true;
$scope.qrHidden = true;
@@ -716,7 +719,7 @@ app.controller('deleteUser', function ($scope, $http) {
app.controller('createACLCTRL', function ($scope, $http) {
$scope.aclCreated = true;
$scope.aclCreationFailed = true;
$scope.aclCreationFailed = false; // false = don't show error alert on load
$scope.couldNotConnect = true;
$scope.aclLoading = true;
@@ -887,14 +890,14 @@ app.controller('createACLCTRL', function ($scope, $http) {
$scope.aclLoading = true;
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'ACL Successfully created.',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -909,7 +912,7 @@ app.controller('createACLCTRL', function ($scope, $http) {
$scope.aclLoading = false;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1108,14 +1111,14 @@ app.controller('deleteACTCTRL', function ($scope, $http) {
$scope.aclLoading = true;
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'ACL Successfully deleted.',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -1127,7 +1130,7 @@ app.controller('deleteACTCTRL', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.aclLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1171,7 +1174,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'Current settings successfully fetched',
type: 'success'
@@ -1251,7 +1254,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
$scope.mailServerSSL = Boolean(response.data.mailServerSSL);
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -1264,7 +1267,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
$scope.aclLoading = false;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1366,14 +1369,14 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
$scope.aclLoading = true;
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'ACL Successfully modified.',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -1388,7 +1391,7 @@ app.controller('modifyACLCtrl', function ($scope, $http) {
$scope.aclLoading = false;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1580,14 +1583,14 @@ app.controller('changeUserACLCTRL', function ($scope, $http) {
$scope.aclLoading = true;
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'ACL Successfully changed.',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -1599,7 +1602,7 @@ app.controller('changeUserACLCTRL', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.aclLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1642,14 +1645,14 @@ app.controller('resellerCenterCTRL', function ($scope, $http) {
$scope.aclLoading = true;
if (response.data.status === 1) {
new PNotify({
safePNotify({
title: 'Success!',
text: 'Changes successfully applied!',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -1661,7 +1664,7 @@ app.controller('resellerCenterCTRL', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.aclLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1711,14 +1714,14 @@ app.controller('apiAccessCTRL', function ($scope, $http) {
if (response.data.status === 1) {
$scope.apiAccessDropDown = true;
new PNotify({
safePNotify({
title: 'Success!',
text: 'Changes successfully applied!',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -1730,7 +1733,7 @@ app.controller('apiAccessCTRL', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1771,13 +1774,13 @@ app.controller('apiUsersCTRL', function ($scope, $http) {
$scope.apiUsers = response.data.users;
$scope.filteredUsers = response.data.users;
new PNotify({
safePNotify({
title: 'Success!',
text: 'API users loaded successfully',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -1787,7 +1790,7 @@ app.controller('apiUsersCTRL', function ($scope, $http) {
function loadAPIUsersError(response) {
$scope.apiUsersLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not load API users. Please refresh the page.',
type: 'error'
@@ -1816,7 +1819,7 @@ app.controller('apiUsersCTRL', function ($scope, $http) {
};
$scope.viewUserDetails = function(user) {
new PNotify({
safePNotify({
title: 'User Details',
text: 'Username: ' + user.userName + '<br>' +
'Full Name: ' + user.firstName + ' ' + user.lastName + '<br>' +
@@ -1859,13 +1862,13 @@ app.controller('apiUsersCTRL', function ($scope, $http) {
});
$scope.filteredUsers = $scope.apiUsers;
new PNotify({
safePNotify({
title: 'Success!',
text: 'API access disabled for ' + response.data.accountUsername,
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -1875,7 +1878,7 @@ app.controller('apiUsersCTRL', function ($scope, $http) {
function disableAPIError(response) {
$scope.apiUsersLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not disable API access. Please try again.',
type: 'error'
@@ -1919,14 +1922,14 @@ app.controller('listTableUsers', function ($scope, $http) {
$scope.records = JSON.parse(response.data.data);
new PNotify({
safePNotify({
title: 'Success!',
text: 'Users successfully fetched!',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -1937,7 +1940,7 @@ app.controller('listTableUsers', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -1977,7 +1980,7 @@ app.controller('listTableUsers', function ($scope, $http) {
if (response.data.deleteStatus === 1) {
$scope.populateCurrentRecords();
$('#deleteModal').modal('hide');
new PNotify({
safePNotify({
title: 'Success!',
text: 'Users successfully deleted!',
type: 'success'
@@ -1985,7 +1988,7 @@ app.controller('listTableUsers', function ($scope, $http) {
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -1999,7 +2002,7 @@ app.controller('listTableUsers', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = false;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -2041,14 +2044,14 @@ app.controller('listTableUsers', function ($scope, $http) {
if (response.data.status === 1) {
$scope.populateCurrentRecords();
$('#editModal').modal('hide');
new PNotify({
safePNotify({
title: 'Success!',
text: 'Changes successfully applied!',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -2059,7 +2062,7 @@ app.controller('listTableUsers', function ($scope, $http) {
}
function cantLoadInitialDatas(response) {
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -2095,14 +2098,14 @@ app.controller('listTableUsers', function ($scope, $http) {
if (response.data.status === 1) {
$scope.populateCurrentRecords();
$('#editModal').modal('hide');
new PNotify({
safePNotify({
title: 'Success!',
text: 'ACL Successfully changed.',
type: 'success'
});
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.errorMessage,
type: 'error'
@@ -2114,7 +2117,7 @@ app.controller('listTableUsers', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.aclLoading = true;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'
@@ -2148,7 +2151,7 @@ app.controller('listTableUsers', function ($scope, $http) {
$scope.cyberpanelLoading = true;
if (response.data.status === 1) {
$scope.populateCurrentRecords();
new PNotify({
safePNotify({
title: 'Success!',
text: 'Action successfully started.',
type: 'success'
@@ -2156,7 +2159,7 @@ app.controller('listTableUsers', function ($scope, $http) {
} else {
new PNotify({
safePNotify({
title: 'Error!',
text: response.data.error_message,
type: 'error'
@@ -2170,7 +2173,7 @@ app.controller('listTableUsers', function ($scope, $http) {
function cantLoadInitialDatas(response) {
$scope.cyberpanelLoading = false;
new PNotify({
safePNotify({
title: 'Error!',
text: 'Could not connect to server, please refresh this page.',
type: 'error'

View File

@@ -4,6 +4,8 @@
{% block content %}
{% load static %}
{# Ensure userManagment controllers load before Angular compiles this page #}
<script src="{% static 'userManagment/userManagment.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
@@ -576,24 +578,36 @@
<script>
// Tab switching functionality
function switchTab(tabName) {
var configContent = document.getElementById('configure-content');
var usersContent = document.getElementById('users-content');
var configTab = document.getElementById('configure-tab');
var usersTab = document.getElementById('users-tab');
if (!configContent || !usersContent) return;
// Hide all tab contents
document.getElementById('configure-content').classList.remove('active');
document.getElementById('users-content').classList.remove('active');
// Remove active class from all tabs
document.getElementById('configure-tab').classList.remove('active');
document.getElementById('users-tab').classList.remove('active');
configContent.classList.remove('active');
usersContent.classList.remove('active');
if (configTab) configTab.classList.remove('active');
if (usersTab) usersTab.classList.remove('active');
// Show selected tab content and add active class
if (tabName === 'configure') {
document.getElementById('configure-content').classList.add('active');
document.getElementById('configure-tab').classList.add('active');
configContent.classList.add('active');
if (configTab) configTab.classList.add('active');
} else if (tabName === 'users') {
document.getElementById('users-content').classList.add('active');
document.getElementById('users-tab').classList.add('active');
// Load API users when switching to users tab
if (typeof angular !== 'undefined' && angular.element(document.getElementById('users-content')).scope()) {
angular.element(document.getElementById('users-content')).scope().loadAPIUsers();
usersContent.classList.add('active');
if (usersTab) usersTab.classList.add('active');
// Load API users when switching to users tab - safely get scope and call if available
if (typeof angular !== 'undefined') {
var elem = angular.element(usersContent);
var scope = elem.scope();
if (scope && typeof scope.loadAPIUsers === 'function') {
try {
scope.$apply(function() { scope.loadAPIUsers(); });
} catch (e) {
scope.loadAPIUsers();
}
}
}
}
}

View File

@@ -4,6 +4,7 @@
{% block content %}
{% load static %}
<script src="{% static 'userManagment/userManagment.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
@@ -216,13 +217,13 @@
<i class="fa fa-plus"></i> {% trans "Create ACL" %}
</button>
</div>
<div ng-hide="aclCreated" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "ACL with name:" %} <strong>{$ aclName $}</strong> {% trans "is successfully created." %}
<div ng-cloak ng-show="aclCreated === false" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "ACL with name:" %} <strong><span ng-bind="aclName"></span></strong> {% trans "is successfully created." %}
</div>
<div ng-hide="aclCreationFailed" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot create ACL. Error message:" %} {$ errorMessage $}
<div ng-cloak ng-show="aclCreationFailed === true" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot create ACL. Error message:" %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<div ng-cloak ng-show="couldNotConnect === false" class="alert alert-danger">
<i class="fa fa-times-circle"></i> {% trans "Could not connect to server. Please refresh this page." %}
</div>
</form>

View File

@@ -4,6 +4,8 @@
{% block content %}
{% load static %}
{# Ensure createUserCtr is registered before Angular compiles #}
<script src="{% static 'userManagment/userManagment.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
@@ -250,9 +252,9 @@
<div class="form-col-6">
<div class="form-group">
<label class="form-label">{% trans "First Name" %}</label>
<input name="firstName" ng-pattern="/^[a-zA-Z ]+$/" type="text" class="form-control"
<input name="firstName" ng-pattern="/^[a-zA-Z ]*$/" type="text" class="form-control"
ng-model="firstName" required>
<div ng-show="createUser.firstName.$error.pattern" class="error-text">
<div ng-show="createUser.firstName.$dirty && createUser.firstName.$error.pattern" class="error-text">
{% trans "First Name should contain only alphabetic characters." %}
</div>
</div>
@@ -260,9 +262,9 @@
<div class="form-col-6">
<div class="form-group">
<label class="form-label">{% trans "Last Name" %}</label>
<input name="lastName" ng-pattern="/^[a-zA-Z ]+$/" type="text" class="form-control"
<input name="lastName" ng-pattern="/^[a-zA-Z ]*$/" type="text" class="form-control"
ng-model="lastName" required>
<div ng-show="createUser.lastName.$error.pattern" class="error-text">
<div ng-show="createUser.lastName.$dirty && createUser.lastName.$error.pattern" class="error-text">
{% trans "Last Name should contain only alphabetic characters." %}
</div>
</div>
@@ -272,7 +274,7 @@
<div class="form-group">
<label class="form-label">{% trans "Email Address" %}</label>
<input name="email" type="email" class="form-control" ng-model="email" required>
<div ng-show="createUser.email.$error.email" class="error-text">
<div ng-show="createUser.email.$dirty && createUser.email.$error.email" class="error-text">
{% trans "Please enter a valid email address" %}
</div>
</div>
@@ -348,19 +350,19 @@
</button>
</div>
<div ng-hide="userCreated" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "Account with username:" %} <strong>{$ userName $}</strong> {% trans "is successfully created." %}
<div ng-cloak ng-show="userCreated === false" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "Account with username:" %} <strong><span ng-bind="userName"></span></strong> {% trans "is successfully created." %}
</div>
<div ng-hide="userCreationFailed" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot create user. Error message:" %} {$ errorMessage $}
<div ng-cloak ng-show="userCreationFailed === true" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot create user. Error message:" %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<div ng-cloak ng-show="couldNotConnect === false" class="alert alert-danger">
<i class="fa fa-times-circle"></i> {% trans "Could not connect to server. Please refresh this page." %}
</div>
<div ng-hide="combinedLength" class="alert alert-danger">
<div ng-cloak ng-show="combinedLength === false" class="alert alert-danger">
<i class="fa fa-exclamation-triangle"></i> {% trans "Length of first and last name combined should be less than or equal to 20 characters" %}
</div>
</form>

View File

@@ -5,6 +5,7 @@
{% block content %}
{% load static %}
<script src="{% static 'userManagment/userManagment.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
@@ -427,19 +428,19 @@
</button>
</div>
</div>
<div ng-hide="userModified" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "Account with username:" %} <strong>{$ userName $}</strong> {% trans "is successfully modified." %}
<div ng-cloak ng-show="userModified === false" class="alert alert-success">
<i class="fa fa-check-circle"></i> {% trans "Account with username:" %} <strong><span ng-bind="userName"></span></strong> {% trans "is successfully modified." %}
</div>
<div ng-hide="canotModifyUser" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot modify user. Error message:" %} {$ errorMessage $}
<div ng-cloak ng-show="canotModifyUser === true" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot modify user. Error message:" %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="couldNotConnect" class="alert alert-danger">
<div ng-cloak ng-show="couldNotConnect === false" class="alert alert-danger">
<i class="fa fa-times-circle"></i> {% trans "Could not connect to server. Please refresh this page." %}
</div>
<div ng-hide="canotFetchDetails" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot fetch details. Error message:" %} {$ errorMessage $}
<div ng-cloak ng-show="canotFetchDetails === true" class="alert alert-danger">
<i class="fa fa-exclamation-circle"></i> {% trans "Cannot fetch details. Error message:" %} <span ng-bind="errorMessage || 'Unknown error'"></span>
</div>
<div ng-hide="detailsFetched" class="alert alert-success">
<div ng-cloak ng-show="detailsFetched === true" class="alert alert-success">
<i class="fa fa-info-circle"></i> {% trans "User details loaded successfully." %}
</div>
</form>

View File

@@ -220,7 +220,7 @@ def submitUserCreation(request):
if ACLManager.CheckRegEx("^[\w'\-,.][^0-9_!¡?÷?¿/\\+=@#$%ˆ&*(){}|~<>;:[\]]{2,}$", firstName) == 0:
data_ret = {'status': 0, 'createStatus': 0, 'error_message': 'First Name can only contain alphabetic characters, and should be more than 2 characters long...'}
json_data = json.dumps(data_ret)
return HttpResponse(json_data)
return HttpResponse(json_data, content_type='application/json')
if ACLManager.CheckRegEx("^[\w'\-,.][^0-9_!¡?÷?¿/\\+=@#$%ˆ&*(){}|~<>;:[\]]{2,}$", lastName) == 0:
data_ret = {'status': 0, 'createStatus': 0, 'error_message': 'Last Name can only contain alphabetic characters, and should be more than 2 characters long...'}
@@ -362,7 +362,7 @@ def submitUserCreation(request):
data_ret = {'status': 1, 'createStatus': 1,
'error_message': "None"}
final_json = json.dumps(data_ret)
return HttpResponse(final_json)
return HttpResponse(final_json, content_type='application/json')
except Exception as e:
secure_log_error(e, 'submitUserCreation', request.session.get('userID', 'Unknown'))

View File

@@ -391,7 +391,7 @@
<label class="form-label">{% trans "Domain Name" %}</label>
<input name="dom" type="text" class="form-control" ng-model="domainNameCreate"
placeholder="{% trans "example.com (Do not enter WWW, it will be auto created)" %}" required>
<div ng-show="websiteCreationForm.dom.$error.pattern" class="field-error">
<div ng-show="websiteCreationForm.dom.$dirty && websiteCreationForm.dom.$error.pattern" class="field-error">
{% trans "Invalid Domain (Note: You don't need to add 'http' or 'https')" %}
</div>
</div>
@@ -401,7 +401,7 @@
<label class="form-label">{% trans "Email" %}</label>
<input type="email" name="email" class="form-control" ng-model="adminEmail"
placeholder="{% trans "admin@example.com" %}" required>
<div ng-show="websiteCreationForm.email.$error.email" class="field-error">
<div ng-show="websiteCreationForm.email.$dirty && websiteCreationForm.email.$error.email" class="field-error">
{% trans "Invalid Email" %}
</div>
</div>

View File

@@ -6,6 +6,24 @@ FTP Quota Management - CyberPanel
{% endblock %}
{% block content %}
<style>
/* Ensure FTP Quota info section is readable - dark text on light background */
.ftp-quota-info .alert-info {
background: #e0f2fe !important;
color: #0c4a6e !important;
border: 1px solid #7dd3fc;
}
.ftp-quota-info .alert-info h5,
.ftp-quota-info .alert-info p,
.ftp-quota-info .alert-info .btn-primary {
color: inherit !important;
}
.ftp-quota-info .alert-info .btn-primary {
background: #0284c7 !important;
border-color: #0284c7 !important;
color: white !important;
}
</style>
<div class="container-fluid">
<div class="row">
<div class="col-12">
@@ -18,7 +36,7 @@ FTP Quota Management - CyberPanel
</div>
<div class="card-body">
<!-- Enable FTP Quota Section -->
<div class="row mb-4">
<div class="row mb-4 ftp-quota-info">
<div class="col-12">
<div class="alert alert-info">
<h5><i class="fas fa-info-circle"></i> FTP Quota System</h5>
@@ -112,22 +130,35 @@ function enableFTPQuota() {
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(data) {
if (data.status === 1) {
showNotification('success', data.message);
showNotification('success', (data && (data.message || data.error_message)) || 'Success');
refreshQuotas();
} else {
showNotification('error', data.message);
showNotification('error', (data && (data.error_message || data.message)) || 'Unknown error');
}
});
}
function refreshQuotas() {
$.post('{% url "getFTPQuotas" %}', {
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(data) {
if (data.status === 1) {
displayQuotas(data.quotas);
} else {
showNotification('error', data.message);
var tbody = document.getElementById('quotasTableBody');
if (tbody) tbody.innerHTML = '<tr><td colspan="9" class="text-center"><i class="fas fa-spinner fa-spin"></i> Loading...</td></tr>';
$.ajax({
url: '{% url "getFTPQuotas" %}',
type: 'POST',
data: { 'csrfmiddlewaretoken': '{{ csrf_token }}' },
dataType: 'json',
success: function(data) {
if (data && data.status === 1) {
displayQuotas(data.quotas || []);
} else {
var msg = (data && (data.error_message || data.message)) || 'Unknown error';
showNotification('error', msg);
if (tbody) tbody.innerHTML = '<tr><td colspan="9" class="text-center text-danger">' + msg + '</td></tr>';
}
},
error: function(xhr) {
var msg = (xhr.responseJSON && (xhr.responseJSON.error_message || xhr.responseJSON.message)) || (xhr.statusText || 'Request failed');
showNotification('error', msg);
if (tbody) tbody.innerHTML = '<tr><td colspan="9" class="text-center text-danger">' + msg + '</td></tr>';
}
});
}
@@ -190,11 +221,11 @@ function saveQuota() {
$.post('{% url "updateFTPQuota" %}', formData, function(data) {
if (data.status === 1) {
showNotification('success', data.message);
showNotification('success', (data && (data.message || data.error_message)) || 'Success');
$('#editQuotaModal').modal('hide');
refreshQuotas();
} else {
showNotification('error', data.message);
showNotification('error', (data && (data.error_message || data.message)) || 'Unknown error');
}
});
}

View File

@@ -201,31 +201,33 @@ urlpatterns = [
path('resetVHostConfigToDefault', views.resetVHostConfigToDefault, name='resetVHostConfigToDefault'),
path('getTerminalJWT', views.get_terminal_jwt, name='get_terminal_jwt'),
# Catch all for domains
path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
path('<domain>', views.domain, name='domain'),
path('get_website_resources/', views.get_website_resources, name='get_website_resources'),
# Subdomain Log Fix
path('fixSubdomainLogs', views.fixSubdomainLogs, name='fixSubdomainLogs'),
path('fixSubdomainLogsAction', views.fixSubdomainLogsAction, name='fixSubdomainLogsAction'),
# FTP Quota Management
# FTP Quota Management (API endpoints only; page is at /ftp/quotaManagement)
path('enableFTPQuota', views.enableFTPQuota, name='enableFTPQuota'),
path('getFTPQuotas', views.getFTPQuotas, name='getFTPQuotas'),
path('updateFTPQuota', views.updateFTPQuota, name='updateFTPQuota'),
# Bandwidth Management
path('bandwidthManagement', views.bandwidthManagementPage, name='bandwidthManagementPage'),
path('resetBandwidth', views.resetBandwidth, name='resetBandwidth'),
path('getBandwidthResetLogs', views.getBandwidthResetLogs, name='getBandwidthResetLogs'),
path('scheduleBandwidthReset', views.scheduleBandwidthReset, name='scheduleBandwidthReset'),
# Security Management
path('securityManagement', views.securityManagementPage, name='securityManagementPage'),
# IP Blocking
path('blockIPAddress', views.blockIPAddress, name='blockIPAddress'),
path('unblockIPAddress', views.unblockIPAddress, name='unblockIPAddress'),
path('getBlockedIPs', views.getBlockedIPs, name='getBlockedIPs'),
path('checkIPStatus', views.checkIPStatus, name='checkIPStatus'),
# Catch all for domains (must be last)
path('<domain>/<childDomain>', views.launchChild, name='launchChild'),
path('<domain>', views.domain, name='domain'),
]

View File

@@ -2236,6 +2236,33 @@ def fixSubdomainLogsAction(request):
return redirect(loadLoginPage)
# FTP Quota Management Views
def ftpQuotaManagementPage(request):
"""Render the FTP Quota Management page."""
try:
userID = request.session['userID']
proc = httpProc(request, 'websiteFunctions/ftpQuotaManagement.html', {}, 'admin')
return proc.render()
except KeyError:
return redirect(loadLoginPage)
def bandwidthManagementPage(request):
"""Render the Bandwidth Management page."""
try:
userID = request.session['userID']
proc = httpProc(request, 'websiteFunctions/bandwidthManagement.html', {}, 'admin')
return proc.render()
except KeyError:
return redirect(loadLoginPage)
def securityManagementPage(request):
"""Render the Security Management page."""
try:
userID = request.session['userID']
proc = httpProc(request, 'websiteFunctions/securityManagement.html', {}, 'admin')
return proc.render()
except KeyError:
return redirect(loadLoginPage)
def enableFTPQuota(request):
try:
userID = request.session['userID']

View File

@@ -8744,7 +8744,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
# Backup existing configurations
@@ -8811,7 +8811,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
quotas = FTPQuota.objects.all().order_by('-created_at')
@@ -8856,7 +8856,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
quota_id = data.get('quota_id')
@@ -8899,7 +8899,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
reset_type = data.get('reset_type', 'manual')
@@ -8970,7 +8970,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
logs = BandwidthResetLog.objects.all().order_by('-reset_at')[:50] # Last 50 entries
@@ -9013,7 +9013,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
schedule_type = data.get('schedule_type', 'monthly') # monthly, weekly, daily
@@ -9068,7 +9068,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
ip_address = data.get('ip_address')
@@ -9119,7 +9119,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
ip_address = data.get('ip_address')
@@ -9169,7 +9169,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
# Import firewall utilities
@@ -9203,7 +9203,7 @@ StrictHostKeyChecking no
admin = Administrator.objects.get(pk=userID)
# Check if user has permission
if not ACLManager.checkIfUserIsAdmin(currentACL):
if not (currentACL.get('admin', 0) == 1):
return ACLManager.loadErrorJson('status', 0)
ip_address = data.get('ip_address')