Add storage stats, last backup info, and error logs to ManageOCBackups page

- Add comprehensive backup account overview with visual stats cards
- Display storage usage (total, used, available, percentage) from platform API
- Show last backup run timestamp and status (success/failed)
- Display total backups count and failed backups count
- Add recent backup error logs table with timestamp, website, and error message
- Fetch all stats from platform.cyberpersons.com/Billing/GetBackupStats endpoint
- Beautiful gradient cards for visual presentation of stats
- Progress bar for storage usage visualization
- Conditional display of error logs (only shown if errors exist)
- Add account info card showing SFTP user and plan name
- Graceful fallback to N/A if platform API is unavailable
- Comprehensive error logging for API failures
This commit is contained in:
usmannasir
2025-10-14 19:11:38 +05:00
parent fb02243245
commit b6f20a6a5e
2 changed files with 157 additions and 4 deletions

View File

@@ -2119,8 +2119,53 @@ class BackupManager:
websitesName = ACLManager.findAllSites(currentACL, userID)
proc = httpProc(request, 'backup/OneClickBackupSchedule.html', {'destination': NormalBackupDests.objects.get(name=ocb.sftpUser).name, 'websites': websitesName},
'scheduleBackups')
# Fetch storage stats and backup info from platform API
storage_info = {
'total_storage': 'N/A',
'used_storage': 'N/A',
'available_storage': 'N/A',
'usage_percentage': 0,
'last_backup_run': 'Never',
'last_backup_status': 'N/A',
'total_backups': 0,
'failed_backups': 0,
'error_logs': []
}
try:
import requests
url = 'https://platform.cyberpersons.com/Billing/GetBackupStats'
payload = {
'sub': ocb.subscription,
'sftpUser': ocb.sftpUser,
'serverIP': ACLManager.fetchIP()
}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
if response.status_code == 200:
api_data = response.json()
if api_data.get('status') == 1:
storage_info = api_data.get('data', storage_info)
logging.CyberCPLogFileWriter.writeToFile(f'Successfully fetched backup stats for {ocb.sftpUser} [ManageOCBackups]')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Platform API returned error: {api_data.get("error_message")} [ManageOCBackups]')
else:
logging.CyberCPLogFileWriter.writeToFile(f'Platform API returned HTTP {response.status_code} [ManageOCBackups]')
except Exception as e:
logging.CyberCPLogFileWriter.writeToFile(f'Failed to fetch backup stats: {str(e)} [ManageOCBackups]')
context = {
'destination': NormalBackupDests.objects.get(name=ocb.sftpUser).name,
'websites': websitesName,
'storage_info': storage_info,
'ocb_subscription': ocb.subscription,
'ocb_plan_name': ocb.planName,
'ocb_sftp_user': ocb.sftpUser
}
proc = httpProc(request, 'backup/OneClickBackupSchedule.html', context, 'scheduleBackups')
return proc.render()
def RestoreOCBackups(self, request=None, userID=None, data=None):

View File

