mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 04:37:48 +02:00
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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)}'
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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">×</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">×</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 %}
|
||||
@@ -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():
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
92
ftp/views.py
92
ftp/views.py
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user