Merge origin/v2.5.5-dev: Plugin Store sidebar, ?view=store, master3395 default, clone comment

This commit is contained in:
KraoESPfan1n
2026-02-16 23:51:13 +01:00
298 changed files with 32518 additions and 27021 deletions

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
import os
import time
from .views import VERSION, BUILD
def version_context(request):
@@ -49,4 +52,54 @@ def notification_preferences_context(request):
return {
'backup_notification_dismissed': False,
'ai_scanner_notification_dismissed': False
}
def firewall_static_context(request):
"""Expose a cache-busting token for firewall static assets (bumps when firewall.js changes)."""
try:
from django.conf import settings
base = settings.BASE_DIR
# Check both app static and repo static so version updates when either is updated
paths = [
os.path.join(base, 'firewall', 'static', 'firewall', 'firewall.js'),
os.path.join(base, 'static', 'firewall', 'firewall.js'),
os.path.join(base, 'public', 'static', 'firewall', 'firewall.js'),
]
version = 0
for p in paths:
try:
version = max(version, int(os.path.getmtime(p)))
except (OSError, TypeError):
pass
if version <= 0:
version = int(time.time())
except (OSError, AttributeError):
version = int(time.time())
return {
'FIREWALL_STATIC_VERSION': version
}
def dns_static_context(request):
"""Cache-busting for DNS static assets (bumps when dns.js changes). Avoids stale JS/layout."""
try:
from django.conf import settings
base = settings.BASE_DIR
paths = [
os.path.join(base, 'dns', 'static', 'dns', 'dns.js'),
os.path.join(base, 'static', 'dns', 'dns.js'),
os.path.join(base, 'public', 'static', 'dns', 'dns.js'),
]
version = 0
for p in paths:
try:
version = max(version, int(os.path.getmtime(p)))
except (OSError, TypeError):
pass
if version <= 0:
version = int(time.time())
except (OSError, AttributeError):
version = int(time.time())
return {
'DNS_STATIC_VERSION': version
}

View File

@@ -0,0 +1,589 @@
/* CyberPanel Mobile Responsive & Readability Fixes */
/* This file ensures all pages are mobile-friendly with proper font sizes and readable text */
/* Base font size and mobile-first approach */
html {
font-size: 16px; /* Base font size for better readability */
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
font-size: 16px;
line-height: 1.6;
color: #2f3640; /* Dark text for better readability on white backgrounds */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* Ensure all text is readable with proper contrast */
* {
color: inherit;
}
/* Override any light text that might be hard to read */
.text-muted, .text-secondary, .text-light {
color: #2f3640 !important; /* Dark text for better readability on white backgrounds */
}
/* Fix small font sizes that are hard to read */
small, .small, .text-small {
font-size: 14px !important; /* Minimum readable size */
}
/* Table improvements for mobile */
.table {
font-size: 16px !important; /* Larger table text */
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.table th, .table td {
padding: 12px 8px !important; /* More padding for touch targets */
border: 1px solid #e8e9ff;
text-align: left;
vertical-align: middle;
font-size: 14px !important;
line-height: 1.4;
}
.table th {
background-color: #f8f9fa;
font-weight: 600;
color: #2f3640 !important;
font-size: 15px !important;
}
/* Button improvements for mobile */
.btn {
font-size: 16px !important;
padding: 12px 20px !important;
border-radius: 8px;
min-height: 44px; /* Minimum touch target size */
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-sm {
font-size: 14px !important;
padding: 8px 16px !important;
min-height: 36px;
}
.btn-xs {
font-size: 13px !important;
padding: 6px 12px !important;
min-height: 32px;
}
/* Form elements */
.form-control, input, textarea, select {
font-size: 16px !important; /* Prevents zoom on iOS */
padding: 12px 16px !important;
border: 2px solid #e8e9ff;
border-radius: 8px;
min-height: 44px;
line-height: 1.4;
color: #2f3640 !important;
background-color: #ffffff;
}
.form-control:focus, input:focus, textarea:focus, select:focus {
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
outline: none;
}
/* Labels and form text */
label, .control-label {
font-size: 16px !important;
font-weight: 600;
color: #2f3640 !important;
margin-bottom: 8px;
display: block;
}
/* Headings with proper hierarchy */
h1 {
font-size: 2.5rem !important; /* 40px */
font-weight: 700;
color: #1e293b !important;
line-height: 1.2;
margin-bottom: 1rem;
}
h2 {
font-size: 2rem !important; /* 32px */
font-weight: 600;
color: #1e293b !important;
line-height: 1.3;
margin-bottom: 0.875rem;
}
h3 {
font-size: 1.5rem !important; /* 24px */
font-weight: 600;
color: #2f3640 !important;
line-height: 1.4;
margin-bottom: 0.75rem;
}
h4 {
font-size: 1.25rem !important; /* 20px */
font-weight: 600;
color: #2f3640 !important;
line-height: 1.4;
margin-bottom: 0.5rem;
}
h5 {
font-size: 1.125rem !important; /* 18px */
font-weight: 600;
color: #2f3640 !important;
line-height: 1.4;
margin-bottom: 0.5rem;
}
h6 {
font-size: 1rem !important; /* 16px */
font-weight: 600;
color: #2f3640 !important;
line-height: 1.4;
margin-bottom: 0.5rem;
}
/* Paragraph and body text */
p {
font-size: 16px !important;
line-height: 1.6;
color: #2f3640 !important;
margin-bottom: 1rem;
}
/* Sidebar improvements */
#page-sidebar {
font-size: 16px !important;
}
#page-sidebar ul li a {
font-size: 16px !important;
padding: 12px 20px !important;
color: #2f3640 !important;
min-height: 44px;
display: flex;
align-items: center;
text-decoration: none;
}
#page-sidebar ul li a:hover {
background-color: #f8f9fa;
color: #5856d6 !important;
}
/* Content area improvements */
.content-box, .panel, .card {
font-size: 16px !important;
color: #2f3640 !important;
background-color: #ffffff;
border: 1px solid #e8e9ff;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
/* Modal improvements */
.modal-content {
font-size: 16px !important;
color: #2f3640 !important;
}
.modal-title {
font-size: 1.5rem !important;
font-weight: 600;
color: #1e293b !important;
}
/* Alert and notification improvements */
.alert {
font-size: 16px !important;
padding: 16px 20px !important;
border-radius: 8px;
margin-bottom: 20px;
}
.alert-success {
background-color: #f0fdf4;
border-color: #bbf7d0;
color: #166534 !important;
}
.alert-danger {
background-color: #fef2f2;
border-color: #fecaca;
color: #dc2626 !important;
}
.alert-warning {
background-color: #fffbeb;
border-color: #fed7aa;
color: #d97706 !important;
}
.alert-info {
background-color: #eff6ff;
border-color: #bfdbfe;
color: #2563eb !important;
}
/* Navigation improvements */
.navbar-nav .nav-link {
font-size: 16px !important;
padding: 12px 16px !important;
color: #2f3640 !important;
}
/* Breadcrumb improvements */
.breadcrumb {
font-size: 16px !important;
background-color: transparent;
padding: 0;
margin-bottom: 20px;
}
.breadcrumb-item {
color: #64748b !important;
}
.breadcrumb-item.active {
color: #2f3640 !important;
}
/* Mobile-first responsive breakpoints */
@media (max-width: 1200px) {
.container, .container-fluid {
padding-left: 15px;
padding-right: 15px;
}
.table-responsive {
border: none;
margin-bottom: 20px;
}
}
@media (max-width: 992px) {
/* Stack columns on tablets */
.col-md-3, .col-md-4, .col-md-6, .col-md-8, .col-md-9 {
flex: 0 0 100%;
max-width: 100%;
margin-bottom: 20px;
}
/* Adjust sidebar for tablets */
#page-sidebar {
width: 100%;
position: static;
height: auto;
}
/* Make tables horizontally scrollable */
.table-responsive {
overflow-x: auto;
}
.table {
min-width: 600px;
}
}
@media (max-width: 768px) {
/* Mobile-specific adjustments */
html {
font-size: 14px;
}
body {
font-size: 14px;
padding: 0;
}
.container, .container-fluid {
padding-left: 10px;
padding-right: 10px;
}
/* Stack all columns on mobile */
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-6, .col-sm-8, .col-sm-9, .col-sm-12,
.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-6, .col-md-8, .col-md-9, .col-md-12 {
flex: 0 0 100%;
max-width: 100%;
margin-bottom: 15px;
}
/* Adjust headings for mobile */
h1 {
font-size: 2rem !important; /* 32px */
}
h2 {
font-size: 1.75rem !important; /* 28px */
}
h3 {
font-size: 1.5rem !important; /* 24px */
}
h4 {
font-size: 1.25rem !important; /* 20px */
}
/* Button adjustments for mobile */
.btn {
font-size: 16px !important;
padding: 14px 20px !important;
width: 100%;
margin-bottom: 10px;
}
.btn-group .btn {
width: auto;
margin-bottom: 0;
}
/* Form adjustments for mobile */
.form-control, input, textarea, select {
font-size: 16px !important; /* Prevents zoom on iOS */
padding: 14px 16px !important;
width: 100%;
}
/* Table adjustments for mobile */
.table {
font-size: 14px !important;
}
.table th, .table td {
padding: 8px 6px !important;
font-size: 13px !important;
}
/* Hide less important columns on mobile */
.table .d-none-mobile {
display: none !important;
}
/* Modal adjustments for mobile */
.modal-dialog {
margin: 10px;
width: calc(100% - 20px);
}
.modal-content {
padding: 20px 15px;
}
/* Content box adjustments */
.content-box, .panel, .card {
padding: 15px;
margin-bottom: 15px;
}
/* Sidebar adjustments for mobile */
#page-sidebar {
position: fixed;
top: 0;
left: -100%;
width: 280px;
height: 100vh;
z-index: 1000;
transition: left 0.3s ease;
background-color: #ffffff;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
#page-sidebar.show {
left: 0;
}
/* Main content adjustments when sidebar is open */
#main-content {
transition: margin-left 0.3s ease;
}
#main-content.sidebar-open {
margin-left: 280px;
}
/* Mobile menu toggle */
.mobile-menu-toggle {
display: block;
position: fixed;
top: 20px;
left: 20px;
z-index: 1001;
background-color: #5856d6;
color: white;
border: none;
padding: 12px;
border-radius: 8px;
font-size: 18px;
cursor: pointer;
}
}
@media (max-width: 576px) {
/* Extra small devices */
html {
font-size: 14px;
}
.container, .container-fluid {
padding-left: 8px;
padding-right: 8px;
}
/* Even smaller buttons and forms for very small screens */
.btn {
font-size: 14px !important;
padding: 12px 16px !important;
}
.form-control, input, textarea, select {
font-size: 16px !important; /* Still 16px to prevent zoom */
padding: 12px 14px !important;
}
/* Compact table for very small screens */
.table th, .table td {
padding: 6px 4px !important;
font-size: 12px !important;
}
/* Hide even more columns on very small screens */
.table .d-none-mobile-sm {
display: none !important;
}
}
/* Utility classes for mobile */
.d-none-mobile {
display: block;
}
.d-none-mobile-sm {
display: block;
}
@media (max-width: 768px) {
.d-none-mobile {
display: none !important;
}
}
@media (max-width: 576px) {
.d-none-mobile-sm {
display: none !important;
}
}
/* Ensure all text has proper contrast */
.text-white {
color: #ffffff !important;
}
.text-dark {
color: #2f3640 !important;
}
.text-muted {
color: #2f3640 !important; /* Dark text for better readability */
}
/* Fix any light text on light backgrounds */
.bg-light .text-muted,
.bg-white .text-muted,
.panel .text-muted {
color: #2f3640 !important; /* Dark text for better readability */
}
/* Ensure proper spacing for touch targets */
a, button, input, select, textarea {
min-height: 44px;
min-width: 44px;
}
/* Additional text readability improvements */
/* Fix any green text issues */
.ng-binding {
color: #2f3640 !important; /* Normal dark text instead of green */
}
/* Ensure all text elements have proper contrast */
span, div, p, label, td, th {
color: inherit;
}
/* Fix specific text color issues */
.text-success {
color: #059669 !important; /* Darker green for better readability */
}
.text-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.text-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
/* Override Bootstrap's muted text */
.text-muted {
color: #2f3640 !important; /* Dark text instead of grey */
}
/* Fix any remaining light text on light backgrounds */
.bg-white .text-light,
.bg-light .text-light,
.panel .text-light,
.card .text-light {
color: #2f3640 !important;
}
/* Fix for small clickable elements */
.glyph-icon, .icon {
min-width: 44px;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}
/* Loading and spinner improvements */
.spinner, .loading {
font-size: 16px !important;
color: #5856d6 !important;
}
/* Print styles */
@media print {
body {
font-size: 12pt;
color: #000000 !important;
background: #ffffff !important;
}
.table th, .table td {
font-size: 10pt !important;
color: #000000 !important;
}
.btn, .alert, .modal {
display: none !important;
}
}

