67329507f4
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
- Browser warns before closing/navigating while backup is in progress - FolderPickerField shows info status for paths with [placeholders] instead of "Directory not found" - Dashboard health check skips filesystem check when backup_dir uses placeholders (resolved at backup time by PlaceholderResolver) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
297 lines
11 KiB
PHP
297 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoJoomBackup
|
|
* @subpackage com_mokojoombackup
|
|
* @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;
|
|
|
|
$ajaxToken = Session::getFormToken();
|
|
$ajaxUrl = Route::_('index.php?option=com_mokojoombackup&format=json', false);
|
|
?>
|
|
<?php if ($this->defaultDirWarning) : ?>
|
|
<div class="alert alert-warning d-flex align-items-center mb-3" role="alert">
|
|
<span class="icon-warning-circle fs-4 me-3" aria-hidden="true"></span>
|
|
<div>
|
|
<strong><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING_TITLE'); ?></strong><br>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_DEFAULT_DIR_WARNING'); ?>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokojoombackup&view=profiles'); ?>" class="alert-link">
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SUBMENU_PROFILES'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row">
|
|
<!-- Row 1: Status Cards (clickable) -->
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card h-100 mb-tile" role="link" data-href="<?php echo $this->lastBackup ? Route::_('index.php?option=com_mokojoombackup&view=backup&id=' . $this->lastBackup->id) : Route::_('index.php?option=com_mokojoombackup&view=backups'); ?>">
|
|
<div class="card-body text-center">
|
|
<span class="icon-database fs-1 text-primary" aria-hidden="true"></span>
|
|
<h5 class="card-title mt-2"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_LAST_BACKUP'); ?></h5>
|
|
<?php if ($this->lastBackup) : ?>
|
|
<p class="card-text text-success fw-bold">
|
|
<?php echo HTMLHelper::_('date', $this->lastBackup->backupend, Text::_('DATE_FORMAT_LC4')); ?>
|
|
</p>
|
|
<small class="text-muted">
|
|
<?php echo $this->escape($this->lastBackup->profile_title); ?>
|
|
—
|
|
<?php echo HTMLHelper::_('number.bytes', $this->lastBackup->total_size); ?>
|
|
</small>
|
|
<?php else : ?>
|
|
<p class="card-text text-warning"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_NO_BACKUPS'); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card h-100 mb-tile" role="link" data-href="<?php echo Route::_('index.php?option=com_scheduler&view=tasks'); ?>">
|
|
<div class="card-body text-center">
|
|
<span class="icon-calendar fs-1 text-info" aria-hidden="true"></span>
|
|
<h5 class="card-title mt-2"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_NEXT_SCHEDULED'); ?></h5>
|
|
<?php if ($this->nextScheduled) : ?>
|
|
<p class="card-text fw-bold">
|
|
<?php echo HTMLHelper::_('date', $this->nextScheduled->next_execution, Text::_('DATE_FORMAT_LC4')); ?>
|
|
</p>
|
|
<small class="text-muted"><?php echo $this->escape($this->nextScheduled->title); ?></small>
|
|
<?php else : ?>
|
|
<p class="card-text text-muted"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_NO_SCHEDULED'); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card h-100 mb-tile" role="link" data-href="<?php echo Route::_('index.php?option=com_mokojoombackup&view=backups'); ?>">
|
|
<div class="card-body text-center">
|
|
<span class="icon-copy fs-1 text-secondary" aria-hidden="true"></span>
|
|
<h5 class="card-title mt-2"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_TOTAL_BACKUPS'); ?></h5>
|
|
<p class="card-text fw-bold fs-3"><?php echo (int) $this->stats->total_count; ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card h-100 mb-tile" role="link" data-href="<?php echo Route::_('index.php?option=com_mokojoombackup&view=backups'); ?>">
|
|
<div class="card-body text-center">
|
|
<span class="icon-folder-open fs-1 text-warning" aria-hidden="true"></span>
|
|
<h5 class="card-title mt-2"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_STORAGE'); ?></h5>
|
|
<p class="card-text fw-bold fs-3">
|
|
<?php echo HTMLHelper::_('number.bytes', (int) $this->stats->total_size); ?>
|
|
</p>
|
|
<?php if ($this->stats->fail_count_7d > 0) : ?>
|
|
<span class="badge bg-danger">
|
|
<?php echo Text::sprintf('COM_MOKOJOOMBACKUP_DASHBOARD_FAILURES_7D', $this->stats->fail_count_7d); ?>
|
|
</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.mb-tile { cursor: pointer; transition: box-shadow 0.2s, transform 0.1s; }
|
|
.mb-tile:hover { box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); transform: translateY(-2px); }
|
|
</style>
|
|
<script>
|
|
document.querySelectorAll('.mb-tile').forEach(function(tile) {
|
|
tile.addEventListener('click', function() { window.location.href = this.dataset.href; });
|
|
});
|
|
</script>
|
|
|
|
<!-- Row 2: Quick Actions -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_QUICK_ACTIONS'); ?></h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<?php if (!empty($this->profiles)) : ?>
|
|
<div class="d-flex align-items-center gap-3 mb-3">
|
|
<select id="mb-profile-select" class="form-select" style="max-width:250px;">
|
|
<?php foreach ($this->profiles as $profile) : ?>
|
|
<option value="<?php echo (int) $profile->id; ?>">
|
|
<?php echo $this->escape($profile->title); ?>
|
|
(<?php echo $this->escape($profile->backup_type); ?>)
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<button type="button" class="btn btn-primary" onclick="window.mokojoombackupStart()">
|
|
<span class="icon-download" aria-hidden="true"></span>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_TOOLBAR_BACKUP_NOW'); ?>
|
|
</button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="list-group">
|
|
<a href="<?php echo Route::_('index.php?option=com_mokojoombackup&view=backups'); ?>" class="list-group-item list-group-item-action">
|
|
<span class="icon-database" aria-hidden="true"></span>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SUBMENU_BACKUPS'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokojoombackup&view=profiles'); ?>" class="list-group-item list-group-item-action">
|
|
<span class="icon-cog" aria-hidden="true"></span>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_SUBMENU_PROFILES'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_scheduler&view=tasks'); ?>" class="list-group-item list-group-item-action">
|
|
<span class="icon-calendar" aria-hidden="true"></span>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_SCHEDULED_TASKS'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_installer&view=updatesites'); ?>" class="list-group-item list-group-item-action">
|
|
<span class="icon-refresh" aria-hidden="true"></span>
|
|
<?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_UPDATE_SITE'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Row 2 right: System Health -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOJOOMBACKUP_DASHBOARD_SYSTEM_HEALTH'); ?></h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm mb-0">
|
|
<tbody>
|
|
<?php foreach ($this->systemHealth as $check) : ?>
|
|
<tr>
|
|
<td class="w-1 text-center">
|
|
<?php if ($check->status) : ?>
|
|
<span class="icon-publish text-success" aria-hidden="true"></span>
|
|
<?php else : ?>
|
|
<span class="icon-unpublish text-danger" aria-hidden="true"></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo $this->escape($check->label); ?></td>
|
|
<td class="text-muted"><?php echo $this->escape($check->detail); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stepped Backup Modal (reused from backups view) -->
|
|
<div id="mokojoombackup-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="mb-modal-title" style="margin:0 0 1rem;">Backup in Progress</h3>
|
|
<div style="background:#e9ecef; border-radius:4px; overflow:hidden; height:24px; margin-bottom:0.5rem;">
|
|
<div id="mb-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="mb-status" style="color:#666; font-size:0.9rem; margin:0.5rem 0;">Initializing...</p>
|
|
<p id="mb-phase" style="color:#999; font-size:0.8rem; margin:0;">Phase: init</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function() {
|
|
const AJAX_URL = <?php echo json_encode($ajaxUrl); ?>;
|
|
const TOKEN_NAME = <?php echo json_encode($ajaxToken); ?>;
|
|
|
|
var backupRunning = false;
|
|
|
|
window.addEventListener('beforeunload', function(e) {
|
|
if (backupRunning) { e.preventDefault(); e.returnValue = ''; }
|
|
});
|
|
|
|
function showModal() {
|
|
backupRunning = true;
|
|
document.getElementById('mokojoombackup-modal').style.display = 'block';
|
|
}
|
|
|
|
function hideModal() {
|
|
backupRunning = false;
|
|
document.getElementById('mokojoombackup-modal').style.display = 'none';
|
|
}
|
|
|
|
function updateProgress(progress, message, phase) {
|
|
const bar = document.getElementById('mb-progress-bar');
|
|
bar.style.width = progress + '%';
|
|
bar.textContent = progress + '%';
|
|
document.getElementById('mb-status').textContent = message;
|
|
document.getElementById('mb-phase').textContent = 'Phase: ' + phase;
|
|
}
|
|
|
|
async function postAjax(params) {
|
|
const form = new URLSearchParams();
|
|
form.append(TOKEN_NAME, '1');
|
|
for (const [k, v] of Object.entries(params)) {
|
|
form.append(k, v);
|
|
}
|
|
const res = await fetch(AJAX_URL, {
|
|
method: 'POST',
|
|
body: form,
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
async function startSteppedBackup() {
|
|
const profileSelect = document.getElementById('mb-profile-select');
|
|
const profileId = profileSelect ? profileSelect.value : '1';
|
|
|
|
showModal();
|
|
updateProgress(0, 'Initializing backup...', 'init');
|
|
|
|
try {
|
|
const initResult = await postAjax({
|
|
task: 'ajax.init',
|
|
profile_id: profileId
|
|
});
|
|
|
|
if (initResult.error) {
|
|
updateProgress(0, 'ERROR: ' + initResult.message, 'failed');
|
|
setTimeout(hideModal, 3000);
|
|
return;
|
|
}
|
|
|
|
const sessionId = initResult.session_id;
|
|
updateProgress(initResult.progress, initResult.message, initResult.phase);
|
|
|
|
let done = false;
|
|
while (!done) {
|
|
const 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;
|
|
}
|
|
|
|
document.getElementById('mb-modal-title').textContent = 'Backup Complete';
|
|
setTimeout(function() {
|
|
hideModal();
|
|
location.reload();
|
|
}, 2000);
|
|
|
|
} catch (err) {
|
|
updateProgress(0, 'ERROR: ' + err.message, 'failed');
|
|
setTimeout(hideModal, 5000);
|
|
}
|
|
}
|
|
|
|
window.mokojoombackupStart = startSteppedBackup;
|
|
})();
|
|
</script>
|