diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index 9477ac53c..0c1aefb25 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -1035,6 +1035,38 @@ cursor: not-allowed; } + .upgrades-badge { + display: inline-block; + min-width: 20px; + height: 20px; + padding: 0 6px; + margin-left: 6px; + background: #f59e0b; + color: white; + border-radius: 10px; + font-size: 12px; + font-weight: 700; + line-height: 20px; + text-align: center; + } + + .upgrades-view-notice { + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 8px; + padding: 16px 20px; + margin-bottom: 20px; + display: flex; + align-items: flex-start; + gap: 12px; + } + + .upgrades-view-notice i { + color: #2563eb; + font-size: 20px; + margin-top: 2px; + } + .btn-link { padding: 6px 12px; background: var(--bg-secondary, #f8f9ff); @@ -1298,6 +1330,11 @@ {% trans "Table View" %} + + + {% trans "Upgrades Available" %} + 0 + {% trans "CyberPanel Plugin Store" %} @@ -1619,6 +1656,11 @@ {% trans "Table View" %} + + + {% trans "Upgrades Available" %} + 0 + {% trans "CyberPanel Plugin Store" %} @@ -1630,6 +1672,43 @@ {% endif %} + + + + + + {% trans "Upgrades Available" %} + {% trans "The following installed plugins have a newer version available in the CyberPanel Plugin Store. Before upgrading, please read the about information for the release and ensure you have a current backup of your website." %} + + + + {% trans "Checking for upgrades..." %} + + + + + + + {% trans "No upgrades available. All installed plugins are up to date." %} + + + + + {% trans "Plugin Name" %} + {% trans "New Version" %} + {% trans "Your Version" %} + {% trans "Date" %} + {% trans "Status / Action" %} + + + + + + + + + + @@ -1801,6 +1880,7 @@ function toggleView(view, updateHash = true) { const gridView = document.getElementById('gridView'); const tableView = document.getElementById('tableView'); const storeView = document.getElementById('storeView'); + const upgradesView = document.getElementById('upgradesView'); const viewBtns = document.querySelectorAll('.view-btn'); viewBtns.forEach(btn => btn.classList.remove('active')); @@ -1831,10 +1911,15 @@ function toggleView(view, updateHash = true) { return; } + // Index of view buttons: 0=Grid, 1=Table, 2=Upgrades Available, 3=Store, 4=Dev Guide (or 3 when no plugins) + const upgradesBtnIndex = viewBtns.length >= 4 ? 2 : -1; + const storeBtnIndex = viewBtns.length >= 4 ? 3 : 2; + if (view === 'grid') { gridView.style.display = 'grid'; tableView.style.display = 'none'; storeView.style.display = 'none'; + if (upgradesView) upgradesView.style.display = 'none'; if (viewBtns[0]) viewBtns[0].classList.add('active'); if (installedSearchWrapper) installedSearchWrapper.style.display = 'block'; if (installedSortFilterBar) installedSortFilterBar.style.display = 'flex'; @@ -1845,40 +1930,110 @@ function toggleView(view, updateHash = true) { gridView.style.display = 'none'; tableView.style.display = 'block'; storeView.style.display = 'none'; + if (upgradesView) upgradesView.style.display = 'none'; if (viewBtns[1]) viewBtns[1].classList.add('active'); if (installedSearchWrapper) installedSearchWrapper.style.display = 'block'; if (installedSortFilterBar) installedSortFilterBar.style.display = 'flex'; if (typeof updateInstalledSortButtons === 'function') updateInstalledSortButtons(); if (typeof doApplyInstalledSort === 'function') doApplyInstalledSort(); filterInstalledPlugins(); + } else if (view === 'upgrades') { + if (installedSearchWrapper) installedSearchWrapper.style.display = 'none'; + if (installedSortFilterBar) installedSortFilterBar.style.display = 'none'; + gridView.style.display = 'none'; + tableView.style.display = 'none'; + storeView.style.display = 'none'; + if (upgradesView) upgradesView.style.display = 'block'; + if (upgradesBtnIndex >= 0 && viewBtns[upgradesBtnIndex]) viewBtns[upgradesBtnIndex].classList.add('active'); + else if (document.getElementById('viewBtnUpgrades')) document.getElementById('viewBtnUpgrades').classList.add('active'); + else if (document.getElementById('viewBtnUpgradesNoPlugins')) document.getElementById('viewBtnUpgradesNoPlugins').classList.add('active'); + + if (storePlugins.length === 0) { + loadPluginStore(true); + } else { + displayUpgradesAvailable(); + } } else if (view === 'store') { if (installedSearchWrapper) installedSearchWrapper.style.display = 'none'; if (installedSortFilterBar) installedSortFilterBar.style.display = 'none'; gridView.style.display = 'none'; tableView.style.display = 'none'; + if (upgradesView) upgradesView.style.display = 'none'; storeView.style.display = 'block'; - if (viewBtns[2]) viewBtns[2].classList.add('active'); + if (storeBtnIndex >= 0 && viewBtns[storeBtnIndex]) viewBtns[storeBtnIndex].classList.add('active'); + else if (viewBtns[2]) viewBtns[2].classList.add('active'); // Load plugins from store if not already loaded if (storePlugins.length === 0) { - loadPluginStore(); + loadPluginStore(false); } else { displayStorePlugins(); } } } -function loadPluginStore() { +function updateUpgradesBadge() { + const count = (storePlugins && storePlugins.filter(p => p.update_available).length) || 0; + const badge = document.getElementById('upgradesBadge'); + const badgeNoPlugins = document.getElementById('upgradesBadgeNoPlugins'); + if (badge) { + badge.textContent = String(count); + badge.style.display = count > 0 ? 'inline-block' : 'none'; + } + if (badgeNoPlugins) { + badgeNoPlugins.textContent = String(count); + badgeNoPlugins.style.display = count > 0 ? 'inline-block' : 'none'; + } +} + +function displayUpgradesAvailable() { + const tbody = document.getElementById('upgradesTableBody'); + const loading = document.getElementById('upgradesLoading'); + const errorDiv = document.getElementById('upgradesError'); + const errorText = document.getElementById('upgradesErrorText'); + const content = document.getElementById('upgradesContent'); + if (!tbody) return; + + if (loading) loading.style.display = 'none'; + if (errorDiv) errorDiv.style.display = 'none'; + if (content) content.style.display = 'block'; + + const upgrades = (storePlugins || []).filter(p => p.update_available); + if (upgrades.length === 0) { + const noUpgradesEl = document.getElementById('transNoUpgrades'); + const noUpgradesMsg = (noUpgradesEl && noUpgradesEl.textContent) ? noUpgradesEl.textContent : 'No upgrades available. All installed plugins are up to date.'; + tbody.innerHTML = '' + escapeHtml(noUpgradesMsg) + ''; + return; + } + + let html = ''; + upgrades.forEach(plugin => { + const dir = (plugin.plugin_dir || '').replace(/'/g, ''').replace(/"/g, '"'); + const name = escapeHtml(plugin.name || plugin.plugin_dir || ''); + const newVer = escapeHtml(plugin.version || ''); + const yourVer = escapeHtml(plugin.installed_version || 'Unknown'); + const date = escapeHtml(plugin.modify_date || ''); + const curVer = (plugin.installed_version || 'Unknown').replace(/'/g, '''); + const nVer = (plugin.version || 'Unknown').replace(/'/g, '''); + const actionHtml = ' Upgrade'; + html += '' + name + '' + newVer + '' + yourVer + '' + date + '' + actionHtml + ''; + }); + tbody.innerHTML = html; +} + +function loadPluginStore(forUpgradesView) { const storeLoading = document.getElementById('storeLoading'); const storeError = document.getElementById('storeError'); const storeErrorText = document.getElementById('storeErrorText'); const storeContent = document.getElementById('storeContent'); const storeTableBody = document.getElementById('storeTableBody'); + const upgradesLoading = document.getElementById('upgradesLoading'); if (!storeLoading || !storeError || !storeErrorText || !storeContent) return; storeLoading.style.display = 'block'; storeError.style.display = 'none'; storeContent.style.display = 'block'; + if (upgradesLoading) upgradesLoading.style.display = forUpgradesView ? 'block' : 'none'; fetch('/plugins/api/store/plugins/', { method: 'GET', @@ -1903,21 +2058,43 @@ function loadPluginStore() { }) .then(data => { storeLoading.style.display = 'none'; + const upgradesLoadingEl = document.getElementById('upgradesLoading'); + if (upgradesLoadingEl) upgradesLoadingEl.style.display = 'none'; if (data.success) { storePlugins = data.plugins; - displayStorePlugins(); + if (typeof updateUpgradesBadge === 'function') updateUpgradesBadge(); + const upgradesViewEl = document.getElementById('upgradesView'); + if (upgradesViewEl && upgradesViewEl.style.display === 'block' && typeof displayUpgradesAvailable === 'function') { + displayUpgradesAvailable(); + } else { + displayStorePlugins(); + } storeContent.style.display = 'block'; } else { storeErrorText.textContent = data.error || 'Failed to load plugins from store'; storeError.style.display = 'block'; + const upgradesError = document.getElementById('upgradesError'); + const upgradesErrorText = document.getElementById('upgradesErrorText'); + if (upgradesError && upgradesErrorText) { + upgradesErrorText.textContent = data.error || 'Failed to load plugin store'; + upgradesError.style.display = 'block'; + } } }) .catch(error => { storeLoading.style.display = 'none'; + const upgradesLoadingEl = document.getElementById('upgradesLoading'); + if (upgradesLoadingEl) upgradesLoadingEl.style.display = 'none'; storeErrorText.textContent = error.message || 'Error loading plugin store. Please refresh the page and try again.'; storeError.style.display = 'block'; storeContent.style.display = 'block'; + const upgradesError = document.getElementById('upgradesError'); + const upgradesErrorText = document.getElementById('upgradesErrorText'); + if (upgradesError && upgradesErrorText) { + upgradesErrorText.textContent = error.message || 'Error checking for upgrades.'; + upgradesError.style.display = 'block'; + } if (storeTableBody && storeTableBody.innerHTML === '') { storeTableBody.innerHTML = 'Unable to load plugins. Please check your connection and try again.'; } @@ -3158,12 +3335,13 @@ document.addEventListener('DOMContentLoaded', function() { // Check URL hash for view preference const hash = window.location.hash.substring(1); // Remove # - const validViews = ['grid', 'table', 'store']; + const validViews = ['grid', 'table', 'upgrades', 'store']; // Check if view elements exist before calling toggleView const gridView = document.getElementById('gridView'); const tableView = document.getElementById('tableView'); const storeView = document.getElementById('storeView'); + const upgradesViewEl = document.getElementById('upgradesView'); // Only proceed if all view elements exist (plugins are installed) if (gridView && tableView && storeView) {
{% trans "The following installed plugins have a newer version available in the CyberPanel Plugin Store. Before upgrading, please read the about information for the release and ensure you have a current backup of your website." %}