Implement remote connection handling and progress tracking for backup operations: Introduce a method for attempting connections to remote CyberPanel servers with port fallback. Enhance the frontend to display detailed progress and logs during backup transfers, including error handling and notifications for port fallback usage. Update HTML and JavaScript to support new progress tracking features and improve user feedback during backup and restore processes.

This commit is contained in:
Master3395
2025-09-20 21:31:41 +02:00
parent 70e76967ec
commit 3032dff01d
4 changed files with 639 additions and 71 deletions

View File

@@ -38,6 +38,33 @@ from django.http import JsonResponse
class BackupManager: class BackupManager:
localBackupPath = '/home/cyberpanel/localBackupPath' localBackupPath = '/home/cyberpanel/localBackupPath'
@staticmethod
def _try_remote_connection(ipAddress, password, endpoint, cyberPanelPort=8090, timeout=10):
"""
Try to connect to remote CyberPanel server with port fallback.
Returns: (success, data, used_port, error_message)
"""
import requests
import json
ports_to_try = [cyberPanelPort, 8090] if cyberPanelPort != 8090 else [8090]
finalData = json.dumps({'username': "admin", "password": password})
for port in ports_to_try:
try:
url = f"https://{ipAddress}:{port}{endpoint}"
r = requests.post(url, data=finalData, verify=False, timeout=timeout)
data = json.loads(r.text)
return True, data, port, None
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout,
requests.exceptions.RequestException, json.JSONDecodeError) as e:
if port == ports_to_try[-1]: # Last port failed
return False, None, None, f"Could not connect to remote server on any port. Tried ports: {', '.join(map(str, ports_to_try))}. Last error: {str(e)}"
continue
return False, None, None, "Connection failed on all ports"
def __init__(self, domain=None, childDomain=None): def __init__(self, domain=None, childDomain=None):
self.domain = domain self.domain = domain
@@ -1084,33 +1111,35 @@ class BackupManager:
ipAddress = data['ipAddress'] ipAddress = data['ipAddress']
password = data['password'] password = data['password']
cyberPanelPort = data.get('cyberPanelPort', 8090) # Default to 8090 if not provided
## Ask for Remote version of CyberPanel ## Ask for Remote version of CyberPanel
try: try:
finalData = json.dumps({'username': "admin", "password": password}) # Try to connect with port fallback
success, data, used_port, error_msg = BackupManager._try_remote_connection(
url = "https://" + ipAddress + ":8090/api/cyberPanelVersion" ipAddress, password, "/api/cyberPanelVersion", cyberPanelPort
)
r = requests.post(url, data=finalData, verify=False)
if not success:
data = json.loads(r.text) data_ret = {'status': 0, 'error_message': error_msg, "dir": "Null"}
data_ret = json.dumps(data_ret)
if data['getVersion'] == 1: return HttpResponse(data_ret)
if float(data['currentVersion']) >= 1.6 and data['build'] >= 0: if data['getVersion'] != 1:
pass
else:
data_ret = {'status': 0,
'error_message': "Your version does not match with version of remote server.",
"dir": "Null"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
else:
data_ret = {'status': 0, data_ret = {'status': 0,
'error_message': "Not able to fetch version of remote server. Error Message: " + 'error_message': "Not able to fetch version of remote server. Error Message: " +
data[ data.get('error_message', 'Unknown error'), "dir": "Null"}
'error_message'], "dir": "Null"} data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
# Check version compatibility
if float(data['currentVersion']) >= 1.6 and data['build'] >= 0:
pass
else:
data_ret = {'status': 0,
'error_message': "Your version does not match with version of remote server.",
"dir": "Null"}
data_ret = json.dumps(data_ret) data_ret = json.dumps(data_ret)
return HttpResponse(data_ret) return HttpResponse(data_ret)
@@ -1125,11 +1154,13 @@ class BackupManager:
## Fetch public key of remote server! ## Fetch public key of remote server!
finalData = json.dumps({'username': "admin", "password": password}) success, data, used_port, error_msg = BackupManager._try_remote_connection(
ipAddress, password, "/api/fetchSSHkey", used_port
url = "https://" + ipAddress + ":8090/api/fetchSSHkey" )
r = requests.post(url, data=finalData, verify=False)
data = json.loads(r.text) if not success:
final_json = json.dumps({'status': 0, 'error_message': error_msg})
return HttpResponse(final_json)
if data['pubKeyStatus'] == 1: if data['pubKeyStatus'] == 1:
pubKey = data["pubKey"].strip("\n") pubKey = data["pubKey"].strip("\n")
@@ -1167,18 +1198,19 @@ class BackupManager:
## ##
try: try:
finalData = json.dumps({'username': "admin", "password": password}) success, data, used_port, error_msg = BackupManager._try_remote_connection(
ipAddress, password, "/api/fetchAccountsFromRemoteServer", used_port
url = "https://" + ipAddress + ":8090/api/fetchAccountsFromRemoteServer" )
r = requests.post(url, data=finalData, verify=False) if not success:
data_ret = {'status': 0, 'error_message': error_msg, "dir": "Null"}
data = json.loads(r.text) data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
if data['fetchStatus'] == 1: if data['fetchStatus'] == 1:
json_data = data['data'] json_data = data['data']
data_ret = {'status': 1, 'error_message': "None", data_ret = {'status': 1, 'error_message': "None",
"dir": "Null", 'data': json_data} "dir": "Null", 'data': json_data, 'used_port': used_port}
data_ret = json.dumps(data_ret) data_ret = json.dumps(data_ret)
return HttpResponse(data_ret) return HttpResponse(data_ret)
else: else:
@@ -1208,6 +1240,7 @@ class BackupManager:
ipAddress = data['ipAddress'] ipAddress = data['ipAddress']
password = data['password'] password = data['password']
accountsToTransfer = data['accountsToTransfer'] accountsToTransfer = data['accountsToTransfer']
cyberPanelPort = data.get('cyberPanelPort', 8090) # Default to 8090 if not provided
try: try:
@@ -1240,9 +1273,32 @@ class BackupManager:
finalData = json.dumps({'username': "admin", "password": password, "ipAddress": ownIP, finalData = json.dumps({'username': "admin", "password": password, "ipAddress": ownIP,
"accountsToTransfer": accountsToTransfer, 'port': port}) "accountsToTransfer": accountsToTransfer, 'port': port})
url = "https://" + ipAddress + ":8090/api/remoteTransfer" # Try to connect with port fallback
ports_to_try = [cyberPanelPort, 8090] if cyberPanelPort != 8090 else [8090]
r = requests.post(url, data=finalData, verify=False) connection_successful = False
used_port = None
for port in ports_to_try:
try:
url = f"https://{ipAddress}:{port}/api/remoteTransfer"
r = requests.post(url, data=finalData, verify=False, timeout=10)
data = json.loads(r.text)
connection_successful = True
used_port = port
break
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout,
requests.exceptions.RequestException, json.JSONDecodeError) as e:
if port == ports_to_try[-1]: # Last port failed
data_ret = {'remoteTransferStatus': 0,
'error_message': f"Could not connect to remote server on any port. Tried ports: {', '.join(map(str, ports_to_try))}. Last error: {str(e)}"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
continue
if not connection_successful:
data_ret = {'remoteTransferStatus': 0, 'error_message': "Connection failed on all ports"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
if os.path.exists('/usr/local/CyberCP/debug'): if os.path.exists('/usr/local/CyberCP/debug'):
message = 'Remote transfer initiation status: %s' % (r.text) message = 'Remote transfer initiation status: %s' % (r.text)
@@ -1302,12 +1358,36 @@ class BackupManager:
password = data['password'] password = data['password']
dir = data['dir'] dir = data['dir']
username = "admin" username = "admin"
cyberPanelPort = data.get('cyberPanelPort', 8090) # Default to 8090 if not provided
finalData = json.dumps({'dir': dir, "username": username, "password": password}) finalData = json.dumps({'dir': dir, "username": username, "password": password})
r = requests.post("https://" + ipAddress + ":8090/api/FetchRemoteTransferStatus", data=finalData,
verify=False) # Try to connect with port fallback
ports_to_try = [cyberPanelPort, 8090] if cyberPanelPort != 8090 else [8090]
data = json.loads(r.text) connection_successful = False
used_port = None
for port in ports_to_try:
try:
url = f"https://{ipAddress}:{port}/api/FetchRemoteTransferStatus"
r = requests.post(url, data=finalData, verify=False, timeout=10)
data = json.loads(r.text)
connection_successful = True
used_port = port
break
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout,
requests.exceptions.RequestException, json.JSONDecodeError) as e:
if port == ports_to_try[-1]: # Last port failed
data_ret = {'remoteTransferStatus': 0,
'error_message': f"Could not connect to remote server on any port. Tried ports: {', '.join(map(str, ports_to_try))}. Last error: {str(e)}"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
continue
if not connection_successful:
data_ret = {'remoteTransferStatus': 0, 'error_message': "Connection failed on all ports"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
if data['fetchStatus'] == 1: if data['fetchStatus'] == 1:
if data['status'].find("Backups are successfully generated and received on") > -1: if data['status'].find("Backups are successfully generated and received on") > -1:
@@ -1429,12 +1509,36 @@ class BackupManager:
password = data['password'] password = data['password']
dir = data['dir'] dir = data['dir']
username = "admin" username = "admin"
cyberPanelPort = data.get('cyberPanelPort', 8090) # Default to 8090 if not provided
finalData = json.dumps({'dir': dir, "username": username, "password": password}) finalData = json.dumps({'dir': dir, "username": username, "password": password})
r = requests.post("https://" + ipAddress + ":8090/api/cancelRemoteTransfer", data=finalData,
verify=False) # Try to connect with port fallback
ports_to_try = [cyberPanelPort, 8090] if cyberPanelPort != 8090 else [8090]
data = json.loads(r.text) connection_successful = False
used_port = None
for port in ports_to_try:
try:
url = f"https://{ipAddress}:{port}/api/cancelRemoteTransfer"
r = requests.post(url, data=finalData, verify=False, timeout=10)
data = json.loads(r.text)
connection_successful = True
used_port = port
break
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout,
requests.exceptions.RequestException, json.JSONDecodeError) as e:
if port == ports_to_try[-1]: # Last port failed
data_ret = {'cancelStatus': 0,
'error_message': f"Could not connect to remote server on any port. Tried ports: {', '.join(map(str, ports_to_try))}. Last error: {str(e)}"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
continue
if not connection_successful:
data_ret = {'cancelStatus': 0, 'error_message': "Connection failed on all ports"}
data_ret = json.dumps(data_ret)
return HttpResponse(data_ret)
if data['cancelStatus'] == 1: if data['cancelStatus'] == 1:
pass pass

View File

@@ -742,6 +742,18 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
$scope.transferBoxBtn = true; $scope.transferBoxBtn = true;
$scope.stopTransferbtn = true; $scope.stopTransferbtn = true;
$scope.fetchAccountsBtn = false; $scope.fetchAccountsBtn = false;
// Progress tracking variables
$scope.overallProgress = 0;
$scope.currentStep = 0;
$scope.transferInProgress = false;
$scope.transferCompleted = false;
$scope.transferError = false;
$scope.downloadStatus = "Waiting...";
$scope.transferStatus = "Waiting...";
$scope.restoreStatus = "Waiting...";
$scope.logEntries = [];
$scope.showLog = false;
// notifications boxes // notifications boxes
@@ -765,6 +777,61 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
$scope.passwordEnter = function () { $scope.passwordEnter = function () {
$scope.backupButton = false; $scope.backupButton = false;
}; };
// Progress tracking functions
$scope.addLogEntry = function(message, type = 'info') {
$scope.logEntries.push({
timestamp: new Date(),
message: message,
type: type
});
// Keep only last 100 log entries
if ($scope.logEntries.length > 100) {
$scope.logEntries = $scope.logEntries.slice(-100);
}
// Auto-scroll to bottom
setTimeout(function() {
var logOutput = document.getElementById('logOutput');
if (logOutput) {
logOutput.scrollTop = logOutput.scrollHeight;
}
}, 100);
};
$scope.updateProgress = function(step, progress, status) {
$scope.currentStep = step;
$scope.overallProgress = progress;
switch(step) {
case 1:
$scope.downloadStatus = status;
break;
case 2:
$scope.transferStatus = status;
break;
case 3:
$scope.restoreStatus = status;
break;
}
};
$scope.toggleLog = function() {
$scope.showLog = !$scope.showLog;
};
$scope.resetProgress = function() {
$scope.overallProgress = 0;
$scope.currentStep = 0;
$scope.transferInProgress = false;
$scope.transferCompleted = false;
$scope.transferError = false;
$scope.downloadStatus = "Waiting...";
$scope.transferStatus = "Waiting...";
$scope.restoreStatus = "Waiting...";
$scope.logEntries = [];
};
$scope.addRemoveWebsite = function (website, websiteStatus) { $scope.addRemoveWebsite = function (website, websiteStatus) {
@@ -819,12 +886,14 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
var IPAddress = $scope.IPAddress; var IPAddress = $scope.IPAddress;
var password = $scope.password; var password = $scope.password;
var cyberPanelPort = $scope.cyberPanelPort || 8090; // Default to 8090 if not specified
url = "/backup/submitRemoteBackups"; url = "/backup/submitRemoteBackups";
var data = { var data = {
ipAddress: IPAddress, ipAddress: IPAddress,
password: password, password: password,
cyberPanelPort: cyberPanelPort,
}; };
var config = { var config = {
@@ -860,6 +929,16 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
$scope.accountsFetched = false; $scope.accountsFetched = false;
$scope.backupProcessStarted = true; $scope.backupProcessStarted = true;
$scope.backupCancelled = true; $scope.backupCancelled = true;
// Show fallback port notification if used
if (response.data.used_port && response.data.used_port != $scope.cyberPanelPort) {
new PNotify({
title: 'Port Fallback Used',
text: `Connected using port ${response.data.used_port} (fallback from ${$scope.cyberPanelPort})`,
type: 'info',
delay: 5000
});
}
} else { } else {
@@ -893,6 +972,10 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
}; };
$scope.startTransfer = function () { $scope.startTransfer = function () {
// Reset progress tracking
$scope.resetProgress();
$scope.transferInProgress = true;
$scope.addLogEntry("Starting remote backup transfer...", "info");
// notifications boxes // notifications boxes
$scope.notificationsBox = true; $scope.notificationsBox = true;
@@ -915,12 +998,14 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
var IPAddress = $scope.IPAddress; var IPAddress = $scope.IPAddress;
var password = $scope.password; var password = $scope.password;
var cyberPanelPort = $scope.cyberPanelPort || 8090; // Default to 8090 if not specified
url = "/backup/starRemoteTransfer"; url = "/backup/starRemoteTransfer";
var data = { var data = {
ipAddress: IPAddress, ipAddress: IPAddress,
password: password, password: password,
cyberPanelPort: cyberPanelPort,
accountsToTransfer: websitesToBeBacked, accountsToTransfer: websitesToBeBacked,
}; };
@@ -1002,6 +1087,7 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
var data = { var data = {
password: $scope.password, password: $scope.password,
ipAddress: $scope.IPAddress, ipAddress: $scope.IPAddress,
cyberPanelPort: $scope.cyberPanelPort || 8090,
dir: tempTransferDir dir: tempTransferDir
}; };
@@ -1021,13 +1107,31 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
if (response.data.backupsSent === 0) { if (response.data.backupsSent === 0) {
$scope.backupStatus = false; $scope.backupStatus = false;
$scope.requestData = response.data.status; $scope.requestData = response.data.status;
// Update progress based on status content
var status = response.data.status;
if (status) {
$scope.addLogEntry(status, "info");
// Parse status for progress updates
if (status.includes("Backup process started") || status.includes("Generating backup")) {
$scope.updateProgress(1, 25, "Generating backups on remote server...");
} else if (status.includes("Transferring") || status.includes("Sending backup")) {
$scope.updateProgress(2, 50, "Transferring backup files...");
} else if (status.includes("Backup received") || status.includes("Downloading")) {
$scope.updateProgress(2, 75, "Downloading backup files...");
}
}
$timeout(getBackupStatus, 2000); $timeout(getBackupStatus, 2000);
} else { } else {
$scope.requestData = response.data.status; $scope.requestData = response.data.status;
$scope.addLogEntry("Backup transfer completed successfully!", "success");
$scope.updateProgress(2, 100, "Transfer completed");
$timeout.cancel(); $timeout.cancel();
// Start the restore of remote backups that are transferred to local server // Start the restore of remote backups that are transferred to local server
$scope.addLogEntry("Starting local restore process...", "info");
remoteBackupRestore(); remoteBackupRestore();
} }
} else { } else {
@@ -1035,6 +1139,8 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
$scope.error_message = response.data.error_message; $scope.error_message = response.data.error_message;
$scope.backupLoading = true; $scope.backupLoading = true;
$scope.couldNotConnect = true; $scope.couldNotConnect = true;
$scope.transferError = true;
$scope.addLogEntry("Transfer failed: " + response.data.error_message, "error");
// Notifications box settings // Notifications box settings
@@ -1077,7 +1183,12 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
function ListInitialDatas(response) { function ListInitialDatas(response) {
if (response.data.remoteRestoreStatus === 1) { if (response.data.remoteRestoreStatus === 1) {
$scope.addLogEntry("Remote restore initiated successfully", "success");
$scope.updateProgress(3, 85, "Restoring websites...");
localRestoreStatus(); localRestoreStatus();
} else {
$scope.addLogEntry("Remote restore failed: " + (response.data.error_message || "Unknown error"), "error");
$scope.transferError = true;
} }
} }
@@ -1121,9 +1232,31 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
if (response.data.complete === 0) { if (response.data.complete === 0) {
$scope.backupStatus = false; $scope.backupStatus = false;
$scope.restoreData = response.data.status; $scope.restoreData = response.data.status;
// Update restore progress
var status = response.data.status;
if (status) {
$scope.addLogEntry(status, "info");
if (status.includes("completed[success]")) {
$scope.updateProgress(3, 100, "Restore completed successfully!");
$scope.transferCompleted = true;
$scope.transferInProgress = false;
$scope.addLogEntry("All websites restored successfully!", "success");
} else if (status.includes("Error") || status.includes("error")) {
$scope.addLogEntry("Restore error: " + status, "error");
} else {
$scope.updateProgress(3, 90, "Finalizing restore...");
}
}
$timeout(localRestoreStatus, 2000); $timeout(localRestoreStatus, 2000);
} else { } else {
$scope.restoreData = response.data.status; $scope.restoreData = response.data.status;
$scope.updateProgress(3, 100, "Restore completed!");
$scope.transferCompleted = true;
$scope.transferInProgress = false;
$scope.addLogEntry("Restore process completed successfully!", "success");
$timeout.cancel(); $timeout.cancel();
$scope.backupLoading = true; $scope.backupLoading = true;
$scope.startTransferbtn = false; $scope.startTransferbtn = false;
@@ -1133,6 +1266,8 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
$scope.error_message = response.data.error_message; $scope.error_message = response.data.error_message;
$scope.backupLoading = true; $scope.backupLoading = true;
$scope.couldNotConnect = true; $scope.couldNotConnect = true;
$scope.transferError = true;
$scope.addLogEntry("Restore failed: " + response.data.error_message, "error");
// Notifications box settings // Notifications box settings
@@ -1163,6 +1298,7 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
var data = { var data = {
password: $scope.password, password: $scope.password,
ipAddress: $scope.IPAddress, ipAddress: $scope.IPAddress,
cyberPanelPort: $scope.cyberPanelPort || 8090,
dir: tempTransferDir, dir: tempTransferDir,
}; };
@@ -1215,12 +1351,14 @@ app.controller('remoteBackupControl', function ($scope, $http, $timeout) {
var IPAddress = $scope.IPAddress; var IPAddress = $scope.IPAddress;
var password = $scope.password; var password = $scope.password;
var cyberPanelPort = $scope.cyberPanelPort || 8090;
url = "/backup/cancelRemoteBackup"; url = "/backup/cancelRemoteBackup";
var data = { var data = {
ipAddress: IPAddress, ipAddress: IPAddress,
password: password, password: password,
cyberPanelPort: cyberPanelPort,
dir: tempTransferDir, dir: tempTransferDir,
}; };

View File

@@ -381,6 +381,251 @@
background: var(--success-color, #10b981); background: var(--success-color, #10b981);
} }
/* Progress Section Styles */
.progress-section {
background: var(--bg-primary, #fff);
border-radius: 15px;
box-shadow: 0 4px 20px var(--shadow-light, rgba(0, 0, 0, 0.1));
margin-top: 2rem;
overflow: hidden;
animation: fadeInUp 0.5s ease-out;
}
.progress-header {
background: linear-gradient(135deg, var(--accent-color, #5b5fcf) 0%, var(--accent-hover, #4547a9) 100%);
color: white;
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.progress-title {
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
}
.status-badge {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-progress {
background: rgba(255, 255, 255, 0.2);
color: white;
}
.status-completed {
background: #10b981;
color: white;
}
.status-error {
background: #ef4444;
color: white;
}
.progress-overview {
padding: 2rem;
background: var(--bg-secondary, #f8fafc);
}
.progress-bar-container {
position: relative;
background: var(--bg-primary, #fff);
border-radius: 10px;
padding: 1rem;
box-shadow: 0 2px 10px var(--shadow-light, rgba(0, 0, 0, 0.05));
}
.progress-bar {
width: 100%;
height: 20px;
background: #e2e8f0;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-color, #5b5fcf) 0%, var(--accent-hover, #4547a9) 100%);
border-radius: 10px;
transition: width 0.5s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.progress-text {
text-align: center;
margin-top: 0.75rem;
font-weight: 600;
color: var(--text-primary, #1e293b);
font-size: 1.1rem;
}
.progress-details {
padding: 2rem;
}
.progress-step {
display: flex;
align-items: center;
padding: 1.5rem 0;
border-bottom: 1px solid var(--border-light, #e2e8f0);
opacity: 0.5;
transition: all 0.3s ease;
}
.progress-step:last-child {
border-bottom: none;
}
.progress-step.active {
opacity: 1;
background: var(--bg-hover, #f8f9ff);
margin: 0 -2rem;
padding: 1.5rem 2rem;
border-radius: 10px;
}
.progress-step.completed {
opacity: 1;
}
.step-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1.5rem;
font-size: 1.25rem;
transition: all 0.3s ease;
}
.progress-step:not(.active):not(.completed) .step-icon {
background: var(--bg-secondary, #f1f5f9);
color: var(--text-secondary, #64748b);
}
.progress-step.active .step-icon {
background: var(--accent-color, #5b5fcf);
color: white;
animation: pulse 2s infinite;
}
.progress-step.completed .step-icon {
background: #10b981;
color: white;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.step-content {
flex: 1;
}
.step-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary, #1e293b);
margin-bottom: 0.25rem;
}
.step-description {
color: var(--text-secondary, #64748b);
font-size: 0.9rem;
}
.log-section {
border-top: 1px solid var(--border-light, #e2e8f0);
background: var(--bg-dark, #1e293b);
color: var(--text-light, #e2e8f0);
}
.log-header {
padding: 1rem 2rem;
border-bottom: 1px solid var(--border-dark, #334155);
display: flex;
justify-content: space-between;
align-items: center;
}
.log-header h4 {
margin: 0;
color: var(--text-light, #e2e8f0);
display: flex;
align-items: center;
gap: 0.5rem;
}
.log-content {
max-height: 300px;
overflow-y: auto;
}
.log-output {
padding: 1rem 2rem;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.875rem;
line-height: 1.5;
}
.log-entry {
margin-bottom: 0.5rem;
display: flex;
gap: 1rem;
}
.log-time {
color: var(--text-muted, #94a3b8);
min-width: 80px;
}
.log-message {
flex: 1;
}
.log-entry.log-error .log-message {
color: #ef4444;
}
.log-entry.log-success .log-message {
color: #10b981;
}
.log-entry.log-info .log-message {
color: #3b82f6;
}
.terminal-body { .terminal-body {
padding: 1.5rem; padding: 1.5rem;
height: 350px; height: 350px;
@@ -525,7 +770,7 @@
<div class="card-body"> <div class="card-body">
<form action="/" method="post"> <form action="/" method="post">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-4">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
<i class="fas fa-map-marker-alt" style="margin-right: 0.5rem;"></i> <i class="fas fa-map-marker-alt" style="margin-right: 0.5rem;"></i>
@@ -535,7 +780,20 @@
placeholder="192.168.1.100" required> placeholder="192.168.1.100" required>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-4">
<div class="form-group">
<label class="form-label">
<i class="fas fa-network-wired" style="margin-right: 0.5rem;"></i>
{% trans "CyberPanel Port" %}
</label>
<input type="number" class="form-control" ng-model="cyberPanelPort"
placeholder="8090" min="1" max="65535" required>
<small class="form-text text-muted">
{% trans "Port where CyberPanel is running on remote server (default: 8090)" %}
</small>
</div>
</div>
<div class="col-md-4">
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
<i class="fas fa-key" style="margin-right: 0.5rem;"></i> <i class="fas fa-key" style="margin-right: 0.5rem;"></i>
@@ -658,32 +916,100 @@
</div> </div>
</div> </div>
<!-- Transfer Progress Terminal --> <!-- Transfer Progress Section -->
<div ng-hide="backupStatus" class="terminal-section"> <div ng-hide="backupStatus" class="progress-section">
<div class="terminal-header"> <div class="progress-header">
<div class="terminal-title"> <div class="progress-title">
<i class="fas fa-terminal"></i> <i class="fas fa-sync-alt fa-spin" ng-if="transferInProgress"></i>
<i class="fas fa-check-circle" ng-if="transferCompleted"></i>
<i class="fas fa-exclamation-triangle" ng-if="transferError"></i>
{% trans "Transfer Progress" %} {% trans "Transfer Progress" %}
</div> </div>
<div class="terminal-dots"> <div class="progress-status">
<div class="terminal-dot red"></div> <span ng-if="transferInProgress" class="status-badge status-progress">
<div class="terminal-dot yellow"></div> <i class="fas fa-clock"></i> {% trans "In Progress" %}
<div class="terminal-dot green"></div> </span>
<span ng-if="transferCompleted" class="status-badge status-completed">
<i class="fas fa-check"></i> {% trans "Completed" %}
</span>
<span ng-if="transferError" class="status-badge status-error">
<i class="fas fa-times"></i> {% trans "Error" %}
</span>
</div> </div>
</div> </div>
<div class="terminal-body">
<div class="terminal-grid"> <!-- Overall Progress Bar -->
<div> <div class="progress-overview">
<h4 style="color: var(--text-secondary, #94a3b8); margin-bottom: 1rem; font-size: 0.875rem;"> <div class="progress-bar-container">
{% trans "Backup Progress" %} <div class="progress-bar">
</h4> <div class="progress-fill" ng-style="{'width': overallProgress + '%'}"></div>
<div ng-bind="requestData" style="white-space: pre-wrap;"></div>
</div> </div>
<div> <div class="progress-text">
<h4 style="color: var(--text-secondary, #94a3b8); margin-bottom: 1rem; font-size: 0.875rem;"> <span ng-if="overallProgress < 100">{{ overallProgress }}%</span>
{% trans "Restore Progress" %} <span ng-if="overallProgress >= 100">{% trans "Complete!" %}</span>
</h4> </div>
<div ng-bind="restoreData" style="white-space: pre-wrap;"></div> </div>
</div>
<!-- Detailed Progress -->
<div class="progress-details">
<div class="progress-step" ng-class="{'active': currentStep >= 1, 'completed': currentStep > 1}">
<div class="step-icon">
<i class="fas fa-download" ng-if="currentStep < 1"></i>
<i class="fas fa-spinner fa-spin" ng-if="currentStep === 1"></i>
<i class="fas fa-check" ng-if="currentStep > 1"></i>
</div>
<div class="step-content">
<div class="step-title">{% trans "Downloading Backups" %}</div>
<div class="step-description">{{ downloadStatus }}</div>
</div>
</div>
<div class="progress-step" ng-class="{'active': currentStep >= 2, 'completed': currentStep > 2}">
<div class="step-icon">
<i class="fas fa-upload" ng-if="currentStep < 2"></i>
<i class="fas fa-spinner fa-spin" ng-if="currentStep === 2"></i>
<i class="fas fa-check" ng-if="currentStep > 2"></i>
</div>
<div class="step-content">
<div class="step-title">{% trans "Transferring Data" %}</div>
<div class="step-description">{{ transferStatus }}</div>
</div>
</div>
<div class="progress-step" ng-class="{'active': currentStep >= 3, 'completed': currentStep > 3}">
<div class="step-icon">
<i class="fas fa-cogs" ng-if="currentStep < 3"></i>
<i class="fas fa-spinner fa-spin" ng-if="currentStep === 3"></i>
<i class="fas fa-check" ng-if="currentStep > 3"></i>
</div>
<div class="step-content">
<div class="step-title">{% trans "Restoring Websites" %}</div>
<div class="step-description">{{ restoreStatus }}</div>
</div>
</div>
</div>
<!-- Live Log Display -->
<div class="log-section">
<div class="log-header">
<h4>
<i class="fas fa-terminal"></i>
{% trans "Live Log" %}
<button class="btn btn-sm btn-outline-secondary" ng-click="toggleLog()">
<i class="fas" ng-class="showLog ? 'fa-eye-slash' : 'fa-eye'"></i>
{{ showLog ? 'Hide' : 'Show' }}
</button>
</h4>
</div>
<div class="log-content" ng-show="showLog">
<div class="log-output" id="logOutput">
<div ng-repeat="logEntry in logEntries track by $index"
class="log-entry"
ng-class="{'log-error': logEntry.type === 'error', 'log-success': logEntry.type === 'success', 'log-info': logEntry.type === 'info'}">
<span class="log-time">{{ logEntry.timestamp | date:'HH:mm:ss' }}</span>
<span class="log-message">{{ logEntry.message }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -150,10 +150,10 @@ class remoteBackup:
return [0, msg] return [0, msg]
@staticmethod @staticmethod
def postRemoteTransfer(ipAddress, ownIP ,password, sshkey): def postRemoteTransfer(ipAddress, ownIP ,password, sshkey, cyberPanelPort=8090):
try: try:
finalData = json.dumps({'username': "admin", "ipAddress": ownIP, "password": password}) finalData = json.dumps({'username': "admin", "ipAddress": ownIP, "password": password})
url = "https://" + ipAddress + ":8090/api/remoteTransfer" url = "https://" + ipAddress + ":" + str(cyberPanelPort) + "/api/remoteTransfer"
r = requests.post(url, data=finalData, verify=False) r = requests.post(url, data=finalData, verify=False)
data = json.loads(r.text) data = json.loads(r.text)