From ee21f7a373fb198db684dd425176f0275b20255e Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Tue, 23 Jun 2026 07:22:04 -0500 Subject: [PATCH] feat: dashboard snapshot widget, backup trend chart, storage breakdown (#61) Add three new dashboard widgets: - Snapshot widget: latest snapshot info, type badges, item counts, link to snapshots view, total count - Backup trend: CSS bar chart showing daily backup sizes over 30 days, red bars for days with failures, tooltips with details - Storage breakdown: horizontal bars showing space used per profile with color coding and backup counts Closes #61 --- CHANGELOG.md | 3 + .../language/en-GB/com_mokosuitebackup.ini | 6 + .../src/Model/DashboardModel.php | 84 +++++++++++++ .../src/View/Dashboard/HtmlView.php | 18 ++- .../tmpl/dashboard/default.php | 116 ++++++++++++++++++ 5 files changed, 222 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5951c6..0fc219d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## [Unreleased] +### Added +- Dashboard: snapshot widget, backup trend chart (30 days), and storage breakdown by profile (#61) + ## [01.33.00] --- 2026-06-23 ## [01.33.00] --- 2026-06-23 diff --git a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini index 67fad73..41ac24e 100644 --- a/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini +++ b/source/packages/com_mokosuitebackup/language/en-GB/com_mokosuitebackup.ini @@ -33,6 +33,12 @@ COM_MOKOJOOMBACKUP_DASHBOARD_QUICK_ACTIONS="Quick Actions" COM_MOKOJOOMBACKUP_DASHBOARD_SCHEDULED_TASKS="Scheduled Tasks" COM_MOKOJOOMBACKUP_DASHBOARD_UPDATE_SITE="Update Site" COM_MOKOJOOMBACKUP_DASHBOARD_SYSTEM_HEALTH="System Health" +COM_MOKOJOOMBACKUP_DASHBOARD_SNAPSHOTS="Content Snapshots" +COM_MOKOJOOMBACKUP_DASHBOARD_VIEW_ALL="View All" +COM_MOKOJOOMBACKUP_DASHBOARD_LATEST_SNAPSHOT="Latest" +COM_MOKOJOOMBACKUP_DASHBOARD_NO_SNAPSHOTS="No snapshots yet. Create one from the Content Snapshots view." +COM_MOKOJOOMBACKUP_DASHBOARD_STORAGE_BREAKDOWN="Storage by Profile" +COM_MOKOJOOMBACKUP_DASHBOARD_BACKUP_TREND="Backup Trend (30 days)" ; Backups view COM_MOKOJOOMBACKUP_BACKUPS_TITLE="Backup Records" diff --git a/source/packages/com_mokosuitebackup/src/Model/DashboardModel.php b/source/packages/com_mokosuitebackup/src/Model/DashboardModel.php index 09e7bbe..b378f0a 100644 --- a/source/packages/com_mokosuitebackup/src/Model/DashboardModel.php +++ b/source/packages/com_mokosuitebackup/src/Model/DashboardModel.php @@ -198,6 +198,90 @@ class DashboardModel extends BaseDatabaseModel return false; } + /** + * Get latest snapshot info for the dashboard widget. + */ + public function getLatestSnapshot(): ?object + { + $db = $this->getDatabase(); + + try { + $query = $db->getQuery(true) + ->select('*') + ->from($db->quoteName('#__mokosuitebackup_snapshots')) + ->where($db->quoteName('status') . ' = ' . $db->quote('complete')) + ->order($db->quoteName('created') . ' DESC'); + $db->setQuery($query, 0, 1); + + return $db->loadObject() ?: null; + } catch (\Throwable $e) { + return null; + } + } + + /** + * Get snapshot count. + */ + public function getSnapshotCount(): int + { + $db = $this->getDatabase(); + + try { + $query = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__mokosuitebackup_snapshots')); + $db->setQuery($query); + + return (int) $db->loadResult(); + } catch (\Throwable $e) { + return 0; + } + } + + /** + * Get backup size trend data for the last 30 days. + * Returns array of {date, total_size, count, status} grouped by day. + */ + public function getBackupTrend(): array + { + $db = $this->getDatabase(); + $cutoff = date('Y-m-d', strtotime('-30 days')); + + $query = $db->getQuery(true) + ->select('DATE(' . $db->quoteName('backupstart') . ') AS backup_date') + ->select('SUM(' . $db->quoteName('total_size') . ') AS day_size') + ->select('COUNT(*) AS day_count') + ->select('SUM(CASE WHEN ' . $db->quoteName('status') . ' = ' . $db->quote('fail') . ' THEN 1 ELSE 0 END) AS fail_count') + ->from($db->quoteName('#__mokosuitebackup_records')) + ->where('DATE(' . $db->quoteName('backupstart') . ') >= ' . $db->quote($cutoff)) + ->group('DATE(' . $db->quoteName('backupstart') . ')') + ->order('backup_date ASC'); + $db->setQuery($query); + + return $db->loadObjectList() ?: []; + } + + /** + * Get storage breakdown by profile. + */ + public function getStorageByProfile(): array + { + $db = $this->getDatabase(); + + $query = $db->getQuery(true) + ->select('p.title AS profile_title') + ->select('COUNT(*) AS backup_count') + ->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')) + ->group($db->quoteName('r.profile_id')) + ->order('total_size DESC'); + $db->setQuery($query); + + return $db->loadObjectList() ?: []; + } + /** * Get published backup profiles for the quick-action selector. * diff --git a/source/packages/com_mokosuitebackup/src/View/Dashboard/HtmlView.php b/source/packages/com_mokosuitebackup/src/View/Dashboard/HtmlView.php index 340dfcc..f5c9b88 100644 --- a/source/packages/com_mokosuitebackup/src/View/Dashboard/HtmlView.php +++ b/source/packages/com_mokosuitebackup/src/View/Dashboard/HtmlView.php @@ -24,18 +24,26 @@ class HtmlView extends BaseHtmlView public array $systemHealth = []; public array $profiles = []; public bool $defaultDirWarning = false; + public ?object $latestSnapshot = null; + public int $snapshotCount = 0; + public array $backupTrend = []; + public array $storageByProfile = []; public function display($tpl = null): void { /** @var \Joomla\Component\MokoSuiteBackup\Administrator\Model\DashboardModel $model */ $model = $this->getModel(); - $this->lastBackup = $model->getLastBackup(); - $this->nextScheduled = $model->getNextScheduled(); - $this->stats = $model->getStats(); - $this->systemHealth = $model->getSystemHealth(); - $this->profiles = $model->getProfiles(); + $this->lastBackup = $model->getLastBackup(); + $this->nextScheduled = $model->getNextScheduled(); + $this->stats = $model->getStats(); + $this->systemHealth = $model->getSystemHealth(); + $this->profiles = $model->getProfiles(); $this->defaultDirWarning = $model->isUsingDefaultBackupDir(); + $this->latestSnapshot = $model->getLatestSnapshot(); + $this->snapshotCount = $model->getSnapshotCount(); + $this->backupTrend = $model->getBackupTrend(); + $this->storageByProfile = $model->getStorageByProfile(); $this->addToolbar(); diff --git a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php index d82ac57..39e255d 100644 --- a/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php +++ b/source/packages/com_mokosuitebackup/tmpl/dashboard/default.php @@ -109,6 +109,122 @@ document.querySelectorAll('.mb-tile').forEach(function(tile) { }); + +
+
+
+
+
+ + +
+ + + +
+
+ latestSnapshot) : ?> + latestSnapshot->content_types, true) ?: []; ?> +

