mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-05-07 09:07:29 +02:00
FTP, dashboard, and notification fixes for v2.5.5-dev
- FTP: Fix createFTPAccount (ChildDomains), deleteFTPAccount, ResetFTPConfigurations, listFTPAccounts, quotaManagement - FTP: Add quotaManagement page at /ftp/quotaManagement, improve reset status polling and error display - Dashboard: Fix Angular ng-click parse error (remove return false from blockIPAddress) - Dashboard: Add data-cfasync=false to jQuery/system-status for Rocket Loader compatibility - FTP Quota Management: Improve error handling, fix refreshQuotas success/error callbacks - Notification: Add updated_at column for usernotificationpreferences (run SQL migration)
This commit is contained in:
@@ -1071,6 +1071,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
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Initial fetch
|
||||
$scope.refreshTopProcesses();
|
||||
|
||||
@@ -972,22 +972,22 @@
|
||||
<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 -->
|
||||
<div ng-if="alert.title === 'Brute Force Attack Detected' && alert.details && alert.details['IP Address']" style="margin-top: 12px;">
|
||||
<button ng-click="blockIPAddress(alert.details['IP Address'])"
|
||||
ng-disabled="blockingIP === alert.details['IP Address']"
|
||||
<!-- 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'">
|
||||
<i class="fas fa-ban" ng-if="blockingIP !== alert.details['IP Address']"></i>
|
||||
<i class="fas fa-spinner fa-spin" ng-if="blockingIP === alert.details['IP Address']"></i>
|
||||
<span ng-if="blockingIP !== alert.details['IP Address']">Ban IP Permanently</span>
|
||||
<span ng-if="blockingIP === alert.details['IP Address']">Banning...</span>
|
||||
<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 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;">
|
||||
<i class="fas fa-external-link-alt"></i> Manage in Firewall
|
||||
</a>
|
||||
<span ng-if="blockedIPs && blockedIPs[alert.details['IP Address']]"
|
||||
<span ng-if="blockedIPs && blockedIPs[alert.details['IP Address'] || alert.details['Top IP']]"
|
||||
style="margin-left: 10px; color: #10b981; font-size: 12px; font-weight: 600;">
|
||||
<i class="fas fa-check-circle"></i> Blocked
|
||||
</span>
|
||||
@@ -1015,12 +1015,43 @@
|
||||
<tr>
|
||||
<th>TIMESTAMP</th>
|
||||
<th>MESSAGE</th>
|
||||
<th>IP ADDRESS</th>
|
||||
<th>ACTIONS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="log in sshLogs">
|
||||
<td>{$ log.timestamp $}</td>
|
||||
<td>{$ log.message $}</td>
|
||||
<td>
|
||||
<span ng-if="log.ip_address" style="font-family: monospace; color: #5b5fcf; font-weight: 600;">
|
||||
{$ log.ip_address $}
|
||||
</span>
|
||||
<span ng-if="!log.ip_address" style="color: #8893a7;">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<button ng-if="log.ip_address && blockingIP !== log.ip_address && !blockedIPs[log.ip_address]"
|
||||
ng-click="banIPFromSSHLog(log.ip_address)"
|
||||
style="background: #dc2626; color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;"
|
||||
onmouseover="this.style.background='#b91c1c'"
|
||||
onmouseout="this.style.background='#dc2626'"
|
||||
title="Ban this IP address permanently">
|
||||
<i class="fas fa-ban"></i>
|
||||
Ban IP
|
||||
</button>
|
||||
<button ng-if="log.ip_address && blockingIP === log.ip_address"
|
||||
disabled
|
||||
style="background: #9ca3af; color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: not-allowed; display: inline-flex; align-items: center; gap: 4px;">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
Banning...
|
||||
</button>
|
||||
<span ng-if="log.ip_address && blockedIPs[log.ip_address]"
|
||||
style="color: #10b981; font-size: 11px; font-weight: 600; display: inline-flex; align-items: center; gap: 4px;">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Banned
|
||||
</span>
|
||||
<span ng-if="!log.ip_address" style="color: #8893a7; font-size: 11px;">-</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1292,31 +1323,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;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
<!-- Readability Fixes CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/readability-fixes.css' %}?v={{ CP_VERSION }}">
|
||||
|
||||
<!-- Core Scripts -->
|
||||
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
|
||||
<!-- Core Scripts (data-cfasync=false prevents Cloudflare Rocket Loader from breaking load order) -->
|
||||
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
|
||||
<script src="https://code.jquery.com/jquery-2.2.4.min.js" data-cfasync="false"></script>
|
||||
<!-- Bootstrap JavaScript -->
|
||||
|
||||
<script src="{% static 'baseTemplate/assets/bootstrap/js/bootstrap.min.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'baseTemplate/bootstrap-toggle.min.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'baseTemplate/custom-js/qrious.min.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'baseTemplate/custom-js/system-status.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'baseTemplate/custom-js/system-status.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
@@ -46,6 +46,10 @@
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/custom-js/pnotify.custom.min.css' %}?v={{ CP_VERSION }}">
|
||||
<script src="{% static 'baseTemplate/custom-js/pnotify.custom.min.js' %}?v={{ CP_VERSION }}"></script>
|
||||
|
||||
<!-- Select2 (required by FTP create account; load after jQuery; exclude from Rocket Loader to preserve order) -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.full.min.js" data-cfasync="false"></script>
|
||||
|
||||
<!-- Modern Design System -->
|
||||
<style>
|
||||
/* CSS Variables for Theme */
|
||||
@@ -252,6 +256,119 @@
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
/* Notification Center Button */
|
||||
.notification-center-btn {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 18px;
|
||||
}
|
||||
.notification-center-btn:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border-color: var(--accent-color);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(88,86,214,0.3);
|
||||
}
|
||||
.notification-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #dc2626;
|
||||
color: white;
|
||||
font-size: 0.7rem;
|
||||
font-weight: bold;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 10px;
|
||||
min-width: 1.2rem;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
border: 2px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.notification-center-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||
width: 520px;
|
||||
max-width: calc(100vw - 40px);
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
}
|
||||
.notification-center-dropdown.show { display: block; }
|
||||
.notification-center-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.notification-center-header h3 { margin: 0; font-size: 1.25rem; font-weight: 700; }
|
||||
.notification-center-list { padding: 1rem; }
|
||||
.notification-center-empty { color: var(--text-secondary); padding: 1rem; }
|
||||
.notification-center-item {
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
margin-bottom: 1rem;
|
||||
background: white;
|
||||
}
|
||||
.notification-center-item.dismissed { opacity: 0.7; background: #f9fafb; }
|
||||
.notification-center-item-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.notification-center-item-title .dismissed-badge {
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
margin-left: auto;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: #f3f4f6;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.notification-center-item-text { color: var(--text-secondary); font-size: 0.95rem; margin-bottom: 1rem; line-height: 1.6; }
|
||||
.notification-center-item-actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
|
||||
.notification-center-item-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: white;
|
||||
background: linear-gradient(135deg, var(--accent-color) 0%, #6d6bd4 100%);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.notification-center-item-link-secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
#sidebar {
|
||||
width: 260px;
|
||||
@@ -1211,7 +1328,20 @@
|
||||
|
||||
<div id="header-right">
|
||||
<div class="info-text">Connect with us — Watch tutorials, <a href="https://community.cyberpanel.net/" target="_blank" rel="noopener" style="color: inherit; text-decoration: underline;">Join discussions</a>, and <a href="https://platform.cyberpersons.com/" target="_blank" rel="noopener" style="color: inherit; text-decoration: underline;">get support</a>.</div>
|
||||
<div class="social-links">
|
||||
<div class="social-links" style="position: relative;">
|
||||
<button id="notification-center-btn" class="notification-center-btn" title="Notifications" onclick="toggleNotificationCenter()">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span id="notification-badge" class="notification-badge">0</span>
|
||||
</button>
|
||||
<div id="notification-center-dropdown" class="notification-center-dropdown">
|
||||
<div class="notification-center-header">
|
||||
<h3>Notifications</h3>
|
||||
<button onclick="toggleNotificationCenter()" style="background: none; border: none; cursor: pointer; font-size: 1.2rem; color: #6b7280;">×</button>
|
||||
</div>
|
||||
<div id="notification-center-list" class="notification-center-list">
|
||||
<div class="notification-center-empty">Loading notifications...</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://web.facebook.com/groups/cyberpanel" target="_blank" rel="noopener" title="Facebook">
|
||||
<i class="fab fa-facebook-f"></i>
|
||||
</a>
|
||||
@@ -1621,7 +1751,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if admin %}
|
||||
<a href="#" class="menu-item" onclick="loadFTPQuotaManagement(); return false;">
|
||||
<a href="{% url 'ftpQuotaManagementPage' %}" class="menu-item" target="_blank">
|
||||
<span>FTP Quota Management</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -2055,7 +2185,7 @@
|
||||
<script src="{% static 'databases/databases.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'dns/dns.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'mailServer/mailServer.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'ftp/ftp.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'ftp/ftp.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
|
||||
<script src="{% static 'backup/backup.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'managePHP/managePHP.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="{% static 'serverLogs/serverLogs.js' %}?v={{ CP_VERSION }}"></script>
|
||||
@@ -2300,8 +2430,88 @@
|
||||
localStorage.setItem('htaccessNotificationDismissed', 'true');
|
||||
}
|
||||
|
||||
function isNotificationDismissed(notificationKey) {
|
||||
if (notificationKey === 'backup-notification') {
|
||||
return {% if backup_notification_dismissed %}true{% else %}false{% endif %};
|
||||
}
|
||||
if (notificationKey === 'ai-scanner-notification') {
|
||||
return {% if ai_scanner_notification_dismissed %}true{% else %}false{% endif %};
|
||||
}
|
||||
if (notificationKey === 'htaccess-notification') {
|
||||
return localStorage.getItem('htaccessNotificationDismissed') === 'true';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function toggleNotificationCenter() {
|
||||
const dropdown = document.getElementById('notification-center-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.classList.toggle('show');
|
||||
if (dropdown.classList.contains('show')) {
|
||||
loadNotificationCenter();
|
||||
}
|
||||
}
|
||||
}
|
||||
function loadNotificationCenter() {
|
||||
const list = document.getElementById('notification-center-list');
|
||||
if (!list) return;
|
||||
const notifications = [
|
||||
{ id: 'backup', title: 'Backup Security', icon: 'fas fa-shield-alt',
|
||||
text: 'Looks like your websites are not secured with automatic backups.',
|
||||
link: '/backup/OneClickBackups', linkText: 'Configure now',
|
||||
learnMoreLink: 'https://cyberpanel.net/docs/backup',
|
||||
dismissed: isNotificationDismissed('backup-notification') },
|
||||
{ id: 'ai-scanner', title: 'AI Security Scanner', icon: 'fas fa-brain',
|
||||
text: 'Secure your websites with AI-powered malware detection. Advanced threat detection • Real-time scanning • Zero false positives',
|
||||
link: '/aiscanner/', linkText: 'Start AI Security Scan',
|
||||
learnMoreLink: 'https://cyberpanel.net/docs/ai-scanner',
|
||||
dismissed: isNotificationDismissed('ai-scanner-notification') },
|
||||
{ id: 'htaccess', title: '.htaccess Support', icon: 'fas fa-magic',
|
||||
text: 'Revolutionary .htaccess Support Now Live! Full .htaccess support • PHP configuration now works • Zero rule rewrites needed',
|
||||
link: 'https://cyberpanel.net/cyberpanel-htaccess-module', linkText: 'View Details',
|
||||
learnMoreLink: 'https://cyberpanel.net/cyberpanel-htaccess-module',
|
||||
dismissed: isNotificationDismissed('htaccess-notification') }
|
||||
];
|
||||
if (notifications.length === 0) {
|
||||
list.innerHTML = '<div class="notification-center-empty">No notifications available</div>';
|
||||
} else {
|
||||
list.innerHTML = notifications.map(notif => {
|
||||
let linkIcon = notif.linkText.includes('Configure') ? '<i class="fas fa-cog"></i>' :
|
||||
notif.linkText.includes('Start') ? '<i class="fas fa-rocket"></i>' :
|
||||
(notif.linkText.includes('View') || notif.linkText.includes('Details')) ? '<i class="fas fa-external-link-alt"></i>' : '<i class="fas fa-arrow-right"></i>';
|
||||
const isExternal = notif.link.startsWith('http');
|
||||
const learnExternal = notif.learnMoreLink && notif.learnMoreLink.startsWith('http');
|
||||
return `<div class="notification-center-item ${notif.dismissed ? 'dismissed' : ''}">
|
||||
<div class="notification-center-item-title">
|
||||
<i class="${notif.icon}"></i>
|
||||
<span>${notif.title}</span>
|
||||
${notif.dismissed ? '<span class="dismissed-badge">Dismissed</span>' : ''}
|
||||
</div>
|
||||
<div class="notification-center-item-text">${notif.text}</div>
|
||||
<div class="notification-center-item-actions">
|
||||
<a href="${notif.link}" class="notification-center-item-link" ${isExternal ? 'target="_blank" rel="noopener"' : ''}>${linkIcon} <span>${notif.linkText}</span></a>
|
||||
${notif.learnMoreLink ? `<a href="${notif.learnMoreLink}" class="notification-center-item-link-secondary" ${learnExternal ? 'target="_blank" rel="noopener"' : ''}><i class="fas fa-info-circle"></i> <span>Learn More</span> <i class="fas fa-arrow-right"></i></a>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
const activeCount = notifications.filter(n => !n.dismissed).length;
|
||||
const badge = document.getElementById('notification-badge');
|
||||
if (badge) {
|
||||
badge.textContent = activeCount;
|
||||
badge.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', function(event) {
|
||||
const dropdown = document.getElementById('notification-center-dropdown');
|
||||
const button = document.getElementById('notification-center-btn');
|
||||
if (dropdown && button && !dropdown.contains(event.target) && !button.contains(event.target)) {
|
||||
dropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Check all notification statuses when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadNotificationCenter();
|
||||
checkBackupStatus();
|
||||
// Show AI Scanner notification with a slight delay for better UX
|
||||
setTimeout(checkAIScannerStatus, 1000);
|
||||
@@ -2388,32 +2598,47 @@
|
||||
<!-- FTP Quota and Bandwidth Management Functions -->
|
||||
<script>
|
||||
function loadFTPQuotaManagement() {
|
||||
// Load FTP quota management interface
|
||||
$.get('{% url "getFTPQuotas" %}', function(data) {
|
||||
if (data.status === 1) {
|
||||
// Create modal or redirect to dedicated page
|
||||
window.open('/websiteFunctions/ftpQuotaManagement.html', '_blank');
|
||||
} else {
|
||||
alert('Error loading FTP quota management: ' + data.message);
|
||||
$.ajax({
|
||||
url: '{% url "getFTPQuotas" %}',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.status === 1) {
|
||||
window.open('{% url "ftpQuotaManagementPage" %}', '_blank');
|
||||
} else {
|
||||
var msg = (data && (data.error_message || data.message || data.errorMessage)) || 'Unknown error';
|
||||
alert('Error loading FTP quota management: ' + msg);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
var msg = (xhr.responseJSON && (xhr.responseJSON.error_message || xhr.responseJSON.message)) || (xhr.statusText || 'Request failed');
|
||||
alert('Error loading FTP quota management: ' + msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadBandwidthManagement() {
|
||||
// Load bandwidth management interface
|
||||
$.get('{% url "getBandwidthResetLogs" %}', function(data) {
|
||||
if (data.status === 1) {
|
||||
// Create modal or redirect to dedicated page
|
||||
window.open('/websiteFunctions/bandwidthManagement.html', '_blank');
|
||||
} else {
|
||||
alert('Error loading bandwidth management: ' + data.message);
|
||||
$.ajax({
|
||||
url: '{% url "getBandwidthResetLogs" %}',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.status === 1) {
|
||||
window.open('{% url "bandwidthManagementPage" %}', '_blank');
|
||||
} else {
|
||||
var msg = (data && (data.error_message || data.message || data.errorMessage)) || 'Unknown error';
|
||||
alert('Error loading bandwidth management: ' + msg);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
var msg = (xhr.responseJSON && (xhr.responseJSON.error_message || xhr.responseJSON.message)) || (xhr.statusText || 'Request failed');
|
||||
alert('Error loading bandwidth management: ' + msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadSecurityManagement() {
|
||||
// Load security management interface
|
||||
window.open('/websiteFunctions/securityManagement.html', '_blank');
|
||||
window.open('{% url "securityManagementPage" %}', '_blank');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -523,12 +523,17 @@ def RestartCyberPanel(request):
|
||||
|
||||
def getDashboardStats(request):
|
||||
try:
|
||||
val = request.session['userID']
|
||||
val = request.session.get('userID')
|
||||
if val is None:
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Session required'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
currentACL = ACLManager.loadedACL(val)
|
||||
admin = Administrator.objects.get(pk=val)
|
||||
|
||||
# Check if user is admin
|
||||
if currentACL['admin'] == 1:
|
||||
if currentACL.get('admin', 0) == 1:
|
||||
# Admin can see all resources
|
||||
total_users = Administrator.objects.count()
|
||||
total_sites = Websites.objects.count()
|
||||
@@ -566,7 +571,7 @@ def getDashboardStats(request):
|
||||
total_emails = EUsers.objects.filter(emailOwner__domainOwner__domain__in=website_names).count()
|
||||
|
||||
# Count FTP users associated with user's domains
|
||||
total_ftp_users = FTPUsers.objects.filter(domain__in=website_names).count()
|
||||
total_ftp_users = FTPUsers.objects.filter(domain__domain__in=website_names).count()
|
||||
else:
|
||||
total_wp_sites = 0
|
||||
total_dbs = 0
|
||||
@@ -584,18 +589,24 @@ def getDashboardStats(request):
|
||||
}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json')
|
||||
logging.writeToFile('getDashboardStats error: %s' % str(e))
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Failed to load dashboard stats'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
def getTrafficStats(request):
|
||||
try:
|
||||
val = request.session['userID']
|
||||
val = request.session.get('userID')
|
||||
if val is None:
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Session required'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
currentACL = ACLManager.loadedACL(val)
|
||||
|
||||
# Only admins should see system-wide network stats
|
||||
if not currentACL.get('admin', 0):
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json')
|
||||
|
||||
# Get network stats from /proc/net/dev (Linux)
|
||||
rx = tx = 0
|
||||
with open('/proc/net/dev', 'r') as f:
|
||||
for line in f.readlines():
|
||||
@@ -603,42 +614,49 @@ def getTrafficStats(request):
|
||||
continue
|
||||
if ':' in line:
|
||||
parts = line.split()
|
||||
rx += int(parts[1])
|
||||
tx += int(parts[9])
|
||||
data = {
|
||||
'rx_bytes': rx,
|
||||
'tx_bytes': tx,
|
||||
'status': 1
|
||||
}
|
||||
try:
|
||||
if len(parts) >= 10:
|
||||
rx += int(parts[1])
|
||||
tx += int(parts[9])
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
data = {'rx_bytes': rx, 'tx_bytes': tx, 'status': 1}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json')
|
||||
logging.writeToFile('getTrafficStats error: %s' % str(e))
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Failed to load traffic stats'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
def getDiskIOStats(request):
|
||||
try:
|
||||
val = request.session['userID']
|
||||
val = request.session.get('userID')
|
||||
if val is None:
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Session required'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
currentACL = ACLManager.loadedACL(val)
|
||||
|
||||
# Only admins should see system-wide disk I/O stats
|
||||
if not currentACL.get('admin', 0):
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json')
|
||||
|
||||
# Parse /proc/diskstats for all disks
|
||||
read_sectors = 0
|
||||
write_sectors = 0
|
||||
sector_size = 512 # Most Linux systems use 512 bytes per sector
|
||||
sector_size = 512
|
||||
with open('/proc/diskstats', 'r') as f:
|
||||
for line in f:
|
||||
parts = line.split()
|
||||
if len(parts) < 14:
|
||||
continue
|
||||
# parts[2] is device name, skip loopback/ram devices
|
||||
dev = parts[2]
|
||||
if dev.startswith('loop') or dev.startswith('ram'):
|
||||
continue
|
||||
# 6th and 10th columns: sectors read/written
|
||||
read_sectors += int(parts[5])
|
||||
write_sectors += int(parts[9])
|
||||
try:
|
||||
read_sectors += int(parts[5])
|
||||
write_sectors += int(parts[9])
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
data = {
|
||||
'read_bytes': read_sectors * sector_size,
|
||||
'write_bytes': write_sectors * sector_size,
|
||||
@@ -646,34 +664,42 @@ def getDiskIOStats(request):
|
||||
}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json')
|
||||
logging.writeToFile('getDiskIOStats error: %s' % str(e))
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Failed to load disk I/O stats'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
def getCPULoadGraph(request):
|
||||
try:
|
||||
val = request.session['userID']
|
||||
val = request.session.get('userID')
|
||||
if val is None:
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Session required'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
currentACL = ACLManager.loadedACL(val)
|
||||
|
||||
# Only admins should see system-wide CPU stats
|
||||
if not currentACL.get('admin', 0):
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': 'Admin access required', 'admin_only': True}), content_type='application/json')
|
||||
|
||||
# Parse /proc/stat for the 'cpu' line
|
||||
cpu_times = []
|
||||
with open('/proc/stat', 'r') as f:
|
||||
for line in f:
|
||||
if line.startswith('cpu '):
|
||||
parts = line.strip().split()
|
||||
# parts[1:] are user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice
|
||||
cpu_times = [float(x) for x in parts[1:]]
|
||||
try:
|
||||
cpu_times = [float(x) for x in parts[1:]]
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
break
|
||||
else:
|
||||
cpu_times = []
|
||||
data = {
|
||||
'cpu_times': cpu_times,
|
||||
'status': 1
|
||||
}
|
||||
data = {'cpu_times': cpu_times, 'status': 1}
|
||||
return HttpResponse(json.dumps(data), content_type='application/json')
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({'status': 0, 'error_message': str(e)}), content_type='application/json')
|
||||
logging.writeToFile('getCPULoadGraph error: %s' % str(e))
|
||||
return HttpResponse(
|
||||
json.dumps({'status': 0, 'error_message': 'Failed to load CPU stats'}),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
@csrf_exempt
|
||||
@require_GET
|
||||
@@ -790,6 +816,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'
|
||||
@@ -801,6 +828,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
|
||||
@@ -811,7 +841,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)
|
||||
@@ -1153,7 +1202,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:
|
||||
@@ -1248,6 +1320,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}')
|
||||
@@ -1263,7 +1383,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)}'
|
||||
|
||||
@@ -1810,49 +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
|
||||
current_time = time.time()
|
||||
active_banned_ips = []
|
||||
|
||||
for banned_ip in banned_ips:
|
||||
if banned_ip.get('expires') == 'Never' or banned_ip.get('expires', 0) > current_time:
|
||||
banned_ip['active'] = True
|
||||
if banned_ip.get('expires') != 'Never':
|
||||
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip['expires']))
|
||||
else:
|
||||
banned_ip['expires'] = 'Never'
|
||||
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
|
||||
else:
|
||||
banned_ip['active'] = False
|
||||
banned_ip['expires'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('expires', current_time)))
|
||||
banned_ip['banned_on'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_ip.get('banned_on', current_time)))
|
||||
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(banned_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)
|
||||
@@ -1934,19 +1934,53 @@ class FirewallManager:
|
||||
with open(banned_ips_file, 'w') as f:
|
||||
json.dump(banned_ips, f, indent=2)
|
||||
|
||||
# Apply firewall rule to block the IP
|
||||
# Apply firewall rule to block the IP using firewalld
|
||||
try:
|
||||
# Add iptables rule to block the IP
|
||||
if '/' in ip:
|
||||
# CIDR notation
|
||||
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'], check=True)
|
||||
else:
|
||||
# Single IP
|
||||
subprocess.run(['iptables', '-A', 'INPUT', '-s', ip, '-j', 'DROP'], check=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)
|
||||
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}')
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'Failed to add iptables rule for {ip}: {str(e)}')
|
||||
# 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'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:
|
||||
# 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'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)
|
||||
@@ -1996,19 +2030,40 @@ class FirewallManager:
|
||||
with open(banned_ips_file, 'w') as f:
|
||||
json.dump(banned_ips, f, indent=2)
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -2,10 +2,25 @@
|
||||
* Created by usman on 9/5/17.
|
||||
*/
|
||||
|
||||
// Helper function to get CSRF token cookie
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
/* Java script code to ADD Firewall Rules */
|
||||
|
||||
app.controller('firewallController', function ($scope, $http) {
|
||||
app.controller('firewallController', function ($scope, $http, $timeout) {
|
||||
|
||||
$scope.rulesLoading = true;
|
||||
$scope.actionFailed = true;
|
||||
@@ -18,7 +33,10 @@ app.controller('firewallController', function ($scope, $http) {
|
||||
|
||||
// Banned IPs variables
|
||||
$scope.activeTab = 'rules';
|
||||
$scope.bannedIPs = [];
|
||||
$scope.bannedIPs = []; // Initialize as empty array
|
||||
|
||||
// Initialize banned IPs array - start as null so template shows empty state
|
||||
// Will be set to array after API call
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
@@ -30,7 +48,144 @@ app.controller('firewallController', function ($scope, $http) {
|
||||
firewallStatus();
|
||||
|
||||
populateCurrentRecords();
|
||||
populateBannedIPs();
|
||||
|
||||
// Load banned IPs immediately when controller initializes
|
||||
console.log('=== FIREWALL CONTROLLER INITIALIZING ===');
|
||||
console.log('Initializing firewall controller, loading banned IPs...');
|
||||
|
||||
// Define populateBannedIPs function first, then call it
|
||||
// This ensures the function is available when setTimeout executes
|
||||
function populateBannedIPs() {
|
||||
console.log('=== populateBannedIPs() START ===');
|
||||
console.log('Current scope.bannedIPs:', $scope.bannedIPs);
|
||||
console.log('Current activeTab:', $scope.activeTab);
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
var url = "/firewall/getBannedIPs";
|
||||
var csrfToken = getCookie('csrftoken');
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Making request to:', url);
|
||||
console.log('CSRF Token:', csrfToken ? 'Found (' + csrfToken.substring(0, 10) + '...)' : 'MISSING!');
|
||||
|
||||
$http.post(url, {}, config).then(
|
||||
function(response) {
|
||||
console.log('=== API RESPONSE RECEIVED ===');
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response data:', JSON.stringify(response.data, null, 2));
|
||||
|
||||
$scope.bannedIPsLoading = false;
|
||||
// Reset error flags
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
if (response.data && response.data.status === 1) {
|
||||
var bannedIPsArray = response.data.bannedIPs || [];
|
||||
console.log('Raw bannedIPs from API:', bannedIPsArray);
|
||||
console.log('Banned IPs count:', bannedIPsArray.length);
|
||||
console.log('Is array?', Array.isArray(bannedIPsArray));
|
||||
|
||||
// Ensure it's an array
|
||||
if (!Array.isArray(bannedIPsArray)) {
|
||||
console.error('ERROR: bannedIPs is not an array:', typeof bannedIPsArray);
|
||||
bannedIPsArray = [];
|
||||
}
|
||||
|
||||
// Assign to scope - Angular $http callbacks already run within $apply
|
||||
console.log('Assigning to scope.bannedIPs...');
|
||||
$scope.bannedIPs = bannedIPsArray;
|
||||
console.log('After assignment - scope.bannedIPs:', $scope.bannedIPs);
|
||||
console.log('After assignment - scope.bannedIPs.length:', $scope.bannedIPs ? $scope.bannedIPs.length : 'undefined');
|
||||
console.log('After assignment - activeTab:', $scope.activeTab);
|
||||
|
||||
// No need to call $apply - $http callbacks run within $apply automatically
|
||||
console.log('View should update automatically (Angular $http handles $apply)');
|
||||
|
||||
console.log('=== populateBannedIPs() SUCCESS ===');
|
||||
} else {
|
||||
console.error('ERROR: API returned status !== 1');
|
||||
console.error('Response data:', response.data);
|
||||
$scope.bannedIPs = [];
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = (response.data && response.data.error_message) || 'Unknown error';
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
console.error('=== HTTP ERROR ===');
|
||||
console.error('Error object:', error);
|
||||
console.error('Error status:', error.status);
|
||||
console.error('Error data:', error.data);
|
||||
console.error('Error statusText:', error.statusText);
|
||||
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
$scope.bannedIPs = [];
|
||||
|
||||
try {
|
||||
if (!$scope.$$phase && !$scope.$root.$$phase) {
|
||||
$scope.$apply();
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Error in $apply (error handler):', e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Expose to scope for template access
|
||||
$scope.populateBannedIPs = function() {
|
||||
console.log('$scope.populateBannedIPs() called from template');
|
||||
populateBannedIPs();
|
||||
};
|
||||
|
||||
// Load banned IPs on page load - use $timeout for Angular compatibility
|
||||
// Wrap in try-catch to ensure it executes even if there are other errors
|
||||
try {
|
||||
$timeout(function() {
|
||||
try {
|
||||
console.log('=== Calling populateBannedIPs from $timeout on page load ===');
|
||||
populateBannedIPs();
|
||||
} catch(e) {
|
||||
console.error('Error in populateBannedIPs from timeout:', e);
|
||||
}
|
||||
}, 500);
|
||||
} catch(e) {
|
||||
console.error('Error setting up timeout for populateBannedIPs:', e);
|
||||
}
|
||||
|
||||
// Also load when switching to banned tab - use deep watch for immediate trigger
|
||||
try {
|
||||
$scope.$watch('activeTab', function(newVal, oldVal) {
|
||||
console.log('=== activeTab WATCH TRIGGERED ===');
|
||||
console.log('activeTab changed from', oldVal, 'to', newVal);
|
||||
if (newVal === 'banned') {
|
||||
console.log('Switched to banned IPs tab, calling populateBannedIPs...');
|
||||
// Call immediately
|
||||
try {
|
||||
if (typeof populateBannedIPs === 'function') {
|
||||
console.log('Calling populateBannedIPs from $watch...');
|
||||
populateBannedIPs();
|
||||
} else if (typeof $scope.populateBannedIPs === 'function') {
|
||||
console.log('Calling $scope.populateBannedIPs from $watch...');
|
||||
$scope.populateBannedIPs();
|
||||
} else {
|
||||
console.error('ERROR: populateBannedIPs is not available!');
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Error calling populateBannedIPs from watch:', e);
|
||||
}
|
||||
}
|
||||
}, true); // Use deep watch (true parameter)
|
||||
} catch(e) {
|
||||
console.error('Error setting up $watch for activeTab:', e);
|
||||
}
|
||||
|
||||
$scope.addRule = function () {
|
||||
|
||||
@@ -154,7 +309,7 @@ app.controller('firewallController', function ($scope, $http) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
$scope.deleteRule = function (id, proto, port, ruleIP) {
|
||||
|
||||
@@ -513,11 +668,280 @@ app.controller('firewallController', function ($scope, $http) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Banned IPs Functions
|
||||
$scope.addBannedIP = function() {
|
||||
if (!$scope.banIP || !$scope.banReason) {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = "Please fill in all required fields";
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = {
|
||||
ip: $scope.banIP,
|
||||
reason: $scope.banReason,
|
||||
duration: $scope.banDuration
|
||||
};
|
||||
|
||||
var url = "/firewall/addBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
// Reset error flags
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
$scope.banIP = '';
|
||||
$scope.banReason = '';
|
||||
$scope.banDuration = '24h';
|
||||
console.log('IP banned successfully, refreshing list...');
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message || 'Unknown error';
|
||||
console.error('Failed to ban IP:', response.data);
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
console.error('Error banning IP:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeBannedIP = function(id, ip) {
|
||||
if (!confirm('Are you sure you want to unban IP address ' + ip + '?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = { id: id };
|
||||
|
||||
var url = "/firewall/removeBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteBannedIP = function(id, ip) {
|
||||
if (!confirm('Are you sure you want to permanently delete the record for IP address ' + ip + '? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = { id: id };
|
||||
|
||||
var url = "/firewall/deleteBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
};
|
||||
|
||||
// Export/Import Firewall Rules Functions
|
||||
$scope.exportRules = function () {
|
||||
$scope.rulesLoading = false;
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = true;
|
||||
|
||||
url = "/firewall/exportFirewallRules";
|
||||
|
||||
var data = {};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(exportSuccess, exportError);
|
||||
|
||||
function exportSuccess(response) {
|
||||
$scope.rulesLoading = true;
|
||||
|
||||
// Check if response is JSON (error) or file download
|
||||
if (typeof response.data === 'string' && response.data.includes('{')) {
|
||||
try {
|
||||
var errorData = JSON.parse(response.data);
|
||||
if (errorData.exportStatus === 0) {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = errorData.error_message;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// If not JSON, assume it's the file content
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, it's a successful file download
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = false;
|
||||
}
|
||||
|
||||
function exportError(response) {
|
||||
$scope.rulesLoading = true;
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.importRules = function () {
|
||||
// Create file input element
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.style.display = 'none';
|
||||
|
||||
input.onchange = function(event) {
|
||||
var file = event.target.files[0];
|
||||
if (file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
var importData = JSON.parse(e.target.result);
|
||||
|
||||
// Validate file format
|
||||
if (!importData.rules || !Array.isArray(importData.rules)) {
|
||||
$scope.$apply(function() {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Invalid import file format. Please select a valid firewall rules export file.";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload file to server
|
||||
uploadImportFile(file);
|
||||
} catch (error) {
|
||||
$scope.$apply(function() {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Invalid JSON file. Please select a valid firewall rules export file.";
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
|
||||
function uploadImportFile(file) {
|
||||
$scope.rulesLoading = false;
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = true;
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('import_file', file);
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken'),
|
||||
'Content-Type': undefined
|
||||
},
|
||||
transformRequest: angular.identity
|
||||
};
|
||||
|
||||
$http.post("/firewall/importFirewallRules", formData, config).then(importSuccess, importError);
|
||||
|
||||
function importSuccess(response) {
|
||||
$scope.rulesLoading = true;
|
||||
|
||||
if (response.data.importStatus === 1) {
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = false;
|
||||
|
||||
// Refresh rules list
|
||||
populateCurrentRecords();
|
||||
|
||||
// Show import summary
|
||||
var summary = `Import completed successfully!\n` +
|
||||
`Imported: ${response.data.imported_count} rules\n` +
|
||||
`Skipped: ${response.data.skipped_count} rules\n` +
|
||||
`Errors: ${response.data.error_count} rules`;
|
||||
|
||||
if (response.data.errors && response.data.errors.length > 0) {
|
||||
summary += `\n\nErrors:\n${response.data.errors.join('\n')}`;
|
||||
}
|
||||
|
||||
alert(summary);
|
||||
} else {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = response.data.error_message;
|
||||
}
|
||||
}
|
||||
|
||||
function importError(response) {
|
||||
$scope.rulesLoading = true;
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/* Java script code to ADD Firewall Rules */
|
||||
|
||||
/* Java script code to Secure SSH */
|
||||
@@ -2413,287 +2837,4 @@ app.controller('litespeed_ent_conf', function ($scope, $http, $timeout, $window)
|
||||
}
|
||||
}
|
||||
|
||||
// Banned IPs Functions
|
||||
function populateBannedIPs() {
|
||||
$scope.bannedIPsLoading = true;
|
||||
var url = "/firewall/getBannedIPs";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, {}, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPs = response.data.bannedIPs || [];
|
||||
} else {
|
||||
$scope.bannedIPs = [];
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.addBannedIP = function() {
|
||||
if (!$scope.banIP || !$scope.banReason) {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = "Please fill in all required fields";
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = {
|
||||
ip: $scope.banIP,
|
||||
reason: $scope.banReason,
|
||||
duration: $scope.banDuration
|
||||
};
|
||||
|
||||
var url = "/firewall/addBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
$scope.banIP = '';
|
||||
$scope.banReason = '';
|
||||
$scope.banDuration = '24h';
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.removeBannedIP = function(id, ip) {
|
||||
if (!confirm('Are you sure you want to unban IP address ' + ip + '?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = { id: id };
|
||||
|
||||
var url = "/firewall/removeBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteBannedIP = function(id, ip) {
|
||||
if (!confirm('Are you sure you want to permanently delete the record for IP address ' + ip + '? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.bannedIPsLoading = true;
|
||||
$scope.bannedIPActionFailed = true;
|
||||
$scope.bannedIPActionSuccess = true;
|
||||
$scope.bannedIPCouldNotConnect = true;
|
||||
|
||||
var data = { id: id };
|
||||
|
||||
var url = "/firewall/deleteBannedIP";
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
if (response.data.status === 1) {
|
||||
$scope.bannedIPActionSuccess = false;
|
||||
populateBannedIPs(); // Refresh the list
|
||||
} else {
|
||||
$scope.bannedIPActionFailed = false;
|
||||
$scope.bannedIPErrorMessage = response.data.error_message;
|
||||
}
|
||||
}, function(error) {
|
||||
$scope.bannedIPsLoading = false;
|
||||
$scope.bannedIPCouldNotConnect = false;
|
||||
});
|
||||
};
|
||||
|
||||
// Export/Import Firewall Rules Functions
|
||||
$scope.exportRules = function () {
|
||||
$scope.rulesLoading = false;
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = true;
|
||||
|
||||
url = "/firewall/exportFirewallRules";
|
||||
|
||||
var data = {};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(exportSuccess, exportError);
|
||||
|
||||
function exportSuccess(response) {
|
||||
$scope.rulesLoading = true;
|
||||
|
||||
// Check if response is JSON (error) or file download
|
||||
if (typeof response.data === 'string' && response.data.includes('{')) {
|
||||
try {
|
||||
var errorData = JSON.parse(response.data);
|
||||
if (errorData.exportStatus === 0) {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = errorData.error_message;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
// If not JSON, assume it's the file content
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, it's a successful file download
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = false;
|
||||
}
|
||||
|
||||
function exportError(response) {
|
||||
$scope.rulesLoading = true;
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||
}
|
||||
};
|
||||
|
||||
$scope.importRules = function () {
|
||||
// Create file input element
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.style.display = 'none';
|
||||
|
||||
input.onchange = function(event) {
|
||||
var file = event.target.files[0];
|
||||
if (file) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
var importData = JSON.parse(e.target.result);
|
||||
|
||||
// Validate file format
|
||||
if (!importData.rules || !Array.isArray(importData.rules)) {
|
||||
$scope.$apply(function() {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Invalid import file format. Please select a valid firewall rules export file.";
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Upload file to server
|
||||
uploadImportFile(file);
|
||||
} catch (error) {
|
||||
$scope.$apply(function() {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Invalid JSON file. Please select a valid firewall rules export file.";
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
|
||||
function uploadImportFile(file) {
|
||||
$scope.rulesLoading = false;
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = true;
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append('import_file', file);
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken'),
|
||||
'Content-Type': undefined
|
||||
},
|
||||
transformRequest: angular.identity
|
||||
};
|
||||
|
||||
$http.post("/firewall/importFirewallRules", formData, config).then(importSuccess, importError);
|
||||
|
||||
function importSuccess(response) {
|
||||
$scope.rulesLoading = true;
|
||||
|
||||
if (response.data.importStatus === 1) {
|
||||
$scope.actionFailed = true;
|
||||
$scope.actionSuccess = false;
|
||||
|
||||
// Refresh rules list
|
||||
populateCurrentRecords();
|
||||
|
||||
// Show import summary
|
||||
var summary = `Import completed successfully!\n` +
|
||||
`Imported: ${response.data.imported_count} rules\n` +
|
||||
`Skipped: ${response.data.skipped_count} rules\n` +
|
||||
`Errors: ${response.data.error_count} rules`;
|
||||
|
||||
if (response.data.errors && response.data.errors.length > 0) {
|
||||
summary += `\n\nErrors:\n${response.data.errors.join('\n')}`;
|
||||
}
|
||||
|
||||
alert(summary);
|
||||
} else {
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = response.data.error_message;
|
||||
}
|
||||
}
|
||||
|
||||
function importError(response) {
|
||||
$scope.rulesLoading = true;
|
||||
$scope.actionFailed = false;
|
||||
$scope.actionSuccess = true;
|
||||
$scope.errorMessage = "Could not connect to server. Please refresh this page.";
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -895,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>
|
||||
@@ -1102,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>
|
||||
@@ -1121,16 +1121,16 @@
|
||||
{$ bannedIP.ip $}
|
||||
</td>
|
||||
<td class="reason">
|
||||
<span class="reason-text">{$ bannedIP.reason $}</span>
|
||||
<span class="reason-text ng-binding">{$ bannedIP.reason $}</span>
|
||||
</td>
|
||||
<td class="banned-date">
|
||||
<td class="banned-date ng-binding">
|
||||
<i class="fas fa-calendar"></i>
|
||||
{$ bannedIP.banned_on | date:'MMM dd, yyyy HH:mm' $}
|
||||
{$ bannedIP.banned_on $}
|
||||
</td>
|
||||
<td class="expires-date">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span ng-if="bannedIP.expires === 'Never'">{% trans "Never" %}</span>
|
||||
<span ng-if="bannedIP.expires !== 'Never'">{$ bannedIP.expires | date:'MMM dd, yyyy HH:mm' $}</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}"
|
||||
@@ -1160,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>
|
||||
@@ -1169,17 +1169,17 @@
|
||||
|
||||
<!-- 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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -141,22 +141,24 @@ class modSec:
|
||||
writeToFile.writelines("ModSecurity Installed.[200]\n")
|
||||
writeToFile.close()
|
||||
|
||||
# Check if custom OLS binary is installed - if so, replace with compatible ModSecurity
|
||||
custom_ols_marker = "/usr/local/lsws/modules/cyberpanel_ols.so"
|
||||
if os.path.exists(custom_ols_marker):
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("Custom OLS detected, installing compatible ModSecurity...\n")
|
||||
writeToFile.close()
|
||||
# Always download and install compatible ModSecurity binary to prevent LMDB dependency crashes
|
||||
# This fixes the "undefined symbol: mdb_env_create" error that causes OpenLiteSpeed to crash
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("Downloading compatible ModSecurity binary to prevent LMDB dependency issues...\n")
|
||||
writeToFile.close()
|
||||
|
||||
platform = modSec.detectPlatform()
|
||||
if modSec.downloadCompatibleModSec(platform):
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("Compatible ModSecurity installed successfully.\n")
|
||||
writeToFile.close()
|
||||
else:
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("WARNING: Could not install compatible ModSecurity. May experience crashes.\n")
|
||||
writeToFile.close()
|
||||
platform = modSec.detectPlatform()
|
||||
if modSec.downloadCompatibleModSec(platform):
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("Compatible ModSecurity binary installed successfully.\n")
|
||||
writeToFile.close()
|
||||
logging.CyberCPLogFileWriter.writeToFile("Compatible ModSecurity binary installed to prevent LMDB dependency crashes [installModSec]")
|
||||
else:
|
||||
writeToFile = open(modSec.installLogPath, 'a')
|
||||
writeToFile.writelines("WARNING: Could not install compatible ModSecurity binary. Using package-manager binary instead.\n")
|
||||
writeToFile.writelines("WARNING: If you experience crashes (SIGSEGV signal 11), manually download compatible binary.\n")
|
||||
writeToFile.close()
|
||||
logging.CyberCPLogFileWriter.writeToFile("WARNING: Could not install compatible ModSecurity binary - may experience LMDB dependency crashes [installModSec]")
|
||||
|
||||
return 1
|
||||
except BaseException as msg:
|
||||
|
||||
@@ -8,27 +8,64 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
|
||||
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.ftpDetails = true;
|
||||
$scope.canNotCreateFTP = true;
|
||||
$scope.successfullyCreatedFTP = true;
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.generatedPasswordView = true;
|
||||
|
||||
$(document).ready(function () {
|
||||
$( ".ftpDetails" ).hide();
|
||||
$( ".ftpPasswordView" ).hide();
|
||||
$('.create-ftp-acct-select').select2();
|
||||
$(".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 {
|
||||
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();
|
||||
if ($scope.ftpDomain && $scope.ftpDomain !== "") $(".ftpDetails").show();
|
||||
else $(".ftpDetails").hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('.create-ftp-acct-select').on('select2:select', function (e) {
|
||||
var data = e.params.data;
|
||||
$scope.ftpDomain = data.text;
|
||||
$( ".ftpDetails" ).show();
|
||||
|
||||
});
|
||||
|
||||
$scope.ftpLoading = true;
|
||||
$scope.showFTPDetails = function () {
|
||||
if ($scope.ftpDomain && $scope.ftpDomain !== "") {
|
||||
$(".ftpDetails").show();
|
||||
$scope.ftpDetails = false;
|
||||
} else {
|
||||
$(".ftpDetails").hide();
|
||||
$scope.ftpDetails = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.createFTPAccount = function () {
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.ftpLoading = true;
|
||||
$scope.ftpDetails = false;
|
||||
$scope.canNotCreate = true;
|
||||
$scope.successfullyCreated = true;
|
||||
$scope.canNotCreateFTP = true;
|
||||
$scope.successfullyCreatedFTP = true;
|
||||
$scope.couldNotConnect = true;
|
||||
|
||||
var ftpDomain = $scope.ftpDomain;
|
||||
@@ -36,19 +73,18 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
var ftpPassword = $scope.ftpPassword;
|
||||
var path = $scope.ftpPath;
|
||||
|
||||
if (typeof path === 'undefined') {
|
||||
path = "";
|
||||
}
|
||||
|
||||
var url = "/ftp/submitFTPCreation";
|
||||
|
||||
if (typeof path === 'undefined' || path == null) path = "";
|
||||
else path = String(path).trim();
|
||||
|
||||
var data = {
|
||||
ftpDomain: ftpDomain,
|
||||
ftpUserName: ftpUserName,
|
||||
passwordByPass: ftpPassword,
|
||||
path: path,
|
||||
enableCustomQuota: $scope.enableCustomQuota || false,
|
||||
customQuotaSize: $scope.customQuotaSize || 0,
|
||||
};
|
||||
var url = "/ftp/submitFTPCreation";
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
@@ -63,57 +99,60 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
||||
|
||||
|
||||
if (response.data.creatFTPStatus === 1) {
|
||||
$scope.ftpLoading = true;
|
||||
new PNotify({
|
||||
title: 'Success!',
|
||||
text: 'FTP account successfully created.',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.successfullyCreatedFTP = false;
|
||||
$scope.canNotCreateFTP = true;
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.createdFTPUsername = ftpDomain + "_" + ftpUserName;
|
||||
if (typeof PNotify !== 'undefined') {
|
||||
new PNotify({ title: 'Success!', text: 'FTP account successfully created.', type: 'success' });
|
||||
}
|
||||
} else {
|
||||
$scope.ftpLoading = true;
|
||||
new PNotify({
|
||||
title: 'Operation Failed!',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.canNotCreateFTP = false;
|
||||
$scope.successfullyCreatedFTP = true;
|
||||
$scope.couldNotConnect = true;
|
||||
$scope.errorMessage = response.data.error_message;
|
||||
if (typeof PNotify !== 'undefined') {
|
||||
new PNotify({ title: 'Operation Failed!', text: response.data.error_message, type: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
function cantLoadInitialDatas(response) {
|
||||
|
||||
$scope.ftpLoading = true;
|
||||
new PNotify({
|
||||
title: 'Operation Failed!',
|
||||
text: 'Could not connect to server, please refresh this page',
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
|
||||
$scope.ftpLoading = false;
|
||||
$scope.couldNotConnect = false;
|
||||
$scope.canNotCreateFTP = true;
|
||||
$scope.successfullyCreatedFTP = true;
|
||||
if (typeof PNotify !== 'undefined') {
|
||||
new PNotify({ title: 'Operation Failed!', text: 'Could not connect to server, please refresh this page', type: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
$scope.hideFewDetails = function () {
|
||||
|
||||
$scope.successfullyCreated = true;
|
||||
|
||||
|
||||
$scope.successfullyCreatedFTP = true;
|
||||
$scope.canNotCreateFTP = true;
|
||||
$scope.couldNotConnect = true;
|
||||
};
|
||||
|
||||
///
|
||||
|
||||
$scope.generatePassword = function () {
|
||||
$( ".ftpPasswordView" ).show();
|
||||
$(".ftpPasswordView").show();
|
||||
$scope.generatedPasswordView = false;
|
||||
$scope.ftpPassword = randomPassword(16);
|
||||
};
|
||||
|
||||
$scope.usePassword = function () {
|
||||
$(".ftpPasswordView" ).hide();
|
||||
$(".ftpPasswordView").hide();
|
||||
$scope.generatedPasswordView = true;
|
||||
};
|
||||
|
||||
$scope.toggleCustomQuota = function () {
|
||||
if (!$scope.enableCustomQuota) $scope.customQuotaSize = 0;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<type>Utility</type>
|
||||
<version>1.0.0</version>
|
||||
<description>A comprehensive test plugin for CyberPanel with enable/disable functionality, test button, popup messages, and inline integration</description>
|
||||
<author>CyberPanel Development Team</author>
|
||||
<author>usmannasir</author>
|
||||
<website>https://github.com/cyberpanel/testPlugin</website>
|
||||
<license>MIT</license>
|
||||
<dependencies>
|
||||
|
||||
@@ -112,22 +112,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 +203,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']
|
||||
|
||||
Reference in New Issue
Block a user