🤞 Sync 2026-06-25 00:16:31

This commit is contained in:
github-actions[bot] committed 2026-06-25 00:16:31 +08:00
1 parent 251e9e9dd1
commit bbcd3f17c5
14 files changed
+505 -276

No files matched your search

@@ -9,32 +9,48 @@
*/
return view.extend({
load: function() {
return Promise.resolve();
// Format raw firmware filename → "OpenWrt 24.10.7 202606-rev01"
fmtVer: function(raw) {
if (!raw) return '';
// From openwrt-24.10.7-4g-lte-202606-rev01
var m = raw.match(/^openwrt-([\d.]+)-.*-(\d{6}-rev\d+)$/);
if (m) return 'OpenWrt ' + m[1] + ' ' + m[2];
return raw;
},
checkInitialState: function() {
load: function() {
return fs.read('/usr/lib/os-release')
.then(function(content) {
// OPENWRT_RELEASE="OpenWrt 24.10.7~4g-lte-202606-rev01"
var m = (content || '').match(/OPENWRT_RELEASE="(OpenWrt\s+[\d.]+)~.*-(\d{6}-rev\d+)"/);
if (m) return m[1].trim() + ' ' + m[2].trim();
return '';
})
.catch(function() { return ''; });
},
checkInitialState: function(currentVer) {
var self = this;
this.verifyCheckResult().then(function(success) {
// Show current version immediately from fw_rev
self.updateVersionBlock(currentVer, '—', false);
// Restore available version if previous check result still on disk
self.verifyCheckResult(currentVer).then(function(success) {
if (success) {
self.checkButton.disabled = true;
self.showUpdateAvailable();
self.upgradeButton.disabled = false;
}
}).catch(function() {});
},
showUpdateAvailable: function() {
var self = this;
self.statusDiv.innerHTML = '';
self.statusDiv.appendChild(E('div', { 'class': 'alert-message success' }, [
E('h3', {}, _('Update Available')),
E('button', {
'class': 'btn cbi-button cbi-button-neutral',
'style': 'margin-top: 6px;',
'click': function() { self.showChangelog(); }
}, _('Show Changelog'))
]));
updateVersionBlock: function(current, upgrade, hasUpdate) {
this.verCurrentVal.textContent = current || '—';
this.verAvailVal.textContent = upgrade || '—';
// Changelog link visible only when update is available
this.verChangelogLink.style.display = hasUpdate ? '' : 'none';
// Colour: important (green) = update found, warning (yellow) = no update / unknown
this.versionBlock.className = 'alert-message ' + (hasUpdate ? 'success' : 'warning');
},
showChangelog: function() {
@@ -52,7 +68,7 @@ return view.extend({
]);
},
render: function() {
render: function(data) {
var self = this;
this.checkButton = E('button', {
@@ -66,7 +82,29 @@ return view.extend({
'click': ui.createHandlerFn(this, 'handleUpgrade')
}, _('Upgrade'));
this.statusDiv = E('div', { 'class': 'cbi-section' });
// Version info rows — always visible, updated after check
this.verCurrentVal = E('span', {}, '—');
this.verAvailVal = E('span', {}, '—');
this.verChangelogLink = E('button', {
'class': 'btn cbi-button cbi-button-neutral',
'style': 'display: none; margin-left: 10px;',
'click': function() { self.showChangelog(); }
}, _('Show Changelog'));
this.versionBlock = E('div', { 'class': 'alert-message', 'style': 'margin-top: 1.5em;' }, [
E('table', { 'style': 'border: none; background: none;' }, [
E('tr', {}, [
E('td', { 'style': 'padding: 2px 10px 2px 0; font-weight: bold; white-space: nowrap;' },
_('Current version:')),
E('td', {}, this.verCurrentVal)
]),
E('tr', {}, [
E('td', { 'style': 'padding: 2px 10px 2px 0; font-weight: bold; white-space: nowrap;' },
_('Available version:')),
E('td', {}, [ this.verAvailVal, this.verChangelogLink ])
])
])
]);
var container = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('OTA System Update')),
@@ -75,10 +113,10 @@ return view.extend({
' ',
this.upgradeButton
]),
this.statusDiv
this.versionBlock
]);
this.checkInitialState();
this.checkInitialState(data);
return container;
},
@@ -87,33 +125,23 @@ return view.extend({
var self = this;
this.checkButton.disabled = true;
this.statusDiv.innerHTML = '';
this.statusDiv.appendChild(E('div', { 'class': 'spinner' }, _('Checking for updates...')));
this.upgradeButton.disabled = true;
return fs.exec('/usr/share/ota.sh', ['check'])
.then(function() {
return self.verifyCheckResult();
return self.verifyCheckResult(self.verCurrentVal.textContent);
})
.then(function(success) {
if (success) {
self.upgradeButton.disabled = false;
self.showUpdateAvailable();
} else {
self.statusDiv.innerHTML = '';
self.statusDiv.appendChild(E('div', { 'class': 'alert-message warning' },
_('No updates available or check failed')));
}
self.upgradeButton.disabled = !success;
self.checkButton.disabled = false;
})
.catch(function(err) {
self.statusDiv.innerHTML = '';
self.statusDiv.appendChild(E('div', { 'class': 'alert-message error' },
_('Check failed: ') + (err.message || err)));
self.updateVersionBlock('', _('Check failed: ') + (err.message || err), false);
self.checkButton.disabled = false;
});
},
verifyCheckResult: function() {
verifyCheckResult: function(currentVer) {
var self = this;
return Promise.all([
@@ -124,11 +152,33 @@ return view.extend({
self.changelog = content;
return content && content.length > 0;
})
.catch(function() { return false; })
.catch(function() { return false; }),
fs.read('/tmp/ota_version')
.then(function(content) { return content || ''; })
.catch(function() { return ''; })
]).then(function(results) {
return results[0] !== null &&
results[1] !== null &&
results[2] === true;
var hasProfiles = results[0] !== null;
var hasLock = results[1] !== null;
var hasChangelog = results[2];
var verContent = results[3];
// both current= and upgrade= are full firmware names written by ota.sh
var current = '', upgrade = '';
verContent.split('\n').forEach(function(line) {
var m;
if ((m = line.match(/^current=(.+)/))) current = self.fmtVer(m[1].trim());
if ((m = line.match(/^upgrade=(.+)/))) upgrade = self.fmtVer(m[1].trim());
});
// fallback: if ota_version missing, use value from load() (already formatted)
if (!current) current = currentVer || '';
if (hasProfiles && hasLock && hasChangelog) {
self.updateVersionBlock(current, upgrade, true);
return true;
}
self.updateVersionBlock(current, _('No updates available'), false);
return false;
});
},
@@ -142,53 +192,46 @@ return view.extend({
this.upgradeButton.disabled = true;
this.checkButton.disabled = true;
// Remove stale progress file before starting
fs.remove('/tmp/ota_progress').catch(function() {});
self.openUpgradeModal();
return fs.exec('/usr/share/ota.sh', ['upgrade'])
// ota-launch.sh starts ota.sh in background and exits immediately
return fs.exec('/usr/share/ota-launch.sh', [])
.then(function() {
self.stopProgressPolling();
self.setModalProgress(100, _('Upgrade started. Device will reboot...'), 'done');
self.finalizeUpgradeModal(false);
// launcher exited — polling is running, wait for 'done' or 'error'
})
.catch(function(err) {
var msg = err.message || err.toString();
self.stopProgressPolling();
// XHR timeout = sysupgrade already running, treat as success
if (msg.indexOf('timed out') !== -1) {
self.setModalProgress(100, _('Upgrade started. Device will reboot...'), 'done');
self.finalizeUpgradeModal(false);
} else {
self.setModalProgress(0, _('Error: ') + msg, 'error');
self.finalizeUpgradeModal(true);
// XHR timeout is expected — launcher or sysupgrade still running,
// polling continues and will finalize via 'flashing'/'error' state
if (msg.indexOf('timed out') !== -1 || msg.indexOf('XHR') !== -1) {
return;
}
// Genuine exec failure
self.stopProgressPolling();
self.setModalProgress(0, _('Launch failed: ') + msg, 'error');
self.finalizeUpgradeModal(true);
});
},
openUpgradeModal: function() {
var self = this;
// Progress bar element
self.modalProgressInner = E('div', { 'style': 'width: 0%; transition: width 0.4s ease;' });
self.modalProgressBar = E('div', { 'class': 'cbi-progressbar', 'style': 'margin: 10px 0;' },
self.modalProgressInner);
self.modalProgressBar = E('div', {
'class': 'cbi-progressbar',
'style': 'margin: 10px 0;'
}, self.modalProgressInner);
// Status label
self.modalStatus = E('div', {
'style': 'font-size: 0.9em; color: #666; margin-bottom: 8px;'
}, _('Starting upgrade...'));
// Log area
self.modalLog = E('pre', {
'style': 'white-space: pre-wrap; max-height: 200px; overflow-y: auto; ' +
'background: #f5f5f5; padding: 8px; border-radius: 3px; ' +
'color: black; font-size: 0.85em; margin-top: 10px;'
}, '');
// Close button (disabled until done)
self.modalCloseBtn = E('button', {
'class': 'btn cbi-button',
'disabled': true,
@@ -209,7 +252,6 @@ return view.extend({
])
]);
// Start polling /tmp/ota_progress
self.startProgressPolling();
},
@@ -227,7 +269,6 @@ return view.extend({
if (line.indexOf('downloading') === 0) {
var pct = parseInt(line.split(' ')[1], 10) || 0;
// downloading maps to 0..70% of bar
var barPct = Math.round(pct * 0.70);
self.setModalProgress(barPct,
_('Downloading firmware: ') + pct + '%', 'progress');
@@ -242,18 +283,37 @@ return view.extend({
self.appendModalLog(_('Testing firmware image...'));
} else if (line === 'flashing') {
self.stopProgressPolling();
self.setModalProgress(95, _('Flashing! DO NOT POWER OFF!'), 'progress');
self.appendModalLog(_('Flashing firmware...'));
// Simulate reboot wait with countdown
var countdown = 90;
self.setModalProgress(98, _('Device will be rebooted, please wait... ') + countdown + 's', 'progress');
var rebootTimer = setInterval(function() {
countdown--;
if (countdown <= 0) {
clearInterval(rebootTimer);
self.setModalProgress(100, _('Device will be rebooted, please wait... '), 'done');
self.finalizeUpgradeModal(false);
} else {
self.setModalProgress(98, _('Device will be rebooted, please wait... ') + countdown + 's', 'progress');
}
}, 1000);
} else if (line === 'done') {
self.stopProgressPolling();
self.setModalProgress(100, _('Upgrade started. Device will reboot...'), 'done');
self.appendModalLog(_('Done. Waiting for reboot...'));
self.finalizeUpgradeModal(false);
} else if (line.indexOf('error') === 0) {
var reason = line.replace('error', '').trim();
self.stopProgressPolling();
self.setModalProgress(0, _('Error: ') + reason, 'error');
self.appendModalLog(_('FAILED: ') + reason);
self.stopProgressPolling();
self.finalizeUpgradeModal(true);
}
}).catch(function() {
// file not yet created — ignore
});
}).catch(function() {});
}, 1500);
},
@@ -269,7 +329,6 @@ return view.extend({
var pct = Math.min(100, Math.max(0, percent));
this.modalProgressInner.style.width = pct + '%';
this.modalStatus.textContent = text;
if (state === 'error') {
this.modalStatus.style.color = '#c00';
} else if (state === 'done') {
@@ -282,7 +341,6 @@ return view.extend({
appendModalLog: function(line) {
if (!this.modalLog) return;
this.modalLog.textContent += line + '\n';
// Auto-scroll to bottom
this.modalLog.scrollTop = this.modalLog.scrollHeight;
},
@@ -291,11 +349,9 @@ return view.extend({
this.modalCloseBtn.disabled = false;
}
if (!isError) {
// For successful flash — don't re-enable buttons,
// device is about to reboot
this.statusDiv.innerHTML = '';
this.statusDiv.appendChild(E('div', { 'class': 'alert-message warning' },
_('Upgrade started! DO NOT POWER OFF THIS DEVICE! System will reboot after upgrade.')));
this.versionBlock.className = 'alert-message warning';
this.verAvailVal.textContent = _('Upgrade started — device will reboot');
this.verChangelogLink.style.display = 'none';
} else {
this.upgradeButton.disabled = false;
this.checkButton.disabled = false;