@@ -380,8 +380,8 @@
</h1>
<p class="page-subtitle">{% trans "Schedule automated backups to protect your data on localhost or remote server" %}</p>
<div class="header-actions">
<a href="https://cyberpanel.net/KnowledgeBase/home/schedule-backups-local-or-sftp/"
target="_blank"
<a href="https://cyberpanel.net/KnowledgeBase/home/schedule-backups-local-or-sftp/"
target="_blank"
class="btn-secondary">
<i class="fas fa-book"></i>
{% trans "Documentation" %}
@@ -389,6 +389,114 @@
</div>
</div>
<!-- Storage and Backup Stats Section -->
<div class="main-card" style="margin-bottom: 2rem;">
<div class="card-header">
<h2 class="card-title">
<i class="fas fa-chart-pie"></i>
{% trans "Backup Account Overview" %}
</h2>
</div>
<div class="card-body">
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem;">
<!-- Storage Stats -->
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 1.5rem; border-radius: 12px; color: white;">
<div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem;">
<i class="fas fa-hdd" style="font-size: 2rem; opacity: 0.9;"></i>
<div>
<div style="font-size: 0.875rem; opacity: 0.9;">{% trans "Storage Used" %}</div>
<div style="font-size: 1.5rem; font-weight: 700;">{{ storage_info.used_storage }}</div>
</div>
</div>
<div style="font-size: 0.875rem; opacity: 0.9;">{% trans "of" %} {{ storage_info.total_storage }}</div>
<div style="background: rgba(255,255,255,0.2); height: 8px; border-radius: 4px; margin-top: 0.75rem; overflow: hidden;">
<div style="background: rgba(255,255,255,0.9); height: 100%; width: {{ storage_info.usage_percentage }}%; transition: width 0.3s ease;"></div>
</div>
</div>
<!-- Last Backup -->
<div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 1.5rem; border-radius: 12px; color: white;">
<div style="display: flex; align-items: center; gap: 1rem;">
<i class="fas fa-clock" style="font-size: 2rem; opacity: 0.9;"></i>
<div>
<div style="font-size: 0.875rem; opacity: 0.9;">{% trans "Last Backup Run" %}</div>
<div style="font-size: 1.125rem; font-weight: 600;">{{ storage_info.last_backup_run }}</div>
<div style="font-size: 0.875rem; opacity: 0.9; margin-top: 0.25rem;">
{% if storage_info.last_backup_status == 'success' %}
<i class="fas fa-check-circle"></i> {% trans "Success" %}
{% elif storage_info.last_backup_status == 'failed' %}
<i class="fas fa-exclamation-circle"></i> {% trans "Failed" %}
{% else %}
{{ storage_info.last_backup_status }}
{% endif %}
</div>
</div>
</div>
</div>
<!-- Total Backups -->
<div style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 1.5rem; border-radius: 12px; color: white;">
<div style="display: flex; align-items: center; gap: 1rem;">
<i class="fas fa-database" style="font-size: 2rem; opacity: 0.9;"></i>
<div>
<div style="font-size: 0.875rem; opacity: 0.9;">{% trans "Total Backups" %}</div>
<div style="font-size: 1.5rem; font-weight: 700;">{{ storage_info.total_backups }}</div>
{% if storage_info.failed_backups > 0 %}
<div style="font-size: 0.875rem; opacity: 0.9; margin-top: 0.25rem;">
<i class="fas fa-exclamation-triangle"></i> {{ storage_info.failed_backups }} {% trans "failed" %}
</div>
{% endif %}
</div>
</div>
</div>
<!-- Account Info -->
<div style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); padding: 1.5rem; border-radius: 12px; color: white;">
<div style="display: flex; align-items: center; gap: 1rem;">
<i class="fas fa-user-circle" style="font-size: 2rem; opacity: 0.9;"></i>
<div>
<div style="font-size: 0.875rem; opacity: 0.9;">{% trans "Backup Account" %}</div>
<div style="font-size: 1.125rem; font-weight: 600;">{{ ocb_sftp_user }}</div>
<div style="font-size: 0.875rem; opacity: 0.9; margin-top: 0.25rem;">{{ ocb_plan_name }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Error Logs Section -->
{% if storage_info.error_logs %}
<div class="main-card" style="margin-bottom: 2rem;">
<div class="card-header" style="background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);">
<h2 class="card-title" style="color: #991b1b;">
<i class="fas fa-exclamation-triangle"></i>
{% trans "Recent Backup Errors" %}
</h2>
</div>
<div class="card-body" style="padding: 0;">
<table class="data-table">
<thead>
<tr>
<th style="width: 180px;">{% trans "Date/Time" %}</th>
<th style="width: 150px;">{% trans "Website" %}</th>
<th>{% trans "Error Message" %}</th>
</tr>
</thead>
<tbody>
{% for log in storage_info.error_logs %}
<tr>
<td>{{ log.timestamp }}</td>
<td><strong>{{ log.website }}</strong></td>
<td style="color: var(--danger-text, #991b1b);">{{ log.error_message }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div ng-controller="scheduleBackup">
<!-- Create New Schedule Card -->
<div class="main-card">