+ : + escape($this->latestSnapshot->description); ?> +

+

+ latestSnapshot->created, Text::_('DATE_FORMAT_LC4')); ?> + — + + escape($type); ?> + +

+

+ + latestSnapshot->articles_count; ?> articles, + latestSnapshot->categories_count; ?> categories, + latestSnapshot->modules_count; ?> modules + — snapshotCount; ?> total snapshots + +

+ +

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+ storageByProfile)) : ?> + storageByProfile, 'total_size')) ?: 1; + $colors = ['#0d6efd', '#198754', '#ffc107', '#dc3545', '#6f42c1', '#0dcaf0']; + ?> + storageByProfile as $i => $profile) : ?> + total_size / $maxSize) * 100); ?> +
+
+ escape($profile->profile_title ?: 'Unknown'); ?> (backup_count; ?>) + total_size); ?> +
+
+
+
+
+ + +

+ +
+
+
+
+ + +backupTrend)) : ?> +
+
+
+
+
+ + +
+
+
+ backupTrend, 'day_size')) ?: 1; + ?> +
+ backupTrend as $day) : ?> + day_size / $maxDaySize) * 100)); + $barColor = $day->fail_count > 0 ? '#dc3545' : '#198754'; + $tooltip = date('M j', strtotime($day->backup_date)) + . ' — ' . $day->day_count . ' backup(s), ' + . number_format($day->day_size / 1048576, 1) . ' MB' + . ($day->fail_count > 0 ? ', ' . $day->fail_count . ' failed' : ''); + ?> +
+ +
+
+ + +
+
+
+
+
+ +