feat(backup): scaffold backup bridge plugin (#208)
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
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 skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
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
Add plg_system_mokosuiteclient_backup — detects MokoSuiteBackup and collects backup status for heartbeat payloads to MokoSuiteHQ. Scaffolding includes manifest, service provider, extension class, and language files. Table column names are placeholders pending MokoSuiteBackup schema confirmation (MokoSuiteBackup#47).
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
; MokoSuiteClient Backup Bridge Plugin
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GPL-3.0-or-later
|
||||
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup"
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="Detects MokoSuiteBackup and includes backup status in heartbeat payloads sent to MokoSuiteHQ."
|
||||
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC="Backup Monitoring"
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC="Configure backup status collection for heartbeat reporting."
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL="Include in Heartbeat"
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC="Include MokoSuiteBackup status data in heartbeat payloads sent to MokoSuiteHQ."
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL="Stale Backup Threshold (days)"
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC="Number of days without a backup before status is marked as degraded. Default: 7."
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
; MokoSuiteClient Backup Bridge Plugin - System strings
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP="System - MokoSuiteClient Backup"
|
||||
PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC="MokoSuiteBackup detection and heartbeat integration."
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoSuiteClient Backup</name>
|
||||
<element>mokosuiteclient_backup</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-06-18</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.34.84-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoSuiteClientBackup</namespace>
|
||||
|
||||
<files>
|
||||
<folder>src</folder>
|
||||
<folder>services</folder>
|
||||
<folder>language</folder>
|
||||
</files>
|
||||
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.ini</language>
|
||||
<language tag="en-GB">en-GB/plg_system_mokosuiteclient_backup.sys.ini</language>
|
||||
</languages>
|
||||
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_FIELDSET_BASIC_DESC">
|
||||
|
||||
<field name="heartbeat_enabled" type="radio" default="1"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_LABEL"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_HEARTBEAT_DESC"
|
||||
class="btn-group btn-group-yesno">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
|
||||
<field name="stale_days" type="number" default="7"
|
||||
label="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOSUITECLIENT_BACKUP_STALE_DAYS_DESC"
|
||||
min="1" max="90" step="1" />
|
||||
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteClient
|
||||
* @subpackage plg_system_mokosuiteclient_backup
|
||||
* @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\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\System\MokoSuiteClientBackup\Extension\Backup;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new Backup($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuiteclient_backup'));
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoSuiteClient
|
||||
* @subpackage plg_system_mokosuiteclient_backup
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\System\MokoSuiteClientBackup\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Log\Log;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* MokoSuiteClient Backup Bridge Plugin
|
||||
*
|
||||
* Detects whether MokoSuiteBackup is installed and collects backup
|
||||
* status data for inclusion in heartbeat payloads to MokoSuiteHQ.
|
||||
*
|
||||
* @since 02.34.84
|
||||
*/
|
||||
class Backup extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onMokoSuiteClientCollectHeartbeat' => 'onCollectHeartbeat',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect backup status data for the heartbeat payload.
|
||||
*
|
||||
* Triggered by the monitor plugin before sending a heartbeat.
|
||||
* Appends a 'backup' key to the heartbeat data array.
|
||||
*/
|
||||
public function onCollectHeartbeat($event): void
|
||||
{
|
||||
if (!$this->params->get('heartbeat_enabled', 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$data = $this->getBackupStatus();
|
||||
$event->addResult('backup', $data);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
Log::add('Backup bridge: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if MokoSuiteBackup is installed.
|
||||
*
|
||||
* Queries the extensions table for the component, which is more
|
||||
* reliable than checking for database tables alone.
|
||||
*/
|
||||
public function isBackupInstalled(): bool
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('com_mokosuitebackup'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return (int) $db->loadResult() > 0;
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backup status summary from MokoSuiteBackup.
|
||||
*
|
||||
* @return array Backup status data for heartbeat inclusion.
|
||||
*/
|
||||
public function getBackupStatus(): array
|
||||
{
|
||||
if (!$this->isBackupInstalled())
|
||||
{
|
||||
return [
|
||||
'installed' => false,
|
||||
];
|
||||
}
|
||||
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
$tables = $db->getTableList();
|
||||
$prefix = $db->getPrefix();
|
||||
$statsTable = $prefix . 'mokosuitebackup_records';
|
||||
|
||||
if (!in_array($statsTable, $tables, true))
|
||||
{
|
||||
return [
|
||||
'installed' => true,
|
||||
'status' => 'degraded',
|
||||
'message' => 'Backup tables not found',
|
||||
];
|
||||
}
|
||||
|
||||
// TODO: Query MokoSuiteBackup records table for latest backup status.
|
||||
//
|
||||
// This is a placeholder — the actual column names and table structure
|
||||
// depend on MokoSuiteBackup's schema. Once that component is available
|
||||
// locally, update this query to match its database layout.
|
||||
//
|
||||
// Expected return shape:
|
||||
// [
|
||||
// 'installed' => true,
|
||||
// 'status' => 'ok' | 'degraded',
|
||||
// 'last_backup' => '2026-06-18 10:30:45',
|
||||
// 'last_status' => 'complete' | 'failed' | 'partial',
|
||||
// 'last_size_mb' => 512,
|
||||
// 'days_since' => 2,
|
||||
// 'total_backups'=> 42,
|
||||
// 'recent_7d' => 5,
|
||||
// 'destination' => 'local' | 's3' | 'remote',
|
||||
// 'description' => 'Full site backup',
|
||||
// ]
|
||||
|
||||
return $this->queryBackupRecords($db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query MokoSuiteBackup records for the latest backup summary.
|
||||
*
|
||||
* @param DatabaseInterface $db Database driver.
|
||||
*
|
||||
* @return array Backup status array.
|
||||
*/
|
||||
private function queryBackupRecords(DatabaseInterface $db): array
|
||||
{
|
||||
$staleDays = (int) $this->params->get('stale_days', 7);
|
||||
|
||||
// Get the most recent backup record
|
||||
$query = $db->getQuery(true)
|
||||
->select('*')
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->order($db->quoteName('id') . ' DESC');
|
||||
|
||||
$db->setQuery($query, 0, 1);
|
||||
$latest = $db->loadObject();
|
||||
|
||||
if (!$latest)
|
||||
{
|
||||
return [
|
||||
'installed' => true,
|
||||
'status' => 'degraded',
|
||||
'message' => 'No backups found',
|
||||
];
|
||||
}
|
||||
|
||||
// Count total and recent backups
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
);
|
||||
$totalBackups = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokosuitebackup_records'))
|
||||
->where($db->quoteName('created') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)')
|
||||
);
|
||||
$recentBackups = (int) $db->loadResult();
|
||||
|
||||
// Determine status
|
||||
$lastDate = $latest->created ?? '';
|
||||
$daysSince = $lastDate ? (int) ((time() - strtotime($lastDate)) / 86400) : 999;
|
||||
$lastStatus = $latest->status ?? 'unknown';
|
||||
|
||||
$status = 'ok';
|
||||
|
||||
if ($lastStatus !== 'complete')
|
||||
{
|
||||
$status = 'degraded';
|
||||
}
|
||||
elseif ($daysSince > $staleDays)
|
||||
{
|
||||
$status = 'degraded';
|
||||
}
|
||||
|
||||
$sizeMb = !empty($latest->total_size)
|
||||
? round($latest->total_size / 1048576)
|
||||
: null;
|
||||
|
||||
return [
|
||||
'installed' => true,
|
||||
'status' => $status,
|
||||
'last_backup' => $lastDate,
|
||||
'last_status' => $lastStatus,
|
||||
'last_size_mb' => $sizeMb,
|
||||
'days_since' => $daysSince,
|
||||
'total_backups' => $totalBackups,
|
||||
'recent_7d' => $recentBackups,
|
||||
'destination' => $latest->destination ?? null,
|
||||
'description' => $latest->description ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<file type="module" id="mod_mokosuiteclient_menu" client="administrator">mod_mokosuiteclient_menu.zip</file>
|
||||
<file type="module" id="mod_mokosuiteclient_cache" client="administrator">mod_mokosuiteclient_cache.zip</file>
|
||||
<file type="module" id="mod_mokosuiteclient_categories" client="administrator">mod_mokosuiteclient_categories.zip</file>
|
||||
<file type="plugin" id="plg_system_mokosuiteclient_backup" group="system">plg_system_mokosuiteclient_backup.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokosuiteclient" group="webservices">plg_webservices_mokosuiteclient.zip</file>
|
||||
<file type="plugin" id="plg_task_mokosuiteclientdemo" group="task">plg_task_mokosuiteclientdemo.zip</file>
|
||||
<file type="plugin" id="plg_task_mokosuiteclientsync" group="task">plg_task_mokosuiteclientsync.zip</file>
|
||||
|
||||
Reference in New Issue
Block a user