View File

@@ -0,0 +1,265 @@
/* CyberPanel Readability & Design Fixes */
/* This file fixes the core design issues with grey text and color inconsistencies */
/* Override CSS Variables for Better Text Contrast */
:root {
/* Ensure all text uses proper dark colors for readability */
--text-primary: #2f3640;
--text-secondary: #2f3640; /* Changed from grey to dark for better readability */
--text-heading: #1e293b;
}
/* Dark theme also uses proper contrast */
[data-theme="dark"] {
--text-primary: #e4e4e7;
--text-secondary: #e4e4e7; /* Changed from grey to light for better readability */
--text-heading: #f3f4f6;
}
/* Fix Green Text Issues */
/* Override Angular binding colors that might be green */
.ng-binding {
color: var(--text-secondary) !important;
}
/* Specific fix for uptime display */
#sidebar .server-info .info-line span,
#sidebar .server-info .info-line .ng-binding,
.server-info .ng-binding {
color: var(--text-secondary) !important;
}
/* Fix Grey Text on White Background */
/* Override all muted and secondary text classes */
.text-muted,
.text-secondary,
.text-light,
small,
.small,
.text-small {
color: var(--text-secondary) !important;
}
/* Fix specific Bootstrap classes */
.text-muted {
color: #2f3640 !important; /* Dark text for better readability */
}
/* Fix text on white/light backgrounds */
.bg-white .text-muted,
.bg-light .text-muted,
.panel .text-muted,
.card .text-muted,
.content-box .text-muted {
color: #2f3640 !important;
}
/* Fix menu items and navigation */
#sidebar .menu-item,
#sidebar .menu-item span,
#sidebar .menu-item i,
.sidebar .menu-item,
.sidebar .menu-item span,
.sidebar .menu-item i {
color: var(--text-secondary) !important;
}
#sidebar .menu-item:hover,
.sidebar .menu-item:hover {
color: var(--accent-color) !important;
}
#sidebar .menu-item.active,
.sidebar .menu-item.active {
color: white !important;
}
/* Fix server info and details */
.server-info,
.server-info *,
.server-details,
.server-details *,
.info-line,
.info-line span,
.info-line strong,
.tagline,
.brand {
color: inherit !important;
}
/* Fix form elements */
label,
.control-label,
.form-label {
color: var(--text-primary) !important;
font-weight: 600;
}
/* Fix table text */
.table th,
.table td {
color: var(--text-primary) !important;
}
.table th {
font-weight: 600;
}
/* Fix alert text */
.alert {
color: var(--text-primary) !important;
}
.alert-success {
color: #059669 !important; /* Darker green for better readability */
}
.alert-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.alert-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
.alert-danger {
color: #dc2626 !important; /* Darker red for better readability */
}
/* Fix breadcrumb text */
.breadcrumb-item {
color: var(--text-secondary) !important;
}
.breadcrumb-item.active {
color: var(--text-primary) !important;
}
/* Fix modal text */
.modal-content {
color: var(--text-primary) !important;
}
.modal-title {
color: var(--text-heading) !important;
}
/* Fix button text */
.btn {
color: inherit;
}
/* Fix any remaining light text issues */
.bg-light .text-light,
.bg-white .text-light,
.panel .text-light,
.card .text-light {
color: #2f3640 !important;
}
/* Ensure proper contrast for all text elements */
span, div, p, label, td, th, a, li {
color: inherit;
}
/* Fix specific color classes */
.text-success {
color: #059669 !important; /* Darker green for better readability */
}
.text-info {
color: #0284c7 !important; /* Darker blue for better readability */
}
.text-warning {
color: #d97706 !important; /* Darker orange for better readability */
}
.text-danger {
color: #dc2626 !important; /* Darker red for better readability */
}
/* Fix any Angular-specific styling */
[ng-controller] {
color: inherit;
}
[ng-show],
[ng-hide],
[ng-if] {
color: inherit;
}
/* Ensure all content areas have proper text color */
.content-box,
.panel,
.card,
.main-content,
.page-content {
color: var(--text-primary) !important;
}
/* Fix any remaining Bootstrap classes */
.text-dark {
color: #2f3640 !important;
}
.text-body {
color: var(--text-primary) !important;
}
/* Mobile-specific fixes */
@media (max-width: 768px) {
/* Ensure mobile text is also readable */
body,
.container,
.container-fluid {
color: var(--text-primary) !important;
}
/* Fix mobile menu text */
.mobile-menu .menu-item,
.mobile-menu .menu-item span {
color: var(--text-secondary) !important;
}
}
/* Print styles */
@media print {
body,
.content-box,
.panel,
.card {
color: #000000 !important;
background: #ffffff !important;
}
.text-muted,
.text-secondary {
color: #000000 !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--text-primary: #000000;
--text-secondary: #000000;
--text-heading: #000000;
}
[data-theme="dark"] {
--text-primary: #ffffff;
--text-secondary: #ffffff;
--text-heading: #ffffff;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

View File

@@ -10,7 +10,7 @@ function getCookie(name) {
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
var cookie = (cookies[i] || '').replace(/^\s+|\s+$/g, '');
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
@@ -39,6 +39,77 @@ function randomPassword(length) {
window.app = angular.module('CyberCP', []);
var app = window.app; // Local reference for this file
// MUST be first: register dashboard controller before any other setup (avoids ctrlreg when CDN/Tracking Prevention blocks scripts)
app.controller('dashboardStatsController', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
$scope.cpuUsage = 0; $scope.ramUsage = 0; $scope.diskUsage = 0; $scope.cpuCores = 0;
$scope.ramTotalMB = 0; $scope.diskTotalGB = 0; $scope.diskFreeGB = 0;
$scope.totalUsers = 0; $scope.totalSites = 0; $scope.totalWPSites = 0;
$scope.totalDBs = 0; $scope.totalEmails = 0; $scope.totalFTPUsers = 0;
$scope.topProcesses = []; $scope.sshLogins = []; $scope.sshLogs = [];
$scope.loadingTopProcesses = true; $scope.loadingSSHLogins = true; $scope.loadingSSHLogs = true;
$scope.blockedIPs = {}; $scope.blockingIP = null; $scope.securityAlerts = [];
var opts = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
try {
$http.get('/base/getSystemStatus', opts).then(function (r) {
if (r && r.data && r.data.status === 1) {
$scope.cpuUsage = r.data.cpuUsage || 0; $scope.ramUsage = r.data.ramUsage || 0;
$scope.diskUsage = r.data.diskUsage || 0; $scope.cpuCores = r.data.cpuCores || 0;
$scope.ramTotalMB = r.data.ramTotalMB || 0; $scope.diskTotalGB = r.data.diskTotalGB || 0;
$scope.diskFreeGB = r.data.diskFreeGB || 0;
}
});
$http.get('/base/getDashboardStats', opts).then(function (r) {
if (r && r.data && r.data.status === 1) {
$scope.totalUsers = r.data.total_users || 0; $scope.totalSites = r.data.total_sites || 0;
$scope.totalWPSites = r.data.total_wp_sites || 0; $scope.totalDBs = r.data.total_dbs || 0;
$scope.totalEmails = r.data.total_emails || 0; $scope.totalFTPUsers = r.data.total_ftp_users || 0;
}
});
$http.get('/base/getRecentSSHLogins', opts).then(function (r) {
$scope.loadingSSHLogins = false;
$scope.sshLogins = (r && r.data && r.data.logins) ? r.data.logins : [];
}, function () { $scope.loadingSSHLogins = false; $scope.sshLogins = []; });
$http.get('/base/getRecentSSHLogs', opts).then(function (r) {
$scope.loadingSSHLogs = false;
$scope.sshLogs = (r && r.data && r.data.logs) ? r.data.logs : [];
}, function () { $scope.loadingSSHLogs = false; $scope.sshLogs = []; });
$http.get('/base/getTopProcesses', opts).then(function (r) {
$scope.loadingTopProcesses = false;
$scope.topProcesses = (r && r.data && r.data.status === 1 && r.data.processes) ? r.data.processes : [];
}, function () { $scope.loadingTopProcesses = false; $scope.topProcesses = []; });
if (typeof $timeout === 'function') { $timeout(function() { /* refresh */ }, 10000); }
} catch (e) { /* ignore */ }
}]);
// Overview CPU/RAM/Disk cards use systemStatusInfo register early so data loads even if later script fails
app.controller('systemStatusInfo', ['$scope', '$http', '$timeout', function ($scope, $http, $timeout) {
$scope.uptimeLoaded = false;
$scope.uptime = 'Loading...';
$scope.cpuUsage = 0; $scope.ramUsage = 0; $scope.diskUsage = 0;
$scope.cpuCores = 0; $scope.ramTotalMB = 0; $scope.diskTotalGB = 0; $scope.diskFreeGB = 0;
$scope.getSystemStatus = function() { fetchStatus(); };
function fetchStatus() {
try {
var csrf = (typeof getCookie === 'function') ? getCookie('csrftoken') : '';
$http.get('/base/getSystemStatus', { headers: { 'X-CSRFToken': csrf } }).then(function (r) {
if (r && r.data && r.data.status === 1) {
$scope.cpuUsage = r.data.cpuUsage != null ? r.data.cpuUsage : 0;
$scope.ramUsage = r.data.ramUsage != null ? r.data.ramUsage : 0;
$scope.diskUsage = r.data.diskUsage != null ? r.data.diskUsage : 0;
$scope.cpuCores = r.data.cpuCores != null ? r.data.cpuCores : 0;
$scope.ramTotalMB = r.data.ramTotalMB != null ? r.data.ramTotalMB : 0;
$scope.diskTotalGB = r.data.diskTotalGB != null ? r.data.diskTotalGB : 0;
$scope.diskFreeGB = r.data.diskFreeGB != null ? r.data.diskFreeGB : 0;
$scope.uptime = r.data.uptime || 'N/A';
}
$scope.uptimeLoaded = true;
}, function() { $scope.uptime = 'Unavailable'; $scope.uptimeLoaded = true; });
if (typeof $timeout === 'function') { $timeout(fetchStatus, 60000); }
} catch (e) { $scope.uptimeLoaded = true; }
}
fetchStatus();
}]);
var globalScope;
function GlobalRespSuccess(response) {
@@ -122,9 +193,17 @@ app.controller('systemStatusInfo', function ($scope, $http, $timeout) {
$scope.uptimeLoaded = false;
$scope.uptime = 'Loading...';
// Defaults so template never shows undefined (avoids raw {$ cpuUsage $} when API is slow or fails)
$scope.cpuUsage = 0;
$scope.ramUsage = 0;
$scope.diskUsage = 0;
$scope.cpuCores = 0;
$scope.ramTotalMB = 0;
$scope.diskTotalGB = 0;
$scope.diskFreeGB = 0;
getStuff();
$scope.getSystemStatus = function() {
getStuff();
};
@@ -138,17 +217,15 @@ app.controller('systemStatusInfo', function ($scope, $http, $timeout) {
function ListInitialData(response) {
$scope.cpuUsage = response.data.cpuUsage;
$scope.ramUsage = response.data.ramUsage;
$scope.diskUsage = response.data.diskUsage;
// Total system information
$scope.cpuCores = response.data.cpuCores;
$scope.ramTotalMB = response.data.ramTotalMB;
$scope.diskTotalGB = response.data.diskTotalGB;
$scope.diskFreeGB = response.data.diskFreeGB;
// Get uptime if available
$scope.cpuUsage = response.data.cpuUsage != null ? response.data.cpuUsage : 0;
$scope.ramUsage = response.data.ramUsage != null ? response.data.ramUsage : 0;
$scope.diskUsage = response.data.diskUsage != null ? response.data.diskUsage : 0;
$scope.cpuCores = response.data.cpuCores != null ? response.data.cpuCores : 0;
$scope.ramTotalMB = response.data.ramTotalMB != null ? response.data.ramTotalMB : 0;
$scope.diskTotalGB = response.data.diskTotalGB != null ? response.data.diskTotalGB : 0;
$scope.diskFreeGB = response.data.diskFreeGB != null ? response.data.diskFreeGB : 0;
if (response.data.uptime) {
$scope.uptime = response.data.uptime;
$scope.uptimeLoaded = true;
@@ -162,6 +239,9 @@ app.controller('systemStatusInfo', function ($scope, $http, $timeout) {
function cantLoadInitialData(response) {
$scope.uptime = 'Unavailable';
$scope.uptimeLoaded = true;
$scope.cpuUsage = 0;
$scope.ramUsage = 0;
$scope.diskUsage = 0;
}
$timeout(getStuff, 60000); // Update every minute
@@ -273,11 +353,11 @@ app.controller('adminController', function ($scope, $http, $timeout) {
}
if (!Boolean(response.data.deleteZone)) {
$('.addDeleteRecords').hide();
$('.deleteZone').hide();
}
if (!Boolean(response.data.addDeleteRecords)) {
$('.deleteDatabase').hide();
$('.addDeleteRecords').hide();
}
// Email Management
@@ -557,15 +637,18 @@ app.controller('homePageStatus', function ($scope, $http, $timeout) {
////////////
function increment() {
$('.box').hide();
var boxes = document.querySelectorAll ? document.querySelectorAll('.box') : [];
for (var i = 0; i < boxes.length; i++) boxes[i].style.display = 'none';
setTimeout(function () {
$('.box').show();
for (var j = 0; j < boxes.length; j++) boxes[j].style.display = '';
}, 100);
}
increment();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', increment);
} else {
increment();
}
////////////
@@ -579,6 +662,7 @@ app.controller('versionManagment', function ($scope, $http, $timeout) {
$scope.updateFinish = true;
$scope.couldNotConnect = true;
var upgradeStatusTimer = null;
$scope.upgrade = function () {
@@ -660,7 +744,8 @@ app.controller('versionManagment', function ($scope, $http, $timeout) {
if (response.data.upgradeStatus === 1) {
if (response.data.finished === 1) {
$timeout.cancel();
if (upgradeStatusTimer) $timeout.cancel(upgradeStatusTimer);
upgradeStatusTimer = null;
$scope.upgradelogBox = false;
$scope.upgradeLog = response.data.upgradeLog;
$scope.upgradeLoading = true;
@@ -672,7 +757,7 @@ app.controller('versionManagment', function ($scope, $http, $timeout) {
} else {
$scope.upgradelogBox = false;
$scope.upgradeLog = response.data.upgradeLog;
timeout(getUpgradeStatus, 2000);
upgradeStatusTimer = $timeout(getUpgradeStatus, 2000);
}
}
@@ -900,7 +985,8 @@ app.controller('OnboardingCP', function ($scope, $http, $timeout, $window) {
});
app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
// Single implementation registered under both names for compatibility (some templates/caches use newDashboardStat)
var dashboardStatsControllerFn = function ($scope, $http, $timeout) {
console.log('dashboardStatsController initialized');
// Card values
@@ -920,7 +1006,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.errorTopProcesses = '';
$scope.refreshTopProcesses = function() {
$scope.loadingTopProcesses = true;
$http.get('/base/getTopProcesses').then(function (response) {
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getTopProcesses', h).then(function (response) {
$scope.loadingTopProcesses = false;
if (response.data && response.data.status === 1 && response.data.processes) {
$scope.topProcesses = response.data.processes;
@@ -939,7 +1026,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.errorSSHLogins = '';
$scope.refreshSSHLogins = function() {
$scope.loadingSSHLogins = true;
$http.get('/base/getRecentSSHLogins').then(function (response) {
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getRecentSSHLogins', h).then(function (response) {
$scope.loadingSSHLogins = false;
if (response.data && response.data.logins) {
$scope.sshLogins = response.data.logins;
@@ -967,7 +1055,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.loadingSecurityAnalysis = false;
$scope.refreshSSHLogs = function() {
$scope.loadingSSHLogs = true;
$http.get('/base/getRecentSSHLogs').then(function (response) {
var h = { headers: { 'X-CSRFToken': (typeof getCookie === 'function') ? getCookie('csrftoken') : '' } };
$http.get('/base/getRecentSSHLogs', h).then(function (response) {
$scope.loadingSSHLogs = false;
if (response.data && response.data.logs) {
$scope.sshLogs = response.data.logs;
@@ -1073,19 +1162,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
return; // Already processing this IP
}
// Check if already blocked
if ($scope.blockedIPs && $scope.blockedIPs[ipAddress]) {
console.log('IP already blocked:', ipAddress);
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Info',
text: `IP address ${ipAddress} is already banned`,
type: 'info',
delay: 3000
});
}
return;
}
// Do not early-return when IP is already in blockedIPs: still call the API so the
// backend can close any active connections from this IP (already-banned path).
// Set blocking flag to prevent duplicate requests
$scope.blockingIP = ipAddress;
@@ -1130,19 +1208,12 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
console.log('Parsed responseData from string:', responseData);
} catch (e) {
console.error('Failed to parse response as JSON:', e);
console.error('Raw response string:', responseData);
// Try to extract error from string
if (responseData.includes('error')) {
if (typeof PNotify !== 'undefined') {
new PNotify({
title: 'Error',
text: 'Failed to block IP address: ' + responseData,
type: 'error',
delay: 5000
});
}
return;
var errorMsg = responseData && responseData.length ? responseData : 'Failed to block IP address';
if (typeof PNotify !== 'undefined') {
new PNotify({ title: 'Error', text: errorMsg, type: 'error', delay: 5000 });
}
$scope.blockingIP = null;
return;
}
}
@@ -1159,11 +1230,12 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
$scope.blockedIPs[ipAddress] = true;
// Show success notification
// Show success notification (use server message when present, e.g. already-banned + connections closed)
if (typeof PNotify !== 'undefined') {
var successText = (responseData.message && responseData.message.length) ? responseData.message : `IP address ${ipAddress} has been permanently banned and added to the firewall. You can manage it in the Firewall > Banned IPs section.`;
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.`,
text: successText,
type: 'success',
delay: 5000
});
@@ -1217,28 +1289,20 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.lastErrorTime = Date.now();
var errorMessage = 'Failed to block IP address';
if (err.data) {
var errData = err.data;
if (typeof errData === 'string') {
try {
errData = JSON.parse(errData);
} catch (e) {
errorMessage = errData || errorMessage;
var errData = err.data;
if (typeof errData === 'string') {
try {
errData = JSON.parse(errData);
} catch (e) {
if (errData && errData.length) {
errorMessage = errData.length > 200 ? errData.substring(0, 200) + '...' : errData;
}
}
if (errData && typeof errData === 'object') {
if (errData.error_message) {
errorMessage = errData.error_message;
} else if (errData.error) {
errorMessage = errData.error;
} else if (errData.message) {
errorMessage = errData.message;
}
}
} else if (err.statusText) {
errorMessage = err.statusText;
}
if (errData && typeof errData === 'object') {
errorMessage = errData.error_message || errData.error || errData.message || errorMessage;
} else if (err.status) {
errorMessage = `HTTP ${err.status}: ${err.statusText || 'Unknown error'}`;
errorMessage = 'HTTP ' + err.status + ': ' + (errorMessage);
}
console.error('Final error message:', errorMessage);
@@ -1287,14 +1351,9 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
return; // Already processing
}
if ($scope.blockedIPs[ipAddress]) {
new PNotify({
title: 'Info',
text: `IP address ${ipAddress} is already banned`,
type: 'info',
delay: 3000
});
return;
// Still call API when already in blockedIPs so backend can close active connections
if (!$scope.blockedIPs) {
$scope.blockedIPs = {};
}
$scope.blockingIP = ipAddress;
@@ -1380,14 +1439,9 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
return; // Already processing
}
if ($scope.blockedIPs[ipAddress]) {
new PNotify({
title: 'Info',
text: `IP address ${ipAddress} is already banned`,
type: 'info',
delay: 3000
});
return;
// Still call API when already in blockedIPs so backend can close active connections
if (!$scope.blockedIPs) {
$scope.blockedIPs = {};
}
$scope.blockingIP = ipAddress;
@@ -1474,6 +1528,12 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
var pollInterval = 2000; // ms
var maxPoints = 30;
// Expose so switchTab can create charts on first tab click if they weren't created at load
window.cyberPanelSetupChartsIfNeeded = function() {
if (window.trafficChart && window.diskIOChart && window.cpuChart) return;
try { setupCharts(); } catch (e) { console.error('cyberPanelSetupChartsIfNeeded:', e); }
};
function pollDashboardStats() {
console.log('[dashboardStatsController] pollDashboardStats() called');
console.log('[dashboardStatsController] Fetching dashboard stats from /base/getDashboardStats');
@@ -1532,8 +1592,8 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
function pollTraffic() {
console.log('pollTraffic called');
$http.get('/base/getTrafficStats').then(function(response) {
if (!response || !response.data) return;
if (response.data.admin_only) {
// Hide chart for non-admin users
$scope.hideSystemCharts = true;
@@ -1581,13 +1641,16 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
lastRx = rx; lastTx = tx;
} else {
console.log('pollTraffic error or no data:', response);
console.warn('pollTraffic: no data or status', response.data);
}
}).catch(function(err) {
console.warn('pollTraffic failed:', err);
});
}
function pollDiskIO() {
$http.get('/base/getDiskIOStats').then(function(response) {
if (!response || !response.data) return;
if (response.data.admin_only) {
// Hide chart for non-admin users
$scope.hideSystemCharts = true;
@@ -1626,11 +1689,14 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
lastDiskRead = read; lastDiskWrite = write;
}
}).catch(function(err) {
console.warn('pollDiskIO failed:', err);
});
}
function pollCPU() {
$http.get('/base/getCPULoadGraph').then(function(response) {
if (!response || !response.data) return;
if (response.data.admin_only) {
// Hide chart for non-admin users
$scope.hideSystemCharts = true;
@@ -1669,13 +1735,34 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
}
lastCPUTimes = cpuTimes;
}
}).catch(function(err) {
console.warn('pollCPU failed:', err);
});
}
function setupCharts() {
console.log('setupCharts called, initializing charts...');
var trafficCtx = document.getElementById('trafficChart').getContext('2d');
trafficChart = new Chart(trafficCtx, {
function setupCharts(retryCount) {
retryCount = retryCount || 0;
if (typeof Chart === 'undefined') {
if (retryCount < 3) {
$timeout(function() { setupCharts(retryCount + 1); }, 400);
}
return;
}
var trafficEl = document.getElementById('trafficChart');
if (!trafficEl) {
if (retryCount < 5) {
$timeout(function() { setupCharts(retryCount + 1); }, 300);
}
return;
}
try {
var trafficCtx = trafficEl.getContext('2d');
} catch (e) {
console.error('trafficChart getContext failed:', e);
return;
}
try {
trafficChart = new Chart(trafficCtx, {
type: 'line',
data: {
labels: [],
@@ -1767,7 +1854,9 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
console.log('trafficChart resized and updated after setup.');
}
}, 500);
var diskCtx = document.getElementById('diskIOChart').getContext('2d');
var diskEl = document.getElementById('diskIOChart');
if (!diskEl) { console.warn('diskIOChart canvas not found'); return; }
var diskCtx = diskEl.getContext('2d');
diskIOChart = new Chart(diskCtx, {
type: 'line',
data: {
@@ -1852,7 +1941,10 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
layout: { padding: { top: 10, bottom: 10, left: 10, right: 10 } }
}
});
var cpuCtx = document.getElementById('cpuChart').getContext('2d');
window.diskIOChart = diskIOChart;
var cpuEl = document.getElementById('cpuChart');
if (!cpuEl) { console.warn('cpuChart canvas not found'); return; }
var cpuCtx = cpuEl.getContext('2d');
cpuChart = new Chart(cpuCtx, {
type: 'line',
data: {
@@ -1925,6 +2017,10 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
layout: { padding: { top: 10, bottom: 10, left: 10, right: 10 } }
}
});
window.cpuChart = cpuChart;
} catch (e) {
console.error('setupCharts error:', e);
}
// Redraw charts on tab shown
$("a[data-toggle='tab']").on('shown.bs.tab', function (e) {
@@ -1957,19 +2053,20 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.refreshSSHLogs();
$timeout(function() {
// Check if user is admin before setting up charts
// Always create charts so Traffic/Disk IO/CPU tabs have something to show; admin check only affects hideSystemCharts
setupCharts();
$http.get('/base/getAdminStatus').then(function(response) {
if (response.data && response.data.admin === 1) {
setupCharts();
if (response.data && (response.data.admin === 1 || response.data.admin === true)) {
$scope.hideSystemCharts = false;
} else {
$scope.hideSystemCharts = true;
}
}).catch(function() {
// If error, assume non-admin and hide charts
}).catch(function(err) {
console.warn('getAdminStatus failed:', err);
$scope.hideSystemCharts = true;
});
// Start polling for all stats
// Start polling for all stats (data feeds charts)
function pollAll() {
pollDashboardStats();
pollTraffic();
@@ -1979,7 +2076,7 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$timeout(pollAll, pollInterval);
}
pollAll();
}, 500);
}, 800);
// SSH User Activity Modal
$scope.showSSHActivityModal = false;
@@ -2425,4 +2522,6 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
$scope.closeSSHActivityModal();
}
};
});
};
app.controller('dashboardStatsController', dashboardStatsControllerFn);
app.controller('newDashboardStat', dashboardStatsControllerFn);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@
<link rel="icon" type="image/png" href="{% static 'baseTemplate/assets/finalBase/favicon.png' %}">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'filemanager/images/fonts/css/font-awesome.min.css' %}">
<link rel="stylesheet" href="{% static 'filemanager/css/fileManager.css' %}">
@@ -52,7 +52,7 @@
</div-->
<ul class="nav mr-10">
<li class="nav-item">
<a onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
<a id="uploadTriggerBtn" onclick="return false;" ng-click="showUploadBox()" class="nav-link point-events" href="#"><i class="fa fa-upload" aria-hidden="true"></i> {% trans "Upload" %}</a>
</li>
<li class="nav-item">
<a onclick="return false;" ng-click="showCreateFileModal()" class="nav-link point-events" href="#"><i class="fa fa-plus-square" aria-hidden="true"></i> {% trans "New File" %}</a>
@@ -608,7 +608,7 @@
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<!-- HTML Editor Include -->

View File

@@ -876,6 +876,29 @@
</table>
</div>
<!-- SSH Logins Pagination -->
<div ng-if="!loadingSSHLogins && sshLogins.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="sshLoginsPerPage" ng-change="sshLoginsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ (sshLoginsPage - 1) * sshLoginsPerPage + 1 $}-{$ (sshLoginsPage * sshLoginsPerPage > sshLoginsTotal ? sshLoginsTotal : sshLoginsPage * sshLoginsPerPage) $} of {$ sshLoginsTotal $}</span>
<button ng-click="sshLoginsGoToPage(sshLoginsPage - 1)" ng-disabled="sshLoginsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<button ng-click="sshLoginsGoToPage(sshLoginsPage + 1)" ng-disabled="sshLoginsPage >= sshLoginsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
<!-- Dummy data for demonstration -->
<table class="activity-table records-table" ng-if="loadingSSHLogins">
<thead>
@@ -1065,6 +1088,29 @@
</tr>
</tbody>
</table>
<!-- SSH Logs Pagination -->
<div ng-if="!loadingSSHLogs && sshLogs.length > 0" class="pagination-controls" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 16px; padding: 12px 0; border-top: 1px solid #e8e9ff;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="color: #64748b; font-size: 13px;">Show</span>
<select ng-model="sshLogsPerPage" ng-change="sshLogsChangePerPage()" style="padding: 6px 10px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 13px; background: white;">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span style="color: #64748b; font-size: 13px;">per page</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #64748b; font-size: 13px;">{$ (sshLogsPage - 1) * sshLogsPerPage + 1 $}-{$ (sshLogsPage * sshLogsPerPage > sshLogsTotal ? sshLogsTotal : sshLogsPage * sshLogsPerPage) $} of {$ sshLogsTotal $}</span>
<button ng-click="sshLogsGoToPage(sshLogsPage - 1)" ng-disabled="sshLogsPage <= 1" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Previous">
<i class="fas fa-chevron-left"></i>
</button>
<button ng-click="sshLogsGoToPage(sshLogsPage + 1)" ng-disabled="sshLogsPage >= sshLogsTotalPages" style="padding: 6px 12px; border: 1px solid #e2e8f0; border-radius: 6px; background: white; cursor: pointer; font-size: 13px;" title="Next">
<i class="fas fa-chevron-right"></i>
</button>
</div>
</div>
</div>
<!-- Top Process Tab -->
@@ -1311,11 +1357,25 @@
// Add active class to clicked tab
tabButton.classList.add('active');
// Trigger chart resize if switching to chart tabs
// Chart tabs: ensure charts exist (lazy init on first click), then resize/update
if (tabId === 'traffic' || tabId === 'diskio' || tabId === 'cpu-usage') {
if (typeof window.cyberPanelSetupChartsIfNeeded === 'function') {
window.cyberPanelSetupChartsIfNeeded();
}
setTimeout(() => {
var ch;
if (tabId === 'traffic' && (ch = window.trafficChart) && typeof ch.resize === 'function') {
ch.resize();
if (typeof ch.update === 'function') ch.update();
} else if (tabId === 'diskio' && (ch = window.diskIOChart) && typeof ch.resize === 'function') {
ch.resize();
if (typeof ch.update === 'function') ch.update();
} else if (tabId === 'cpu-usage' && (ch = window.cpuChart) && typeof ch.resize === 'function') {
ch.resize();
if (typeof ch.update === 'function') ch.update();
}
window.dispatchEvent(new Event('resize'));
}, 100);
}, 200);
}
}
@@ -1334,56 +1394,38 @@
}
$.ajax({
url: '/base/blockIPAddress',
url: '/firewall/addBannedIP',
type: 'POST',
contentType: 'application/json',
headers: {
'X-CSRFToken': getCookie('csrftoken')
},
data: JSON.stringify({
'ip_address': ipAddress,
'reason': 'Security alert detected from dashboard'
'ip': ipAddress,
'reason': 'Brute force attack detected from SSH Security Analysis',
'duration': 'permanent'
}),
success: function(data) {
// Handle both success and error responses
if (data.status === 1) {
showNotification('success', data.message || 'IP address blocked successfully');
if (data && data.status === 1) {
showNotification('success', data.message || 'IP address blocked successfully. Manage in Firewall > Banned IPs.');
// Refresh the page to update the blocked IPs list
setTimeout(() => {
location.reload();
}, 1000);
setTimeout(function() { 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';
var errorMsg = (data && (data.error_message || data.error || 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);
console.error('Ban IP error:', xhr.status, xhr.responseText);
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);
}
}
var data = xhr.responseJSON;
if (!data && xhr.responseText) {
try { data = JSON.parse(xhr.responseText); } catch(e) {}
}
if (data && (data.error_message || data.error || data.message)) {
errorMsg = data.error_message || data.error || data.message;
}
showNotification('error', errorMsg);
},
complete: function() {

View File

@@ -1,6 +1,6 @@
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% with CP_VERSION="2.4.4.1" %}
{% with CP_VERSION=CYBERPANEL_FULL_VERSION|default:"2.5.5.dev" %}
<!DOCTYPE html>
<html lang="en" ng-app="CyberCP">
<head>
@@ -26,29 +26,25 @@
<!-- Readability Fixes CSS -->
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/readability-fixes.css' %}?v={{ CP_VERSION }}">
<!-- Core Scripts (data-cfasync=false prevents Cloudflare Rocket Loader from breaking load order) -->
<!-- Core Scripts: Angular + system-status FIRST so dashboard works even when CDN/Tracking Prevention blocks jQuery/Bootstrap -->
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'baseTemplate/custom-js/system-status.js' %}?v={{ CP_VERSION }}&dashboard=3" 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 }}" data-cfasync="false"></script>
<script src="{% static 'baseTemplate/custom-js/chart.umd.min.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">
<!-- Chart.js -->
<script src="{% static 'baseTemplate/custom-js/chart.umd.min.js' %}?v={{ CP_VERSION }}"></script>
<!-- PNotify (data-cfasync=false ensures it loads before controllers that use it) -->
<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 }}" data-cfasync="false"></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>
<!-- Select2 (required by FTP create account; local copy avoids CDN Tracking Prevention blocking) -->
<link rel="stylesheet" href="{% static 'baseTemplate/vendor/select2/select2.min.css' %}?v={{ CP_VERSION }}">
<script src="{% static 'baseTemplate/vendor/select2/select2.full.min.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<!-- Modern Design System -->
<style>
@@ -297,38 +293,51 @@
align-items: center;
justify-content: center;
}
/* Notification dropdown: always light panel with dark text so readable in both light and dark mode */
.notification-center-dropdown {
position: absolute;
top: calc(100% + 10px);
right: 0;
background: white;
border: 1px solid var(--border-color);
background: #ffffff;
border: 1px solid #e5e7eb;
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;
flex-direction: column;
z-index: 10000;
overflow: hidden;
min-height: 0;
}
.notification-center-dropdown.show { display: block; }
.notification-center-header {
.notification-center-dropdown.show { display: flex; }
.notification-center-dropdown .notification-center-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border-color);
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.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-dropdown .notification-center-header h3 { margin: 0; font-size: 1.25rem; font-weight: 700; color: #111827; }
.notification-center-list {
padding: 1rem;
flex: 1 1 0;
min-height: 0;
max-height: 480px;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
#notification-center-dropdown .notification-center-empty { color: #4b5563; padding: 1rem; }
.notification-center-item {
padding: 1.5rem;
border: 1px solid var(--border-color);
border: 1px solid #e5e7eb;
border-radius: 12px;
margin-bottom: 1rem;
background: white;
background: #ffffff;
overflow: visible;
}
.notification-center-item.dismissed { opacity: 0.7; background: #f9fafb; }
.notification-center-item-title {
@@ -337,36 +346,69 @@
display: flex;
align-items: center;
gap: 0.75rem;
color: #111827;
}
.notification-center-item-title .dismissed-badge {
font-size: 0.75rem;
color: #6b7280;
color: #374151;
margin-left: auto;
padding: 0.25rem 0.5rem;
background: #f3f4f6;
background: #e5e7eb;
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-dropdown .notification-center-item-text { color: #4b5563; font-size: 0.95rem; margin-bottom: 1rem; line-height: 1.6; }
#notification-center-dropdown .notification-center-item-actions {
display: flex;
flex-direction: column;
gap: 1.25rem;
align-items: flex-start;
margin-top: 0.75rem;
width: 100%;
}
.notification-center-item-link-secondary {
#notification-center-dropdown .notification-center-item-actions .notification-center-item-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--accent-color);
justify-content: center;
gap: 0.4rem;
color: #ffffff !important;
background: linear-gradient(135deg, #4f46e5 0%, #4338ca 100%) !important;
text-decoration: none;
font-size: 0.9rem;
font-size: 0.875rem;
font-weight: 600;
padding: 0.5rem 1.25rem;
border-radius: 8px;
white-space: nowrap;
flex-shrink: 0;
min-width: 160px;
width: auto;
min-height: 2.25rem;
line-height: 1.3;
box-sizing: border-box;
}
#notification-center-dropdown .notification-center-item-actions .notification-center-item-link:hover {
filter: brightness(1.1);
color: #ffffff !important;
}
#notification-center-dropdown .notification-center-item-actions .notification-center-item-link-secondary {
display: inline-flex;
align-items: center;
gap: 0.4rem;
color: #4338ca !important;
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
padding: 0.4rem 0.75rem;
white-space: nowrap;
flex-shrink: 0;
min-width: 120px;
width: auto;
min-height: 1.75rem;
line-height: 1.3;
box-sizing: border-box;
}
#notification-center-dropdown .notification-center-item-actions .notification-center-item-link-secondary:hover {
text-decoration: underline;
color: #3730a3 !important;
}
/* Sidebar */
@@ -1547,9 +1589,6 @@
<a href="{% url 'listChildDomains' %}" class="menu-item">
<span>List Sub/Addon Domains</span>
</a>
<a href="{% url 'fixSubdomainLogs' %}" class="menu-item">
<span>Fix Subdomain Logs</span>
</a>
{% if admin or modifyWebsite %}
<a href="{% url 'modifyWebsite' %}" class="menu-item">
<span>Modify Website</span>
@@ -1651,22 +1690,22 @@
</a>
{% endif %}
{% if admin or deleteZone %}
<a href="{% url 'deleteDNSZone' %}" class="menu-item">
<a href="{% url 'deleteDNSZone' %}" class="menu-item deleteZone">
<span>Delete Zone</span>
</a>
{% endif %}
{% if admin or addDeleteRecords %}
<a href="{% url 'addDeleteDNSRecords' %}" class="menu-item">
<a href="{% url 'addDeleteDNSRecords' %}" class="menu-item addDeleteRecords">
<span>Add/Delete Records</span>
</a>
{% endif %}
{% if admin or addDeleteRecords %}
<a href="{% url 'addDeleteDNSRecordsCloudFlare' %}" class="menu-item">
<a href="{% url 'addDeleteDNSRecordsCloudFlare' %}" class="menu-item addDeleteRecords">
<span>CloudFlare</span>
</a>
{% endif %}
{% if admin or addDeleteRecords %}
<a href="{% url 'ResetDNSConfigurations' %}" class="menu-item">
<a href="{% url 'ResetDNSConfigurations' %}" class="menu-item addDeleteRecords">
<span>Reset DNS Configurations</span>
</a>
{% endif %}
@@ -1835,9 +1874,7 @@
<a href="{% url 'manageSSL' %}" class="menu-item">
<span>Manage SSL</span>
</a>
<a href="{% url 'sslReconcile' %}" class="menu-item">
<span>SSL Reconciliation</span>
</a>
{% comment %}SSL Reconciliation - hidden; URL /manageSSL/sslReconcile still works if needed{% endcomment %}
{% endif %}
{% if admin or hostnameSSL %}
<a href="{% url 'sslForHostName' %}" class="menu-item">
@@ -2186,14 +2223,14 @@
<script src="{% static 'websiteFunctions/websiteFunctions.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'userManagment/userManagment.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'databases/databases.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'dns/dns.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'dns/dns.js' %}?v={{ CP_VERSION }}&dns={{ DNS_STATIC_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'mailServer/mailServer.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'ftp/ftp.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'backup/backup.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'managePHP/managePHP.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverLogs/serverLogs.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'serverStatus/serverStatus.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'firewall/firewall.js' %}?v={{ CP_VERSION }}&fw={{ FIREWALL_STATIC_VERSION|default:CP_VERSION }}&cb=4" data-cfasync="false"></script>
<script src="{% static 'emailPremium/emailPremium.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'manageServices/manageServices.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
<script src="{% static 'CLManager/CLManager.js' %}?v={{ CP_VERSION }}" data-cfasync="false"></script>
@@ -2305,10 +2342,7 @@
// Check if user has backup configured (you'll need to implement this API)
// For now, we'll show it by default unless they have a backup plan
// This should be replaced with an actual check
{% if not request.session.has_backup_configured %}
showBackupNotification();
{% endif %}
// Session check omitted - has_backup_configured may be absent in some views
// For demonstration, let's show it if URL doesn't contain 'OneClickBackups'
if (!window.location.href.includes('OneClickBackups')) {
@@ -2400,12 +2434,13 @@
// .htaccess Feature Notification Functions
function checkHtaccessStatus() {
// Check if user has dismissed the notification permanently (localStorage for longer persistence)
if (localStorage.getItem('htaccessNotificationDismissed') === 'true') {
return;
}
try {
if (localStorage.getItem('htaccessNotificationDismissed') === 'true') return;
} catch (e) { return; }
// Check if notification has been shown today
const lastShown = localStorage.getItem('htaccessNotificationLastShown');
var lastShown;
try { lastShown = localStorage.getItem('htaccessNotificationLastShown'); } catch (e) { return; }
const today = new Date().toDateString();
if (lastShown === today) {
@@ -2414,7 +2449,7 @@
// Show the notification
showHtaccessNotification();
localStorage.setItem('htaccessNotificationLastShown', today);
try { localStorage.setItem('htaccessNotificationLastShown', today); } catch (e) {}
}
function showHtaccessNotification() {
@@ -2430,7 +2465,7 @@
banner.classList.remove('show');
body.classList.remove('htaccess-shown');
// Remember dismissal permanently
localStorage.setItem('htaccessNotificationDismissed', 'true');
try { localStorage.setItem('htaccessNotificationDismissed', 'true'); } catch (e) {}
}
function isNotificationDismissed(notificationKey) {
@@ -2441,7 +2476,7 @@
return {% if ai_scanner_notification_dismissed %}true{% else %}false{% endif %};
}
if (notificationKey === 'htaccess-notification') {
return localStorage.getItem('htaccessNotificationDismissed') === 'true';
try { return localStorage.getItem('htaccessNotificationDismissed') === 'true'; } catch (e) { return false; }
}
return false;
}
@@ -2516,6 +2551,10 @@
document.addEventListener('DOMContentLoaded', function() {
loadNotificationCenter();
checkBackupStatus();
// Optional: open notification dropdown for testing (e.g. ?showNotifications=1)
if (window.location.search.indexOf('showNotifications=1') !== -1) {
setTimeout(function() { document.getElementById('notification-center-dropdown').classList.add('show'); }, 400);
}
// Show AI Scanner notification with a slight delay for better UX
setTimeout(checkAIScannerStatus, 1000);
// Show .htaccess notification with additional delay for staggered effect
@@ -2560,10 +2599,12 @@
<!-- Dark Mode Toggle Script -->
<script>
// Theme switching functionality
// Theme switching functionality (wrapped in try-catch for Tracking Prevention / private browsing)
(function() {
// Get saved theme from localStorage or default to light
const savedTheme = localStorage.getItem('cyberPanelTheme') || 'light';
var savedTheme = 'light';
try {
savedTheme = localStorage.getItem('cyberPanelTheme') || 'light';
} catch (e) { /* Tracking Prevention or storage disabled */ }
document.documentElement.setAttribute('data-theme', savedTheme);
// Update icon based on current theme
@@ -2586,7 +2627,7 @@
// Update theme
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('cyberPanelTheme', newTheme);
try { localStorage.setItem('cyberPanelTheme', newTheme); } catch (e) { /* Tracking Prevention */ }
// Update icon
updateThemeIcon(newTheme);

View File

@@ -199,9 +199,9 @@
word-break: break-all;
}
/* Progress log */
/* Progress log - dark background for terminal-style visibility */
.log-container {
background: var(--text-primary, #1e1e1e);
background: #1e1e1e;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
@@ -215,11 +215,12 @@
min-height: 300px;
background: transparent;
border: none;
color: var(--bg-secondary, #f0f0f0);
color: #e8e8e8;
font-family: 'SF Mono', Monaco, monospace;
font-size: 13px;
line-height: 1.6;
resize: vertical;
caret-color: #e8e8e8;
}
.log-textarea:focus {
@@ -337,30 +338,51 @@
</div>
<script>
// Function to populate the branch dropdown
function populateBranches(branches) {
var branchSelect = document.getElementById("branchSelect");
for (let i = branches.length - 1; i >= 0; i--) {
const branch = branches[i];
var option = document.createElement("option");
option.value = branch;
option.text = branch;
if (branch.startsWith("v") && branch.indexOf("dev") === -1 && branch.indexOf("version-counter") === -1) {
branchSelect.appendChild(option);
}
}
// Sort key for version branches: v2.5.5-dev > v2.5.5 > v2.4.4
function versionSortKey(name) {
var m = name.match(/^v(\d+)\.(\d+)(?:\.(\d+))?(?:-(.+))?$/);
if (!m) return [0, 0, 0, 0];
var major = parseInt(m[1], 10) || 0;
var minor = parseInt(m[2], 10) || 0;
var patch = parseInt(m[3], 10) || 0;
var suffix = (m[4] === 'dev') ? 1 : 0;
return [major, minor, patch, suffix];
}
function compareBranches(a, b) {
var ka = versionSortKey(a), kb = versionSortKey(b);
for (var i = 0; i < 4; i++) {
if (ka[i] !== kb[i]) return kb[i] - ka[i];
}
return 0;
}
// Function to populate the branch dropdown (latest 10 only)
function populateBranches(branches) {
var filtered = branches.filter(function(b) {
return b.startsWith("v") && b.indexOf("version-counter") === -1;
});
filtered.sort(compareBranches);
var latest = filtered.slice(0, 10);
var branchSelect = document.getElementById("branchSelect");
for (var i = 0; i < latest.length; i++) {
var option = document.createElement("option");
option.value = latest[i];
option.text = latest[i];
branchSelect.appendChild(option);
}
}
function getBranches(url, branches, page) {
if (!page) page = 1;
fetch(url + '?page=' + page)
fetch(url + '?per_page=100&page=' + page)
.then((response) => response.json())
.then((data) => {
if (data.length === 0) {
populateBranches(branches);
} else {
const branchNames = data.map(branch => branch.name);
var branchNames = data.map(function(b) { return b.name; });
branches = branches.concat(branchNames);
getBranches(url, branches, page + 1);
}

View File

@@ -7,6 +7,7 @@ urlpatterns = [
re_path(r'^getAdminStatus$', views.getAdminStatus, name='getSystemInformation'),
re_path(r'^getLoadAverage$', views.getLoadAverage, name='getLoadAverage'),
re_path(r'^versionManagment$', views.versionManagment, name='versionManagment'),
re_path(r'^versionManagement$', views.versionManagment, name='versionManagement'),
re_path(r'^design$', views.design, name='design'),
re_path(r'^getthemedata$', views.getthemedata, name='getthemedata'),
re_path(r'^upgrade$', views.upgrade, name='upgrade'),

View File

@@ -16,6 +16,7 @@ from plogical.acl import ACLManager
from manageServices.models import PDNSStatus
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt
from plogical.processUtilities import ProcessUtilities
from plogical.firewallUtilities import FirewallUtilities
from plogical.httpProc import httpProc
from websiteFunctions.models import Websites, WPSites
from databases.models import Databases
@@ -25,6 +26,7 @@ from loginSystem.models import Administrator
from packages.models import Package
from django.views.decorators.http import require_GET, require_POST
import pwd
import re
# Create your views here.
@@ -32,6 +34,27 @@ VERSION = '2.5.5'
BUILD = 'dev'
def _version_compare(a, b):
"""Return 1 if a > b, -1 if a < b, 0 if equal."""
def parse(v):
parts = []
for p in str(v).split('.'):
try:
parts.append(int(p))
except ValueError:
parts.append(0)
return parts
pa, pb = parse(a), parse(b)
for i in range(max(len(pa), len(pb))):
va = pa[i] if i < len(pa) else 0
vb = pb[i] if i < len(pb) else 0
if va > vb:
return 1
if va < vb:
return -1
return 0
@ensure_csrf_cookie
def renderBase(request):
template = 'baseTemplate/homePage.html'
@@ -44,27 +67,41 @@ def renderBase(request):
@ensure_csrf_cookie
def versionManagement(request):
currentVersion = VERSION
currentBuild = str(BUILD)
getVersion = requests.get('https://cyberpanel.net/version.txt')
latest = getVersion.json()
latestVersion = latest['version']
latestBuild = latest['build']
branch_ref = 'v%s.%s' % (latestVersion, latestBuild)
currentVersion = VERSION
currentBuild = str(BUILD)
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=v%s.%s" % (latestVersion, latestBuild)
logging.writeToFile(u)
r = requests.get(u)
latestcomit = r.json()[0]['sha']
command = "git -C /usr/local/CyberCP/ rev-parse HEAD"
output = ProcessUtilities.outputExecutioner(command)
Currentcomt = output.rstrip("\n")
notechk = True
Currentcomt = ''
latestcomit = ''
if Currentcomt == latestcomit:
if _version_compare(currentVersion, latestVersion) > 0:
notechk = False
else:
remote_cmd = 'git -C /usr/local/CyberCP remote get-url origin 2>/dev/null || true'
remote_out = ProcessUtilities.outputExecutioner(remote_cmd)
is_usmannasir = 'usmannasir/cyberpanel' in (remote_out or '')
if is_usmannasir:
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=%s" % branch_ref
logging.CyberCPLogFileWriter.writeToFile(u)
try:
r = requests.get(u, timeout=10)
r.raise_for_status()
latestcomit = r.json()[0]['sha']
except (requests.RequestException, IndexError, KeyError) as e:
logging.CyberCPLogFileWriter.writeToFile('[versionManagement] GitHub API failed: %s' % str(e))
head_cmd = 'git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || true'
Currentcomt = ProcessUtilities.outputExecutioner(head_cmd).rstrip('\n')
if latestcomit and Currentcomt == latestcomit:
notechk = False
else:
notechk = False
template = 'baseTemplate/versionManagment.html'
finalData = {'build': currentBuild, 'currentVersion': currentVersion, 'latestVersion': latestVersion,
@@ -129,6 +166,11 @@ def getAdminStatus(request):
def getSystemStatus(request):
default_fallback = {
'cpuUsage': 0, 'ramUsage': 0, 'diskUsage': 0,
'cpuCores': 2, 'ramTotalMB': 4096, 'diskTotalGB': 100,
'diskFreeGB': 100, 'uptime': 'N/A'
}
try:
val = request.session['userID']
currentACL = ACLManager.loadedACL(val)
@@ -215,31 +257,13 @@ def getSystemStatus(request):
except KeyError as e:
logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] KeyError - No session userID: {str(e)}')
# Return default values on error
default_data = {
'cpuUsage': 0,
'ramUsage': 0,
'diskUsage': 0,
'cpuCores': 2,
'ramTotalMB': 4096,
'diskTotalGB': 100,
'diskFreeGB': 100,
'uptime': 'N/A'
}
return HttpResponse(json.dumps(default_data))
return HttpResponse(json.dumps(default_fallback))
except Exception as e:
# Return default values on error
default_data = {
'cpuUsage': 0,
'ramUsage': 0,
'diskUsage': 0,
'cpuCores': 2,
'ramTotalMB': 4096,
'diskTotalGB': 100,
'diskFreeGB': 100,
'uptime': 'N/A'
}
return HttpResponse(json.dumps(default_data))
logging.CyberCPLogFileWriter.writeToFile(f'[getSystemStatus] Exception: {str(e)}')
try:
return HttpResponse(json.dumps(default_fallback))
except Exception:
return HttpResponse('{"cpuUsage":0,"ramUsage":0,"diskUsage":0,"cpuCores":2,"ramTotalMB":4096,"diskTotalGB":100,"diskFreeGB":100,"uptime":"N/A"}', content_type='application/json')
def getLoadAverage(request):
@@ -265,31 +289,75 @@ def getLoadAverage(request):
@ensure_csrf_cookie
def versionManagment(request):
## Get latest version
getVersion = requests.get('https://cyberpanel.net/version.txt')
latest = getVersion.json()
latestVersion = latest['version']
latestBuild = latest['build']
## Get local version
currentVersion = VERSION
currentBuild = str(BUILD)
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=v%s.%s" % (latestVersion, latestBuild)
logging.CyberCPLogFileWriter.writeToFile(u)
r = requests.get(u)
latestcomit = r.json()[0]['sha']
command = "git -C /usr/local/CyberCP/ rev-parse HEAD"
output = ProcessUtilities.outputExecutioner(command)
Currentcomt = output.rstrip("\n")
notechk = True
Currentcomt = ''
latestcomit = ''
latestVersion = '0'
latestBuild = '0'
if (Currentcomt == latestcomit):
on_dev_branch = (currentVersion == '2.5.5' and currentBuild == 'dev')
try:
getVersion = requests.get('https://cyberpanel.net/version.txt', timeout=10)
getVersion.raise_for_status()
latest = getVersion.json()
latestVersion = str(latest.get('version', '0'))
latestBuild = str(latest.get('build', '0'))
except (requests.RequestException, ValueError, KeyError) as e:
logging.CyberCPLogFileWriter.writeToFile('[versionManagment] cyberpanel.net/version.txt failed: %s' % str(e))
if on_dev_branch:
latestVersion, latestBuild = '2.5.5', 'dev'
# Dev branch: compare against v2.5.5-dev, show dev version info
if on_dev_branch:
branch_ref = 'v2.5.5-dev'
latestVersion, latestBuild = '2.5.5', 'dev'
else:
branch_ref = 'v%s.%s' % (latestVersion, latestBuild)
# Always fetch local HEAD for display
head_cmd = 'git -C /usr/local/CyberCP rev-parse HEAD 2>/dev/null || true'
Currentcomt = (ProcessUtilities.outputExecutioner(head_cmd) or '').rstrip('\n')
remote_cmd = 'git -C /usr/local/CyberCP remote get-url origin 2>/dev/null || true'
remote_out = ProcessUtilities.outputExecutioner(remote_cmd)
is_usmannasir = 'usmannasir/cyberpanel' in (remote_out or '')
# Stable: newer than cyberpanel.net = up to date; dev: compare commits
if not on_dev_branch and notechk and _version_compare(currentVersion, latestVersion) > 0:
notechk = False
elif notechk:
# Dev branch: always use usmannasir v2.5.5-dev as canonical "latest"
# Forks: use usmannasir for Latest Commit so all dev users compare to same upstream
fetch_branch = branch_ref if (is_usmannasir or on_dev_branch) else None
if fetch_branch:
u = "https://api.github.com/repos/usmannasir/cyberpanel/commits?sha=%s" % fetch_branch
logging.CyberCPLogFileWriter.writeToFile(u)
try:
r = requests.get(u, timeout=10)
r.raise_for_status()
latestcomit = r.json()[0]['sha']
if Currentcomt and latestcomit and Currentcomt == latestcomit:
notechk = False
except (requests.RequestException, IndexError, KeyError) as e:
logging.CyberCPLogFileWriter.writeToFile('[versionManagment] GitHub API failed: %s' % str(e))
elif not on_dev_branch:
# Stable fork: fetch from fork's branch
m = re.search(r'github\.com[:/]([^/]+)/([^/]+?)(?:\.git)?$', (remote_out or '').strip())
if m:
owner, repo = m.group(1), m.group(2).rstrip('.git')
try:
u = "https://api.github.com/repos/%s/%s/commits?sha=%s" % (owner, repo, branch_ref)
r = requests.get(u, timeout=10)
r.raise_for_status()
latestcomit = r.json()[0]['sha']
if Currentcomt and latestcomit and Currentcomt == latestcomit:
notechk = False
except (requests.RequestException, IndexError, KeyError):
pass
template = 'baseTemplate/versionManagment.html'
finalData = {'build': currentBuild, 'currentVersion': currentVersion, 'latestVersion': latestVersion,
@@ -729,9 +797,19 @@ def getRecentSSHLogins(request):
import re, time
from collections import OrderedDict
# Run 'last -n 20' to get recent SSH logins
# Pagination params
try:
output = ProcessUtilities.outputExecutioner('last -n 20')
page = max(1, int(request.GET.get('page', 1)))
except (ValueError, TypeError):
page = 1
try:
per_page = min(100, max(5, int(request.GET.get('per_page', 20))))
except (ValueError, TypeError):
per_page = 20
# Run 'last -n 500' to get enough entries for pagination
try:
output = ProcessUtilities.outputExecutioner('last -n 500')
except Exception as e:
return HttpResponse(json.dumps({'error': 'Failed to run last: %s' % str(e)}), content_type='application/json', status=500)
@@ -815,7 +893,19 @@ def getRecentSSHLogins(request):
'is_active': is_active,
'raw': line
})
return HttpResponse(json.dumps({'logins': logins}), content_type='application/json')
total = len(logins)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = min(page, total_pages) if total_pages > 0 else 1
start = (page - 1) * per_page
end = start + per_page
paginated_logins = logins[start:end]
return HttpResponse(json.dumps({
'logins': paginated_logins,
'total': total,
'page': page,
'per_page': per_page,
'total_pages': total_pages
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@@ -829,6 +919,17 @@ def getRecentSSHLogs(request):
currentACL = ACLManager.loadedACL(user_id)
if not currentACL.get('admin', 0):
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
# Pagination params
try:
page = max(1, int(request.GET.get('page', 1)))
except (ValueError, TypeError):
page = 1
try:
per_page = min(100, max(5, int(request.GET.get('per_page', 25))))
except (ValueError, TypeError):
per_page = 25
from plogical.processUtilities import ProcessUtilities
import re
distro = ProcessUtilities.decideDistro()
@@ -837,7 +938,7 @@ def getRecentSSHLogs(request):
else:
log_path = '/var/log/secure'
try:
output = ProcessUtilities.outputExecutioner(f'tail -n 100 {log_path}')
output = ProcessUtilities.outputExecutioner(f'tail -n 500 {log_path}')
except Exception as e:
return HttpResponse(json.dumps({'error': f'Failed to read log: {str(e)}'}), content_type='application/json', status=500)
lines = output.split('\n')
@@ -875,7 +976,21 @@ def getRecentSSHLogs(request):
'raw': line,
'ip_address': ip_address
})
return HttpResponse(json.dumps({'logs': logs}), content_type='application/json')
# Reverse so newest logs appear first (page 1 = most recent)
logs.reverse()
total = len(logs)
total_pages = (total + per_page - 1) // per_page if total > 0 else 1
page = min(page, total_pages) if total_pages > 0 else 1
start = (page - 1) * per_page
end = start + per_page
paginated_logs = logs[start:end]
return HttpResponse(json.dumps({
'logs': paginated_logs,
'total': total,
'page': page,
'per_page': per_page,
'total_pages': total_pages
}), content_type='application/json')
except Exception as e:
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
@@ -1284,61 +1399,23 @@ def blockIPAddress(request):
'error': 'Invalid IP address'
}), content_type='application/json', status=400)
# Use firewalld (CSF has been discontinued)
# Use FirewallUtilities so firewall-cmd runs with proper privileges (root/lscpd)
firewall_cmd = 'firewalld'
reason = data.get('reason', 'Security alert detected from dashboard')
try:
# Verify firewalld is active using subprocess for better security
import subprocess
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):
return HttpResponse(json.dumps({
'status': 0,
'error': 'Firewalld is not active. Please enable firewalld service.'
}), content_type='application/json', status=500)
except subprocess.TimeoutExpired:
return HttpResponse(json.dumps({
'status': 0,
'error': 'Timeout checking firewalld status'
}), content_type='application/json', status=500)
success, msg = FirewallUtilities.blockIP(ip_address, reason)
except Exception as e:
return HttpResponse(json.dumps({
'status': 0,
'error': f'Cannot check firewalld status: {str(e)}'
}), content_type='application/json', status=500)
# Block the IP address using firewalld with subprocess for better security
success = False
error_message = ''
try:
# Use subprocess with explicit argument lists to prevent injection
rich_rule = f'rule family=ipv4 source address={ip_address} 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:
success = True
else:
error_message = f'Failed to reload firewall rules: {reload_result.stderr}'
else:
error_message = f'Failed to add firewall rule: {result.stderr}'
except subprocess.TimeoutExpired:
error_message = 'Firewall command timed out'
except Exception as e:
error_message = f'Firewall command failed: {str(e)}'
success = False
msg = 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'
primary_file = '/usr/local/CyberCP/data/banned_ips.json'
legacy_file = '/etc/cyberpanel/banned_ips.json'
banned_ips_file = primary_file if os.path.exists(primary_file) else legacy_file if os.path.exists(legacy_file) else primary_file
banned_ips = []
if os.path.exists(banned_ips_file):
@@ -1372,10 +1449,10 @@ def blockIPAddress(request):
banned_ips.append(new_banned_ip)
# Ensure directory exists
os.makedirs(os.path.dirname(banned_ips_file), exist_ok=True)
os.makedirs(os.path.dirname(primary_file), exist_ok=True)
# Save to file
with open(banned_ips_file, 'w') as f:
with open(primary_file, 'w') as f:
json.dump(banned_ips, f, indent=2)
except Exception as e:
# Log but don't fail the request if JSON update fails
@@ -1394,7 +1471,7 @@ def blockIPAddress(request):
else:
return HttpResponse(json.dumps({
'status': 0,
'error': error_message or 'Failed to block IP address'
'error': msg or 'Failed to block IP address'
}), content_type='application/json', status=500)
except json.JSONDecodeError as e: