fix: use warning status when backup succeeds but remote upload fails
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Previously a successful backup with a failed remote upload was marked as "complete", hiding the upload failure. Now these records get a "warning" status with a yellow badge so operators can see at a glance which backups didn't reach their remote destination. Warning-status records are treated as usable backups throughout: - Downloadable, browsable, and restorable (the archive is intact) - Counted in dashboard stats, storage totals, and success streaks - Included in purge operations and differential base lookups - Shown with yellow "warning" badge in list, detail, and cpanel module - Filterable via the status dropdown on Backup Records Claude-Session: https://claude.ai/code/session_01MbEjBtsSjPuTWhqqrMS2wG
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
>
|
||||
<option value="">COM_MOKOJOOMBACKUP_FILTER_STATUS_ALL</option>
|
||||
<option value="complete">COM_MOKOJOOMBACKUP_STATUS_COMPLETE</option>
|
||||
<option value="warning">COM_MOKOJOOMBACKUP_STATUS_WARNING</option>
|
||||
<option value="running">COM_MOKOJOOMBACKUP_STATUS_RUNNING</option>
|
||||
<option value="fail">COM_MOKOJOOMBACKUP_STATUS_FAIL</option>
|
||||
<option value="pending">COM_MOKOJOOMBACKUP_STATUS_PENDING</option>
|
||||
|
||||
@@ -207,6 +207,7 @@ COM_MOKOJOOMBACKUP_TYPE_DIFFERENTIAL="Differential (changed files + full DB)"
|
||||
|
||||
; Status labels
|
||||
COM_MOKOJOOMBACKUP_STATUS_COMPLETE="Complete"
|
||||
COM_MOKOJOOMBACKUP_STATUS_WARNING="Warning"
|
||||
COM_MOKOJOOMBACKUP_STATUS_RUNNING="Running"
|
||||
COM_MOKOJOOMBACKUP_STATUS_FAIL="Failed"
|
||||
COM_MOKOJOOMBACKUP_STATUS_PENDING="Pending"
|
||||
|
||||
@@ -123,6 +123,9 @@ COM_MOKOJOOMBACKUP_CANCEL_NONE_SELECTED="No backup records selected."
|
||||
COM_MOKOJOOMBACKUP_CANCEL_NONE_RUNNING="None of the selected backups are in running status."
|
||||
COM_MOKOJOOMBACKUP_CANCEL_SUCCESS="%d stalled backup(s) cancelled."
|
||||
|
||||
; Backup status
|
||||
COM_MOKOJOOMBACKUP_STATUS_WARNING="Warning"
|
||||
|
||||
; ACL - Cancel
|
||||
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL="Cancel Stalled Backup"
|
||||
COM_MOKOSUITEBACKUP_ACTION_BACKUP_CANCEL_DESC="Allows users to cancel backup records stuck in running status and clean up partial archive files."
|
||||
|
||||
@@ -65,7 +65,7 @@ CREATE TABLE IF NOT EXISTS `#__mokosuitebackup_records` (
|
||||
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`profile_id` INT(11) UNSIGNED NOT NULL DEFAULT 1,
|
||||
`description` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, running, complete, fail',
|
||||
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending, running, complete, warning, fail',
|
||||
`origin` VARCHAR(20) NOT NULL DEFAULT 'backend' COMMENT 'backend, cli, api, scheduled',
|
||||
`backup_type` VARCHAR(20) NOT NULL DEFAULT 'full' COMMENT 'full, database, files',
|
||||
`archivename` VARCHAR(512) NOT NULL DEFAULT '',
|
||||
|
||||
@@ -512,7 +512,7 @@ class AjaxController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
if ($record->status !== 'complete' || !$record->filesexist) {
|
||||
if (!\in_array($record->status, ['complete', 'warning'], true) || !$record->filesexist) {
|
||||
$this->sendJson(['error' => true, 'message' => 'Archive not available']);
|
||||
|
||||
return;
|
||||
@@ -808,7 +808,7 @@ class AjaxController extends BaseController
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('backupstart') . ' < ' . $db->quote($cutoff))
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'));
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')');
|
||||
$db->setQuery($query);
|
||||
$count = (int) $db->loadResult();
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -199,7 +199,7 @@ class BackupsController extends AdminController
|
||||
->select($db->quoteName('id'))
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('backupstart') . ' < ' . $db->quote($cutoff))
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'));
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')');
|
||||
$db->setQuery($query);
|
||||
$ids = $db->loadColumn();
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ class BackupEngine
|
||||
// Final record update (includes fields needed by NotificationSender)
|
||||
$update = (object) [
|
||||
'id' => $recordId,
|
||||
'status' => 'complete',
|
||||
'status' => $uploadFailed ? 'warning' : 'complete',
|
||||
'description' => $description,
|
||||
'backup_type' => $profile->backup_type,
|
||||
'archivename' => $archiveName,
|
||||
@@ -606,7 +606,7 @@ class BackupEngine
|
||||
->select($db->quoteName('manifest'))
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('profile_id') . ' = ' . $profileId)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')')
|
||||
->where($db->quoteName('manifest') . ' != ' . $db->quote(''))
|
||||
->where($db->quoteName('backup_type') . ' = ' . $db->quote('full'))
|
||||
->order($db->quoteName('backupstart') . ' DESC');
|
||||
|
||||
@@ -165,7 +165,7 @@ class PreflightCheck
|
||||
->select($db->quoteName('total_size'))
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('profile_id') . ' = ' . (int) $profile->id)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'))
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')')
|
||||
->where($db->quoteName('total_size') . ' > 0')
|
||||
->order($db->quoteName('backupstart') . ' DESC');
|
||||
$db->setQuery($query, 0, 1);
|
||||
|
||||
@@ -67,7 +67,7 @@ class RestoreEngine
|
||||
return ['success' => false, 'message' => 'Backup record not found: ' . $recordId];
|
||||
}
|
||||
|
||||
if ($record->status !== 'complete') {
|
||||
if ($record->status !== 'complete' && $record->status !== 'warning') {
|
||||
return ['success' => false, 'message' => 'Cannot restore from incomplete backup (status: ' . $record->status . ')'];
|
||||
}
|
||||
|
||||
|
||||
@@ -647,7 +647,7 @@ class SteppedBackupEngine
|
||||
|
||||
$update = (object) [
|
||||
'id' => $session->recordId,
|
||||
'status' => 'complete',
|
||||
'status' => $uploadFailed ? 'warning' : 'complete',
|
||||
'backupend' => date('Y-m-d H:i:s'),
|
||||
'total_size' => $totalSize,
|
||||
'checksum' => $checksum,
|
||||
|
||||
@@ -64,7 +64,7 @@ class SteppedRestoreEngine
|
||||
return ['error' => true, 'message' => 'Backup record not found: ' . $recordId];
|
||||
}
|
||||
|
||||
if ($record->status !== 'complete') {
|
||||
if ($record->status !== 'complete' && $record->status !== 'warning') {
|
||||
return ['error' => true, 'message' => 'Cannot restore from incomplete backup (status: ' . $record->status . ')'];
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class BackupStatusHelper
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitebackup_records', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id')
|
||||
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
||||
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning', 'fail'])) . ')')
|
||||
->order($db->quoteName('r.backupstart') . ' DESC');
|
||||
|
||||
if ($profileId !== null) {
|
||||
@@ -148,7 +148,7 @@ class BackupStatusHelper
|
||||
$query = $db->getQuery(true)
|
||||
->select($db->quoteName('status'))
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'fail'])) . ')')
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning', 'fail'])) . ')')
|
||||
->order($db->quoteName('backupstart') . ' DESC')
|
||||
->setLimit(50);
|
||||
|
||||
@@ -156,7 +156,7 @@ class BackupStatusHelper
|
||||
$streak = 0;
|
||||
|
||||
foreach ($statuses as $s) {
|
||||
if ($s === 'complete') {
|
||||
if ($s === 'complete' || $s === 'warning') {
|
||||
$streak++;
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -30,7 +30,7 @@ class DashboardModel extends BaseDatabaseModel
|
||||
->select('r.*, p.title AS profile_title')
|
||||
->from($db->quoteName('#__mokosuitebackup_records', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id')
|
||||
->where($db->quoteName('r.status') . ' = ' . $db->quote('complete'))
|
||||
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')')
|
||||
->order($db->quoteName('r.backupend') . ' DESC');
|
||||
$db->setQuery($query, 0, 1);
|
||||
|
||||
@@ -75,7 +75,7 @@ class DashboardModel extends BaseDatabaseModel
|
||||
->select('COUNT(*) AS total_count')
|
||||
->select('COALESCE(SUM(' . $db->quoteName('total_size') . '), 0) AS total_size')
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('complete'));
|
||||
->where($db->quoteName('status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')');
|
||||
$db->setQuery($query);
|
||||
$stats = $db->loadObject();
|
||||
|
||||
@@ -274,7 +274,7 @@ class DashboardModel extends BaseDatabaseModel
|
||||
->select('COALESCE(SUM(r.total_size), 0) AS total_size')
|
||||
->from($db->quoteName('#__mokosuitebackup_records', 'r'))
|
||||
->join('LEFT', $db->quoteName('#__mokosuitebackup_profiles', 'p') . ' ON p.id = r.profile_id')
|
||||
->where($db->quoteName('r.status') . ' = ' . $db->quote('complete'))
|
||||
->where($db->quoteName('r.status') . ' IN (' . implode(',', array_map([$db, 'quote'], ['complete', 'warning'])) . ')')
|
||||
->group($db->quoteName('r.profile_id'))
|
||||
->order('total_size DESC');
|
||||
$db->setQuery($query);
|
||||
|
||||
@@ -41,7 +41,7 @@ class HtmlView extends BaseHtmlView
|
||||
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
if ($this->item->status === 'complete'
|
||||
if (\in_array($this->item->status, ['complete', 'warning'], true)
|
||||
&& !empty($this->item->filesexist)
|
||||
&& $user->authorise('mokosuitebackup.backup.download', 'com_mokosuitebackup')
|
||||
) {
|
||||
|
||||
@@ -30,6 +30,7 @@ $ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false)
|
||||
<?php
|
||||
$statusClass = match ($this->item->status) {
|
||||
'complete' => 'badge bg-success',
|
||||
'warning' => 'badge bg-warning text-dark',
|
||||
'running' => 'badge bg-info',
|
||||
'fail' => 'badge bg-danger',
|
||||
default => 'badge bg-secondary',
|
||||
|
||||
@@ -92,6 +92,7 @@ $listDirn = $this->escape($this->state->get('list.direction'));
|
||||
<?php
|
||||
$statusClass = match ($item->status) {
|
||||
'complete' => 'badge bg-success',
|
||||
'warning' => 'badge bg-warning text-dark',
|
||||
'running' => 'badge bg-info',
|
||||
'fail' => 'badge bg-danger',
|
||||
default => 'badge bg-secondary',
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ MOD_MOKOSUITEBACKUP_CPANEL_NOT_INSTALLED="MokoSuiteBackup is not installed or is
|
||||
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_LAST_BACKUP="Last Backup"
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_STATUS_OK="Success"
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_STATUS_WARNING="Warning"
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_STATUS_FAIL="Failed"
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_NO_BACKUPS="No backups yet."
|
||||
MOD_MOKOSUITEBACKUP_CPANEL_FILES_TABLES="%d files, %d tables"
|
||||
|
||||
@@ -51,10 +51,20 @@ $moduleId = 'mod-msb-cpanel-' . $displayData['module']->id;
|
||||
<?php if ($latest) : ?>
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<span class="badge <?php echo $latest['status'] === 'complete' ? 'bg-success' : 'bg-danger'; ?>">
|
||||
<?php echo $latest['status'] === 'complete'
|
||||
? Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STATUS_OK')
|
||||
: Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STATUS_FAIL'); ?>
|
||||
<?php
|
||||
$cpanelBadge = match ($latest['status']) {
|
||||
'complete' => 'bg-success',
|
||||
'warning' => 'bg-warning text-dark',
|
||||
default => 'bg-danger',
|
||||
};
|
||||
$cpanelLabel = match ($latest['status']) {
|
||||
'complete' => Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STATUS_OK'),
|
||||
'warning' => Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STATUS_WARNING'),
|
||||
default => Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STATUS_FAIL'),
|
||||
};
|
||||
?>
|
||||
<span class="badge <?php echo $cpanelBadge; ?>">
|
||||
<?php echo $cpanelLabel; ?>
|
||||
</span>
|
||||
<span class="ms-1 small text-muted">
|
||||
<?php echo htmlspecialchars($latest['profile'] ?? ''); ?>
|
||||
|
||||
Reference in New Issue
Block a user