Compare commits
2 Commits
32080e5aef
...
18ff440115
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ff440115 | ||
|
|
1460acb941 |
@ -119,6 +119,10 @@
|
|||||||
<button class="main-tab-button" data-tab="backups" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('backups')">
|
<button class="main-tab-button" data-tab="backups" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('backups')">
|
||||||
<span class="tab-emoji">💾</span>
|
<span class="tab-emoji">💾</span>
|
||||||
<span class="tab-name">Backups</span>
|
<span class="tab-name">Backups</span>
|
||||||
|
</button>
|
||||||
|
<button class="main-tab-button" data-tab="updater" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('updater')">
|
||||||
|
<span class="tab-emoji">⬆️</span>
|
||||||
|
<span class="tab-name">Updates</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="main-tab-button" data-tab="tasks" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('tasks')">
|
<button class="main-tab-button" data-tab="tasks" onclick="if(window.appTabbedManager) window.appTabbedManager.switchTab('tasks')">
|
||||||
<span class="tab-emoji">📋</span>
|
<span class="tab-emoji">📋</span>
|
||||||
@ -222,6 +226,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Updates Tab (per-app: version state, CVEs, recovery, history) -->
|
||||||
|
<div class="tab-pane" id="updater-tab">
|
||||||
|
<div class="app-updater-section" id="app-updater-section">
|
||||||
|
<div class="updater-empty">Loading…</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Tasks Tab -->
|
<!-- Tasks Tab -->
|
||||||
<div class="tab-pane" id="tasks-tab">
|
<div class="tab-pane" id="tasks-tab">
|
||||||
<div class="tasks-section">
|
<div class="tasks-section">
|
||||||
|
|||||||
@ -292,6 +292,10 @@ class AppTabbedManager {
|
|||||||
// IMPORTANT: Re-apply button state if there are running tasks
|
// IMPORTANT: Re-apply button state if there are running tasks
|
||||||
this.restoreButtonState();
|
this.restoreButtonState();
|
||||||
break;
|
break;
|
||||||
|
case 'updater':
|
||||||
|
await this.loadAppUpdater();
|
||||||
|
this.restoreButtonState();
|
||||||
|
break;
|
||||||
case 'services':
|
case 'services':
|
||||||
if (window.servicesManager) {
|
if (window.servicesManager) {
|
||||||
await window.servicesManager.load(this.currentApp);
|
await window.servicesManager.load(this.currentApp);
|
||||||
@ -553,6 +557,62 @@ class AppTabbedManager {
|
|||||||
await this.backupAppCard.render();
|
await this.backupAppCard.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the per-app Updates tab — version state + CVEs + recovery + history for
|
||||||
|
// the current app. Reuses the headless UpdaterPage: the same data layer the
|
||||||
|
// fleet Overview uses, and renderAppDetail() for the body. Actions dispatch
|
||||||
|
// through the task system, identical to the fleet view.
|
||||||
|
async loadAppUpdater() {
|
||||||
|
const section = document.getElementById('app-updater-section');
|
||||||
|
if (!section) return;
|
||||||
|
if (typeof UpdaterPage === 'undefined') {
|
||||||
|
section.innerHTML = '<div class="updater-empty">Updater unavailable.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.appUpdater) this.appUpdater = new UpdaterPage((window.LP && window.LP.services) || {});
|
||||||
|
|
||||||
|
// One delegated listener scoped to this section (the pane lives in the
|
||||||
|
// shared layout, replaced on full navigation, so it GCs with the node).
|
||||||
|
if (section.dataset.updBound !== '1') {
|
||||||
|
section.dataset.updBound = '1';
|
||||||
|
section.addEventListener('click', (e) => {
|
||||||
|
const a = e.target.closest('[data-updater-action]');
|
||||||
|
if (!a) return;
|
||||||
|
const app = a.dataset.app || this.currentApp;
|
||||||
|
switch (a.dataset.updaterAction) {
|
||||||
|
case 'check': this.appUpdater.checkForUpdates(); break;
|
||||||
|
case 'update': this.appUpdater.applyUpdate(app); break;
|
||||||
|
case 'rollback': this.appUpdater.rollback(app); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
section.innerHTML = '<div class="updater-empty">Loading…</div>';
|
||||||
|
await this.appUpdater.refreshAll();
|
||||||
|
const app = (this.appUpdater.apps || []).find((x) => x.name === this.currentApp);
|
||||||
|
if (!app) {
|
||||||
|
section.innerHTML = `<div class="updater-empty">No update data for this app yet. <button class="updater-btn updater-btn-primary" data-updater-action="check">Check now</button></div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
section.innerHTML = this.renderAppUpdaterHead(app) + this.appUpdater.renderAppDetail(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAppUpdaterHead(a) {
|
||||||
|
const up = this.appUpdater;
|
||||||
|
const esc = (s) => up.escape(s);
|
||||||
|
const cur = esc(a.current_version || a.current_image || '—');
|
||||||
|
const avail = a.update_available ? esc(a.available_version || a.available_image || 'newer') : null;
|
||||||
|
const badge = a.update_available
|
||||||
|
? `<span class="updater-badge updater-badge-update">update available</span>`
|
||||||
|
: (a.scanned ? `<span class="updater-badge updater-badge-ok">up to date</span>` : `<span class="updater-badge updater-badge-unknown">unscanned</span>`);
|
||||||
|
const updBtn = a.update_available
|
||||||
|
? `<button class="updater-btn updater-btn-primary" data-updater-action="update" data-app="${esc(a.name)}">Update</button>`
|
||||||
|
: '';
|
||||||
|
return `<div class="app-updater-head">
|
||||||
|
<div class="app-updater-head-main">${badge} <span class="updater-row-ver">${cur}${avail ? ` <span class="updater-arrow">→</span> <strong>${avail}</strong>` : ''}</span></div>
|
||||||
|
<div class="app-updater-head-actions"><button class="updater-btn" data-updater-action="check">↻ Check</button> ${updBtn}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the tabbed manager
|
// Initialize the tabbed manager
|
||||||
async initialize() {
|
async initialize() {
|
||||||
// Prevent double initialization
|
// Prevent double initialization
|
||||||
@ -849,7 +909,7 @@ class AppTabbedManager {
|
|||||||
|
|
||||||
// Disable config, services and backup tabs when task is running
|
// Disable config, services and backup tabs when task is running
|
||||||
disableTabs() {
|
disableTabs() {
|
||||||
const tabs = ['config', 'services', 'tools', 'backups']
|
const tabs = ['config', 'services', 'tools', 'backups', 'updater']
|
||||||
.map(name => document.querySelector(`.main-tab-button[data-tab="${name}"], .tab-button[data-tab="${name}"]`))
|
.map(name => document.querySelector(`.main-tab-button[data-tab="${name}"], .tab-button[data-tab="${name}"]`))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
@ -864,7 +924,7 @@ class AppTabbedManager {
|
|||||||
|
|
||||||
// Enable config, services and backup tabs when task completes
|
// Enable config, services and backup tabs when task completes
|
||||||
enableTabs() {
|
enableTabs() {
|
||||||
const tabs = ['config', 'services', 'tools', 'backups']
|
const tabs = ['config', 'services', 'tools', 'backups', 'updater']
|
||||||
.map(name => document.querySelector(`.main-tab-button[data-tab="${name}"], .tab-button[data-tab="${name}"]`))
|
.map(name => document.querySelector(`.main-tab-button[data-tab="${name}"], .tab-button[data-tab="${name}"]`))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|||||||
@ -172,3 +172,20 @@
|
|||||||
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, .08));
|
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, .08));
|
||||||
}
|
}
|
||||||
.ov-loc-row span:first-child { font-weight: 500; }
|
.ov-loc-row span:first-child { font-weight: 500; }
|
||||||
|
|
||||||
|
/* ---- per-app Updates tab header ----------------------------------------- */
|
||||||
|
.app-updater-section { padding: 4px 0; }
|
||||||
|
.app-updater-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid var(--border-color, rgba(255, 255, 255, .12));
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--input-bg, rgba(255, 255, 255, .03));
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.app-updater-head-main { display: flex; align-items: center; gap: 10px; }
|
||||||
|
.app-updater-head-actions { display: flex; gap: 8px; }
|
||||||
|
|||||||
@ -195,6 +195,7 @@ class SystemLoader {
|
|||||||
'/components/apps/port-manager/js/port-manager.js',
|
'/components/apps/port-manager/js/port-manager.js',
|
||||||
'/core/tasks/js/task-manager.js', // Add TaskManager for backup functionality
|
'/core/tasks/js/task-manager.js', // Add TaskManager for backup functionality
|
||||||
'/core/backup-card/js/backup-app-card.js',
|
'/core/backup-card/js/backup-app-card.js',
|
||||||
|
'/components/updater/js/updater-page.js', // headless reuse: per-app Updates tab + fleet Overview
|
||||||
'/components/apps/services/js/services-manager.js',
|
'/components/apps/services/js/services-manager.js',
|
||||||
'/components/apps/tools/js/tools-manager.js',
|
'/components/apps/tools/js/tools-manager.js',
|
||||||
'/components/apps/routing/js/routing-manager.js',
|
'/components/apps/routing/js/routing-manager.js',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user