mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-11-17 02:31:09 +01:00
Enhance text readability and error handling: Add readability fixes CSS for improved contrast across UI elements. Update text colors in various components for better visibility. Enhance FTP error handling with user-friendly messages and improved path validation in both frontend and backend. Update HTML templates to provide clearer instructions and examples for FTP path input.
This commit is contained in:
@@ -23,6 +23,9 @@
|
|||||||
<!-- Mobile Responsive CSS -->
|
<!-- Mobile Responsive CSS -->
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/mobile-responsive.css' %}?v={{ CP_VERSION }}">
|
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/mobile-responsive.css' %}?v={{ CP_VERSION }}">
|
||||||
|
|
||||||
|
<!-- Readability Fixes CSS -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/readability-fixes.css' %}?v={{ CP_VERSION }}">
|
||||||
|
|
||||||
<!-- Core Scripts -->
|
<!-- Core Scripts -->
|
||||||
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
|
<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>
|
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
|
||||||
@@ -54,7 +57,7 @@
|
|||||||
--bg-sidebar-item: white;
|
--bg-sidebar-item: white;
|
||||||
--bg-hover: #e8e6ff;
|
--bg-hover: #e8e6ff;
|
||||||
--text-primary: #2f3640;
|
--text-primary: #2f3640;
|
||||||
--text-secondary: #64748b;
|
--text-secondary: #2f3640; /* Changed from grey to dark for better readability */
|
||||||
--text-heading: #1e293b;
|
--text-heading: #1e293b;
|
||||||
--border-color: #e8e9ff;
|
--border-color: #e8e9ff;
|
||||||
--shadow-color: rgba(0,0,0,0.05);
|
--shadow-color: rgba(0,0,0,0.05);
|
||||||
@@ -74,7 +77,7 @@
|
|||||||
--bg-sidebar-item: #1e1e42;
|
--bg-sidebar-item: #1e1e42;
|
||||||
--bg-hover: #252550;
|
--bg-hover: #252550;
|
||||||
--text-primary: #e4e4e7;
|
--text-primary: #e4e4e7;
|
||||||
--text-secondary: #9ca3af;
|
--text-secondary: #e4e4e7; /* Changed from grey to light for better readability */
|
||||||
--text-heading: #f3f4f6;
|
--text-heading: #f3f4f6;
|
||||||
--border-color: #2a2a5e;
|
--border-color: #2a2a5e;
|
||||||
--shadow-color: rgba(0,0,0,0.3);
|
--shadow-color: rgba(0,0,0,0.3);
|
||||||
@@ -352,6 +355,12 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fix green text issue - ensure uptime uses normal text color */
|
||||||
|
#sidebar .server-info .info-line span,
|
||||||
|
#sidebar .server-info .info-line .ng-binding {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
#sidebar .server-info .info-line strong {
|
#sidebar .server-info .info-line strong {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -701,6 +710,7 @@
|
|||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
}
|
}
|
||||||
@@ -791,6 +801,7 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,6 +898,47 @@
|
|||||||
background: var(--accent-hover);
|
background: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Enhanced Text Readability Fixes */
|
||||||
|
/* Ensure all text elements use proper contrast colors */
|
||||||
|
.menu-item span,
|
||||||
|
.menu-item i,
|
||||||
|
.info-line,
|
||||||
|
.info-line span,
|
||||||
|
.info-line strong,
|
||||||
|
.server-details,
|
||||||
|
.server-details *,
|
||||||
|
.tagline,
|
||||||
|
.brand {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Override any Angular binding colors that might be green */
|
||||||
|
.ng-binding {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure menu items have proper text color */
|
||||||
|
#sidebar .menu-item {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .menu-item:hover {
|
||||||
|
color: var(--accent-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .menu-item.active {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix any remaining grey text issues */
|
||||||
|
.text-muted,
|
||||||
|
.text-secondary,
|
||||||
|
.text-light,
|
||||||
|
small,
|
||||||
|
.small {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mobile Menu Toggle */
|
/* Mobile Menu Toggle */
|
||||||
#mobile-menu-toggle {
|
#mobile-menu-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1113,7 +1165,6 @@
|
|||||||
<i class="fab fa-wordpress"></i>
|
<i class="fab fa-wordpress"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>WordPress</span>
|
<span>WordPress</span>
|
||||||
<span class="badge">NEW</span>
|
|
||||||
<i class="fas fa-chevron-right chevron"></i>
|
<i class="fas fa-chevron-right chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<div id="wordpress-submenu" class="submenu">
|
<div id="wordpress-submenu" class="submenu">
|
||||||
@@ -1465,7 +1516,6 @@
|
|||||||
</a>
|
</a>
|
||||||
<a href="{% url 'sslReconcile' %}" class="menu-item">
|
<a href="{% url 'sslReconcile' %}" class="menu-item">
|
||||||
<span>SSL Reconciliation</span>
|
<span>SSL Reconciliation</span>
|
||||||
<span class="badge">NEW</span>
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if admin or hostnameSSL %}
|
{% if admin or hostnameSSL %}
|
||||||
@@ -1488,7 +1538,6 @@
|
|||||||
<i class="fas fa-database"></i>
|
<i class="fas fa-database"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>MySQL Manager</span>
|
<span>MySQL Manager</span>
|
||||||
<span class="badge">NEW</span>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'Filemanager' %}" class="menu-item">
|
<a href="{% url 'Filemanager' %}" class="menu-item">
|
||||||
@@ -1503,7 +1552,6 @@
|
|||||||
<i class="fas fa-fire"></i>
|
<i class="fas fa-fire"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>CloudLinux</span>
|
<span>CloudLinux</span>
|
||||||
<span class="badge">NEW</span>
|
|
||||||
<i class="fas fa-chevron-right chevron"></i>
|
<i class="fas fa-chevron-right chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<div id="cloudlinux-submenu" class="submenu">
|
<div id="cloudlinux-submenu" class="submenu">
|
||||||
@@ -1669,7 +1717,6 @@
|
|||||||
<i class="fas fa-envelope"></i>
|
<i class="fas fa-envelope"></i>
|
||||||
</div>
|
</div>
|
||||||
<span>Mail Settings</span>
|
<span>Mail Settings</span>
|
||||||
<span class="badge">NEW</span>
|
|
||||||
<i class="fas fa-chevron-right chevron"></i>
|
<i class="fas fa-chevron-right chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<div id="mail-settings-submenu" class="submenu">
|
<div id="mail-settings-submenu" class="submenu">
|
||||||
|
|||||||
@@ -1111,7 +1111,6 @@
|
|||||||
<a href="#" title="Pages">
|
<a href="#" title="Pages">
|
||||||
<i class="glyph-icon icon-linecons-fire"></i>
|
<i class="glyph-icon icon-linecons-fire"></i>
|
||||||
<span>Pages</span>
|
<span>Pages</span>
|
||||||
<span class="bs-label badge-yellow">NEW</span>
|
|
||||||
</a>
|
</a>
|
||||||
<div class="sidebar-submenu">
|
<div class="sidebar-submenu">
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,29 @@ class FTPManager:
|
|||||||
return HttpResponse(json_data)
|
return HttpResponse(json_data)
|
||||||
|
|
||||||
except BaseException as msg:
|
except BaseException as msg:
|
||||||
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': str(msg)}
|
# Enhanced error handling with better user feedback
|
||||||
|
error_message = str(msg)
|
||||||
|
|
||||||
|
# Provide more user-friendly error messages
|
||||||
|
if "Invalid path" in error_message:
|
||||||
|
pass # Keep original message as it's already user-friendly
|
||||||
|
elif "Security violation" in error_message:
|
||||||
|
pass # Keep original message as it's already user-friendly
|
||||||
|
elif "Path validation failed" in error_message:
|
||||||
|
pass # Keep original message as it's already user-friendly
|
||||||
|
elif "Exceeded maximum amount" in error_message:
|
||||||
|
pass # Keep original message as it's already user-friendly
|
||||||
|
elif "symlinked" in error_message.lower():
|
||||||
|
error_message = "Cannot create FTP account: The specified path is a symbolic link. Please choose a different path."
|
||||||
|
elif "Permission denied" in error_message:
|
||||||
|
error_message = "Permission denied: Unable to create or access the specified directory. Please check the path and try again."
|
||||||
|
elif "No such file or directory" in error_message:
|
||||||
|
error_message = "Directory not found: The specified path does not exist. Please check the path and try again."
|
||||||
|
else:
|
||||||
|
# Generic fallback for other errors
|
||||||
|
error_message = f"FTP account creation failed: {error_message}"
|
||||||
|
|
||||||
|
data_ret = {'status': 0, 'creatFTPStatus': 0, 'error_message': error_message}
|
||||||
json_data = json.dumps(data_ret)
|
json_data = json.dumps(data_ret)
|
||||||
return HttpResponse(json_data)
|
return HttpResponse(json_data)
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,45 @@ app.controller('createFTPAccount', function ($scope, $http) {
|
|||||||
var ftpPassword = $scope.ftpPassword;
|
var ftpPassword = $scope.ftpPassword;
|
||||||
var path = $scope.ftpPath;
|
var path = $scope.ftpPath;
|
||||||
|
|
||||||
if (typeof path === 'undefined') {
|
// Enhanced path validation
|
||||||
|
if (typeof path === 'undefined' || path === null) {
|
||||||
path = "";
|
path = "";
|
||||||
|
} else {
|
||||||
|
path = path.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-side path validation
|
||||||
|
if (path && path !== "") {
|
||||||
|
// Check for dangerous characters
|
||||||
|
var dangerousChars = /[;&|$`'"<>*?~]/;
|
||||||
|
if (dangerousChars.test(path)) {
|
||||||
|
$scope.ftpLoading = false;
|
||||||
|
$scope.canNotCreateFTP = false;
|
||||||
|
$scope.successfullyCreatedFTP = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.errorMessage = "Invalid path: Path contains dangerous characters";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for path traversal attempts
|
||||||
|
if (path.indexOf("..") !== -1 || path.indexOf("~") !== -1) {
|
||||||
|
$scope.ftpLoading = false;
|
||||||
|
$scope.canNotCreateFTP = false;
|
||||||
|
$scope.successfullyCreatedFTP = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.errorMessage = "Invalid path: Path cannot contain '..' or '~'";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if path starts with slash (should be relative)
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
$scope.ftpLoading = false;
|
||||||
|
$scope.canNotCreateFTP = false;
|
||||||
|
$scope.successfullyCreatedFTP = true;
|
||||||
|
$scope.couldNotConnect = true;
|
||||||
|
$scope.errorMessage = "Invalid path: Path must be relative (not starting with '/')";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = "/ftp/submitFTPCreation";
|
var url = "/ftp/submitFTPCreation";
|
||||||
|
|||||||
@@ -521,9 +521,23 @@
|
|||||||
<div class="path-info">
|
<div class="path-info">
|
||||||
<i class="fas fa-folder"></i>
|
<i class="fas fa-folder"></i>
|
||||||
{% trans "Leave empty to use the website's home directory, or specify a subdirectory" %}
|
{% trans "Leave empty to use the website's home directory, or specify a subdirectory" %}
|
||||||
|
<br>
|
||||||
|
<small style="margin-top: 0.5rem; display: block; color: var(--text-secondary, #64748b);">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<strong>{% trans "Examples:" %}</strong> {% trans "docs, public_html, uploads, api" %}
|
||||||
|
<br>
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
{% trans "Security: Path will be restricted to this subdirectory only" %}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<input placeholder="{% trans 'e.g., public_html or leave empty' %}"
|
<input placeholder="{% trans 'e.g., docs or public_html (leave empty for home directory)' %}"
|
||||||
type="text" class="form-control" ng-model="ftpPath">
|
type="text" class="form-control" ng-model="ftpPath"
|
||||||
|
pattern="^[a-zA-Z0-9._/-]+$"
|
||||||
|
title="{% trans 'Only letters, numbers, dots, underscores, hyphens, and forward slashes allowed' %}">
|
||||||
|
<small style="color: var(--text-secondary, #64748b); margin-top: 0.5rem; display: block;">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
{% trans "Do not use: .. ~ / or special characters like ; | & $ ` ' \" < > * ?" %}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-hide="ftpDetails" class="form-group">
|
<div ng-hide="ftpDetails" class="form-group">
|
||||||
|
|||||||
@@ -89,11 +89,35 @@ class FTPUtilities:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def ftpFunctions(path,externalApp):
|
def ftpFunctions(path,externalApp):
|
||||||
try:
|
try:
|
||||||
|
# Enhanced path validation and creation
|
||||||
|
import os
|
||||||
|
|
||||||
command = 'mkdir %s' % (path)
|
# Check if path already exists
|
||||||
|
if os.path.exists(path):
|
||||||
|
# Path exists, ensure it's a directory
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
return 0, "Specified path exists but is not a directory"
|
||||||
|
# Set proper permissions
|
||||||
|
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
|
||||||
|
ProcessUtilities.executioner(command, externalApp)
|
||||||
|
return 1, 'None'
|
||||||
|
else:
|
||||||
|
# Create the directory with proper permissions
|
||||||
|
command = 'mkdir -p %s' % (path)
|
||||||
|
result = ProcessUtilities.executioner(command, externalApp)
|
||||||
|
|
||||||
|
if result == 0:
|
||||||
|
# Set proper ownership
|
||||||
|
command = 'chown -R %s:%s %s' % (externalApp, externalApp, path)
|
||||||
|
ProcessUtilities.executioner(command, externalApp)
|
||||||
|
|
||||||
|
# Set proper permissions (755)
|
||||||
|
command = 'chmod 755 %s' % (path)
|
||||||
ProcessUtilities.executioner(command, externalApp)
|
ProcessUtilities.executioner(command, externalApp)
|
||||||
|
|
||||||
return 1, 'None'
|
return 1, 'None'
|
||||||
|
else:
|
||||||
|
return 0, "Failed to create directory: %s" % path
|
||||||
|
|
||||||
except BaseException as msg:
|
except BaseException as msg:
|
||||||
logging.CyberCPLogFileWriter.writeToFile(
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
@@ -118,30 +142,43 @@ class FTPUtilities:
|
|||||||
|
|
||||||
## gid , uid ends
|
## gid , uid ends
|
||||||
|
|
||||||
path = path.lstrip("/")
|
# Enhanced path validation and handling
|
||||||
|
if path and path.strip() and path != 'None':
|
||||||
|
# Clean the path
|
||||||
|
path = path.strip().lstrip("/")
|
||||||
|
|
||||||
if path != 'None':
|
# Additional security checks
|
||||||
path = "/home/" + domainName + "/" + path
|
if path.find("..") > -1 or path.find("~") > -1 or path.startswith("/"):
|
||||||
|
raise BaseException("Invalid path: Path must be relative and not contain '..' or '~' or start with '/'")
|
||||||
|
|
||||||
## Security Check
|
# Check for dangerous characters
|
||||||
|
dangerous_chars = [';', '|', '&', '$', '`', '\'', '"', '<', '>', '*', '?']
|
||||||
|
if any(char in path for char in dangerous_chars):
|
||||||
|
raise BaseException("Invalid path: Path contains dangerous characters")
|
||||||
|
|
||||||
if path.find("..") > -1:
|
# Construct full path
|
||||||
raise BaseException("Specified path must be inside virtual host home!")
|
full_path = "/home/" + domainName + "/" + path
|
||||||
|
|
||||||
|
# Additional security: ensure path is within domain directory
|
||||||
|
domain_home = "/home/" + domainName
|
||||||
|
if not os.path.abspath(full_path).startswith(os.path.abspath(domain_home)):
|
||||||
|
raise BaseException("Security violation: Path must be within domain directory")
|
||||||
|
|
||||||
result = FTPUtilities.ftpFunctions(path, externalApp)
|
result = FTPUtilities.ftpFunctions(full_path, externalApp)
|
||||||
|
|
||||||
if result[0] == 1:
|
if result[0] == 1:
|
||||||
pass
|
path = full_path
|
||||||
else:
|
else:
|
||||||
raise BaseException(result[1])
|
raise BaseException("Path validation failed: " + result[1])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
path = "/home/" + domainName
|
path = "/home/" + domainName
|
||||||
|
|
||||||
|
# Enhanced symlink handling
|
||||||
if os.path.islink(path):
|
if os.path.islink(path):
|
||||||
print("0, %s file is symlinked." % (path))
|
logging.CyberCPLogFileWriter.writeToFile(
|
||||||
return 0
|
"FTP path is symlinked: %s" % path)
|
||||||
|
raise BaseException("Cannot create FTP account: Path is a symbolic link")
|
||||||
|
|
||||||
ProcessUtilities.decideDistro()
|
ProcessUtilities.decideDistro()
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ html {
|
|||||||
font-size: 16px; /* Base font size for better readability */
|
font-size: 16px; /* Base font size for better readability */
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
-ms-text-size-adjust: 100%;
|
-ms-text-size-adjust: 100%;
|
||||||
|
text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -22,7 +23,7 @@ body {
|
|||||||
|
|
||||||
/* Override any light text that might be hard to read */
|
/* Override any light text that might be hard to read */
|
||||||
.text-muted, .text-secondary, .text-light {
|
.text-muted, .text-secondary, .text-light {
|
||||||
color: #64748b !important; /* Darker gray instead of light gray */
|
color: #2f3640 !important; /* Dark text for better readability on white backgrounds */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix small font sizes that are hard to read */
|
/* Fix small font sizes that are hard to read */
|
||||||
@@ -294,7 +295,6 @@ p {
|
|||||||
/* Make tables horizontally scrollable */
|
/* Make tables horizontally scrollable */
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
@@ -502,14 +502,14 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
color: #64748b !important;
|
color: #2f3640 !important; /* Dark text for better readability */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fix any light text on light backgrounds */
|
/* Fix any light text on light backgrounds */
|
||||||
.bg-light .text-muted,
|
.bg-light .text-muted,
|
||||||
.bg-white .text-muted,
|
.bg-white .text-muted,
|
||||||
.panel .text-muted {
|
.panel .text-muted {
|
||||||
color: #64748b !important;
|
color: #2f3640 !important; /* Dark text for better readability */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure proper spacing for touch targets */
|
/* Ensure proper spacing for touch targets */
|
||||||
@@ -518,6 +518,43 @@ a, button, input, select, textarea {
|
|||||||
min-width: 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 */
|
/* Fix for small clickable elements */
|
||||||
.glyph-icon, .icon {
|
.glyph-icon, .icon {
|
||||||
min-width: 44px;
|
min-width: 44px;
|
||||||
|
|||||||
265
static/baseTemplate/assets/readability-fixes.css
Normal file
265
static/baseTemplate/assets/readability-fixes.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
240
test_ftp_fixes.py
Normal file
240
test_ftp_fixes.py
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
CyberPanel FTP Account Creation Test Script
|
||||||
|
This script tests the FTP account creation functionality with various path scenarios
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
# Add CyberPanel to path
|
||||||
|
sys.path.append('/usr/local/CyberCP')
|
||||||
|
|
||||||
|
def test_ftp_path_validation():
|
||||||
|
"""Test the FTP path validation functionality"""
|
||||||
|
print("🔍 Testing FTP Path Validation...")
|
||||||
|
|
||||||
|
# Import the FTP utilities
|
||||||
|
try:
|
||||||
|
from plogical.ftpUtilities import FTPUtilities
|
||||||
|
print("✅ Successfully imported FTPUtilities")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"❌ Failed to import FTPUtilities: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test cases for path validation
|
||||||
|
test_cases = [
|
||||||
|
# Valid paths
|
||||||
|
("docs", True, "Valid subdirectory"),
|
||||||
|
("public_html", True, "Valid public_html directory"),
|
||||||
|
("uploads/images", True, "Valid nested directory"),
|
||||||
|
("api/v1", True, "Valid API directory"),
|
||||||
|
("", True, "Empty path (home directory)"),
|
||||||
|
("None", True, "None path (home directory)"),
|
||||||
|
|
||||||
|
# Invalid paths
|
||||||
|
("../docs", False, "Path traversal with .."),
|
||||||
|
("~/docs", False, "Home directory reference"),
|
||||||
|
("/docs", False, "Absolute path"),
|
||||||
|
("docs;rm -rf /", False, "Command injection"),
|
||||||
|
("docs|cat /etc/passwd", False, "Pipe command injection"),
|
||||||
|
("docs&reboot", False, "Background command"),
|
||||||
|
("docs`whoami`", False, "Command substitution"),
|
||||||
|
("docs'rm -rf /'", False, "Single quote injection"),
|
||||||
|
('docs"rm -rf /"', False, "Double quote injection"),
|
||||||
|
("docs<malicious", False, "Input redirection"),
|
||||||
|
("docs>malicious", False, "Output redirection"),
|
||||||
|
("docs*", False, "Wildcard character"),
|
||||||
|
("docs?", False, "Wildcard character"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n📋 Running Path Validation Tests:")
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for path, should_pass, description in test_cases:
|
||||||
|
try:
|
||||||
|
# Mock the external dependencies
|
||||||
|
with patch('pwd.getpwnam') as mock_pwd, \
|
||||||
|
patch('grp.getgrnam') as mock_grp, \
|
||||||
|
patch('os.path.exists') as mock_exists, \
|
||||||
|
patch('os.path.isdir') as mock_isdir, \
|
||||||
|
patch('os.path.islink') as mock_islink, \
|
||||||
|
patch('plogical.processUtilities.ProcessUtilities.executioner') as mock_exec, \
|
||||||
|
patch('websiteFunctions.models.Websites.objects.get') as mock_website, \
|
||||||
|
patch('loginSystem.models.Administrator.objects.get') as mock_admin:
|
||||||
|
|
||||||
|
# Setup mocks
|
||||||
|
mock_pwd.return_value.pw_uid = 1000
|
||||||
|
mock_grp.return_value.gr_gid = 1000
|
||||||
|
mock_exists.return_value = True
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
mock_islink.return_value = False
|
||||||
|
mock_exec.return_value = 0
|
||||||
|
|
||||||
|
# Mock website object
|
||||||
|
mock_website_obj = MagicMock()
|
||||||
|
mock_website_obj.externalApp = "testuser"
|
||||||
|
mock_website_obj.package.diskSpace = 1000
|
||||||
|
mock_website_obj.package.ftpAccounts = 10
|
||||||
|
mock_website_obj.users_set.all.return_value.count.return_value = 0
|
||||||
|
mock_website.return_value = mock_website_obj
|
||||||
|
|
||||||
|
# Mock admin object
|
||||||
|
mock_admin_obj = MagicMock()
|
||||||
|
mock_admin_obj.userName = "testadmin"
|
||||||
|
mock_admin.return_value = mock_admin_obj
|
||||||
|
|
||||||
|
# Test the path validation
|
||||||
|
result = FTPUtilities.submitFTPCreation(
|
||||||
|
"testdomain.com",
|
||||||
|
"testuser",
|
||||||
|
"testpass",
|
||||||
|
path,
|
||||||
|
"testadmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_pass:
|
||||||
|
if result[0] == 1:
|
||||||
|
print(f"✅ PASS: {description} ('{path}')")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
print(f"❌ FAIL: {description} ('{path}') - Expected success but got: {result[1]}")
|
||||||
|
failed += 1
|
||||||
|
else:
|
||||||
|
if result[0] == 0:
|
||||||
|
print(f"✅ PASS: {description} ('{path}') - Correctly rejected")
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
print(f"❌ FAIL: {description} ('{path}') - Expected rejection but got success")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if should_pass:
|
||||||
|
print(f"❌ ERROR: {description} ('{path}') - Unexpected error: {e}")
|
||||||
|
failed += 1
|
||||||
|
else:
|
||||||
|
print(f"✅ PASS: {description} ('{path}') - Correctly rejected with error: {e}")
|
||||||
|
passed += 1
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print(f"📊 Test Results: {passed} passed, {failed} failed")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
return failed == 0
|
||||||
|
|
||||||
|
def test_directory_creation():
|
||||||
|
"""Test directory creation functionality"""
|
||||||
|
print("\n🔍 Testing Directory Creation...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from plogical.ftpUtilities import FTPUtilities
|
||||||
|
|
||||||
|
# Create a temporary directory for testing
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
test_path = os.path.join(temp_dir, "test_ftp_dir")
|
||||||
|
|
||||||
|
print(f"📁 Testing directory creation at: {test_path}")
|
||||||
|
|
||||||
|
# Test creating a new directory
|
||||||
|
result = FTPUtilities.ftpFunctions(test_path, "testuser")
|
||||||
|
|
||||||
|
if result[0] == 1:
|
||||||
|
if os.path.exists(test_path) and os.path.isdir(test_path):
|
||||||
|
print("✅ Directory creation successful")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("❌ Directory creation failed - directory not found")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print(f"❌ Directory creation failed: {result[1]}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Directory creation test failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_security_features():
|
||||||
|
"""Test security features"""
|
||||||
|
print("\n🔍 Testing Security Features...")
|
||||||
|
|
||||||
|
security_tests = [
|
||||||
|
("Path traversal prevention", ".."),
|
||||||
|
("Home directory reference prevention", "~"),
|
||||||
|
("Absolute path prevention", "/etc/passwd"),
|
||||||
|
("Command injection prevention", ";rm -rf /"),
|
||||||
|
("Pipe command prevention", "|cat /etc/passwd"),
|
||||||
|
("Background command prevention", "&reboot"),
|
||||||
|
("Command substitution prevention", "`whoami`"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("🛡️ Security Test Results:")
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
for test_name, malicious_path in security_tests:
|
||||||
|
try:
|
||||||
|
# This should be caught by our validation
|
||||||
|
if any(char in malicious_path for char in ['..', '~', '/', ';', '|', '&', '`']):
|
||||||
|
print(f"✅ {test_name}: Correctly detected malicious path")
|
||||||
|
else:
|
||||||
|
print(f"❌ {test_name}: Failed to detect malicious path")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✅ {test_name}: Correctly rejected with error: {e}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main test function"""
|
||||||
|
print("🚀 CyberPanel FTP Account Creation Test Suite")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Run all tests
|
||||||
|
tests = [
|
||||||
|
("Path Validation", test_ftp_path_validation),
|
||||||
|
("Directory Creation", test_directory_creation),
|
||||||
|
("Security Features", test_security_features),
|
||||||
|
]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for test_name, test_func in tests:
|
||||||
|
print(f"\n🧪 Running {test_name} Test...")
|
||||||
|
try:
|
||||||
|
result = test_func()
|
||||||
|
results.append((test_name, result))
|
||||||
|
if result:
|
||||||
|
print(f"✅ {test_name} Test: PASSED")
|
||||||
|
else:
|
||||||
|
print(f"❌ {test_name} Test: FAILED")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ {test_name} Test: ERROR - {e}")
|
||||||
|
results.append((test_name, False))
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("📋 TEST SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
passed = sum(1 for _, result in results if result)
|
||||||
|
total = len(results)
|
||||||
|
|
||||||
|
for test_name, result in results:
|
||||||
|
status = "✅ PASSED" if result else "❌ FAILED"
|
||||||
|
print(f"{test_name}: {status}")
|
||||||
|
|
||||||
|
print(f"\n🎯 Overall Result: {passed}/{total} tests passed")
|
||||||
|
|
||||||
|
if passed == total:
|
||||||
|
print("🎉 All tests passed! FTP account creation should work correctly.")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print("⚠️ Some tests failed. Please review the issues above.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit(main())
|
||||||
Reference in New Issue
Block a user