3742477aef
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: PR Check / Secret Scan (pull_request) Successful in 9s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 29s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
- Convert 10 inline CSS modals to Bootstrap 5 (backups: 7, snapshots: 3) - Replace style.display show/hide with Bootstrap Modal API - Fix JFIELD_ORDERING_LABEL_ASC → JFIELD_ORDERING_ASC in profile filter - Add COM_MOKOJOOMBACKUP_CONFIGURATION key for Options page title - Change ntfy default server to ntfy.mokoconsulting.tech - Add profile ID to dropdown labels across backups, dashboard, cpanel module - Add error handling to MokoRestore post() and runPreflight() to prevent UI stalling - Remove outdated SSH auth pattern references from field descriptions
255 lines
9.5 KiB
PHP
255 lines
9.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoSuiteBackup
|
|
* @subpackage mod_mokosuitebackup_cpanel
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
*/
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\HTML\HTMLHelper;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Router\Route;
|
|
use Joomla\CMS\Session\Session;
|
|
|
|
/** @var array $displayData */
|
|
$status = $displayData['status'];
|
|
$profiles = $displayData['profiles'];
|
|
$nextScheduled = $displayData['nextScheduled'];
|
|
$params = $displayData['params'];
|
|
|
|
$showButtons = (int) $params->get('show_backup_buttons', 1);
|
|
$showSchedule = (int) $params->get('show_schedule', 1);
|
|
|
|
$latest = $status['latest'] ?? null;
|
|
$installed = $status['installed'] ?? false;
|
|
$totals = $status['totals'] ?? [];
|
|
|
|
$ajaxToken = Session::getFormToken();
|
|
$ajaxUrl = Route::_('index.php?option=com_mokosuitebackup&format=json', false);
|
|
|
|
$moduleId = 'mod-msb-cpanel-' . $displayData['module']->id;
|
|
?>
|
|
|
|
<?php if (!$installed) : ?>
|
|
<div class="alert alert-warning mb-0">
|
|
<span class="icon-warning-circle" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_NOT_INSTALLED'); ?>
|
|
</div>
|
|
<?php return; endif; ?>
|
|
|
|
<div id="<?php echo $moduleId; ?>" class="mod-mokosuitebackup-cpanel">
|
|
<!-- Last Backup Status -->
|
|
<div class="mb-3">
|
|
<h6 class="text-muted text-uppercase small mb-2">
|
|
<span class="icon-database" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_LAST_BACKUP'); ?>
|
|
</h6>
|
|
<?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'); ?>
|
|
</span>
|
|
<span class="ms-1 small text-muted">
|
|
<?php echo htmlspecialchars($latest['profile'] ?? ''); ?>
|
|
</span>
|
|
</div>
|
|
<span class="small text-muted">
|
|
<?php echo HTMLHelper::_('date', $latest['backup_start'], Text::_('DATE_FORMAT_LC4')); ?>
|
|
</span>
|
|
</div>
|
|
<div class="small text-muted mt-1">
|
|
<?php echo HTMLHelper::_('number.bytes', (int) $latest['total_size']); ?>
|
|
— <?php echo Text::sprintf('MOD_MOKOSUITEBACKUP_CPANEL_FILES_TABLES', (int) $latest['files_count'], (int) $latest['tables_count']); ?>
|
|
</div>
|
|
<?php else : ?>
|
|
<p class="text-muted small mb-0"><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_NO_BACKUPS'); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<!-- Next Scheduled -->
|
|
<?php if ($showSchedule && $nextScheduled) : ?>
|
|
<div class="mb-3">
|
|
<h6 class="text-muted text-uppercase small mb-1">
|
|
<span class="icon-calendar" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_NEXT_SCHEDULED'); ?>
|
|
</h6>
|
|
<div class="small">
|
|
<?php echo HTMLHelper::_('date', $nextScheduled->next_execution, Text::_('DATE_FORMAT_LC4')); ?>
|
|
<span class="text-muted">— <?php echo htmlspecialchars($nextScheduled->title); ?></span>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Stats row -->
|
|
<?php if (!empty($totals)) : ?>
|
|
<div class="d-flex gap-3 mb-3 small">
|
|
<div>
|
|
<span class="fw-bold"><?php echo (int) ($totals['all_time'] ?? 0); ?></span>
|
|
<span class="text-muted"><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_TOTAL'); ?></span>
|
|
</div>
|
|
<div>
|
|
<span class="fw-bold text-success"><?php echo (int) ($totals['success_streak'] ?? 0); ?></span>
|
|
<span class="text-muted"><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_STREAK'); ?></span>
|
|
</div>
|
|
<?php if (($totals['recent_failed'] ?? 0) > 0) : ?>
|
|
<div>
|
|
<span class="fw-bold text-danger"><?php echo (int) $totals['recent_failed']; ?></span>
|
|
<span class="text-muted"><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_FAILED_7D'); ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Backup Now Buttons -->
|
|
<?php if ($showButtons && !empty($profiles)) : ?>
|
|
<div class="mb-3">
|
|
<h6 class="text-muted text-uppercase small mb-2">
|
|
<span class="icon-download" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_BACKUP_NOW'); ?>
|
|
</h6>
|
|
<div class="d-flex flex-wrap gap-1">
|
|
<?php foreach ($profiles as $profile) : ?>
|
|
<button type="button"
|
|
class="btn btn-sm btn-outline-primary msb-cpanel-backup-btn"
|
|
data-profile-id="<?php echo (int) $profile->id; ?>"
|
|
data-module-id="<?php echo $moduleId; ?>">
|
|
#<?php echo (int) $profile->id; ?> <?php echo htmlspecialchars($profile->title); ?>
|
|
<span class="badge bg-secondary ms-1"><?php echo htmlspecialchars($profile->backup_type); ?></span>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Quick Links -->
|
|
<div class="list-group list-group-flush small">
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitebackup&view=backups'); ?>"
|
|
class="list-group-item list-group-item-action px-0 py-1">
|
|
<span class="icon-database" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_LINK_BACKUPS'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitebackup&view=snapshots&task=snapshot.add'); ?>"
|
|
class="list-group-item list-group-item-action px-0 py-1">
|
|
<span class="icon-camera" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_LINK_SNAPSHOT'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitebackup&view=profiles'); ?>"
|
|
class="list-group-item list-group-item-action px-0 py-1">
|
|
<span class="icon-cog" aria-hidden="true"></span>
|
|
<?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_LINK_PROFILES'); ?>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Stepped Backup Modal -->
|
|
<div id="<?php echo $moduleId; ?>-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000;">
|
|
<div style="max-width:500px; margin:10% auto; background:#fff; border-radius:8px; padding:2rem; box-shadow:0 4px 20px rgba(0,0,0,0.3);">
|
|
<h3 id="<?php echo $moduleId; ?>-modal-title" style="margin:0 0 1rem;"><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_BACKUP_IN_PROGRESS'); ?></h3>
|
|
<div class="alert alert-warning py-1 px-2 mb-2" style="font-size:0.85rem;">
|
|
<span class="icon-warning-circle" aria-hidden="true"></span>
|
|
<strong><?php echo Text::_('MOD_MOKOSUITEBACKUP_CPANEL_DO_NOT_CLOSE'); ?></strong>
|
|
</div>
|
|
<div style="background:#e9ecef; border-radius:4px; overflow:hidden; height:24px; margin-bottom:0.5rem;">
|
|
<div id="<?php echo $moduleId; ?>-progress-bar" style="height:100%; background:#0d6efd; transition:width 0.3s; width:0%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:0.8rem; font-weight:bold;">0%</div>
|
|
</div>
|
|
<p id="<?php echo $moduleId; ?>-status" style="color:#666; font-size:0.9rem; margin:0.5rem 0;">Initializing...</p>
|
|
<p id="<?php echo $moduleId; ?>-phase" style="color:#999; font-size:0.8rem; margin:0;">Phase: init</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
var MOD_ID = <?php echo json_encode($moduleId); ?>;
|
|
var AJAX_URL = <?php echo json_encode($ajaxUrl); ?>;
|
|
var TOKEN = <?php echo json_encode($ajaxToken); ?>;
|
|
|
|
var running = false;
|
|
|
|
window.addEventListener('beforeunload', function(e) {
|
|
if (running) { e.preventDefault(); e.returnValue = ''; }
|
|
});
|
|
|
|
function el(id) { return document.getElementById(id); }
|
|
|
|
function showModal() {
|
|
running = true;
|
|
el(MOD_ID + '-modal').style.display = 'block';
|
|
}
|
|
|
|
function hideModal() {
|
|
running = false;
|
|
el(MOD_ID + '-modal').style.display = 'none';
|
|
}
|
|
|
|
function updateProgress(pct, msg, phase) {
|
|
var bar = el(MOD_ID + '-progress-bar');
|
|
bar.style.width = pct + '%';
|
|
bar.textContent = pct + '%';
|
|
el(MOD_ID + '-status').textContent = msg;
|
|
el(MOD_ID + '-phase').textContent = 'Phase: ' + phase;
|
|
}
|
|
|
|
async function postAjax(params) {
|
|
var form = new URLSearchParams();
|
|
form.append(TOKEN, '1');
|
|
for (var k in params) { form.append(k, params[k]); }
|
|
var res = await fetch(AJAX_URL, {
|
|
method: 'POST',
|
|
body: form,
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
async function startBackup(profileId) {
|
|
showModal();
|
|
updateProgress(0, 'Initializing backup...', 'init');
|
|
|
|
try {
|
|
var initResult = await postAjax({ task: 'ajax.init', profile_id: profileId });
|
|
if (initResult.error) {
|
|
updateProgress(0, 'ERROR: ' + initResult.message, 'failed');
|
|
setTimeout(hideModal, 5000);
|
|
return;
|
|
}
|
|
|
|
var sessionId = initResult.session_id;
|
|
updateProgress(initResult.progress, initResult.message, initResult.phase);
|
|
|
|
var done = false;
|
|
while (!done) {
|
|
var stepResult = await postAjax({ task: 'ajax.step', session_id: sessionId });
|
|
if (stepResult.error) {
|
|
updateProgress(0, 'ERROR: ' + stepResult.message, 'failed');
|
|
setTimeout(hideModal, 5000);
|
|
return;
|
|
}
|
|
updateProgress(stepResult.progress, stepResult.message, stepResult.phase);
|
|
done = stepResult.done || false;
|
|
}
|
|
|
|
el(MOD_ID + '-modal-title').textContent = <?php echo json_encode(Text::_('MOD_MOKOSUITEBACKUP_CPANEL_BACKUP_COMPLETE')); ?>;
|
|
setTimeout(function() { hideModal(); location.reload(); }, 2000);
|
|
} catch (err) {
|
|
updateProgress(0, 'ERROR: ' + err.message, 'failed');
|
|
setTimeout(hideModal, 5000);
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('#' + MOD_ID + ' .msb-cpanel-backup-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
startBackup(this.getAttribute('data-profile-id'));
|
|
});
|
|
});
|
|
});
|
|
})();
|
|
</script>
|