updates: only send email notifications when not auto-updating
fixes #749
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
Dear Cloudron Admin,
|
||||
|
||||
<% for (var i = 0; i < apps.length; i++) { -%>
|
||||
A new version <%= apps[i].updateInfo.manifest.version %> of the app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> is available.
|
||||
The app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> has an update available.
|
||||
|
||||
Changes:
|
||||
<%= apps[i].app.manifest.title %> v<%= apps[i].updateInfo.manifest.version %> changes:
|
||||
<%= apps[i].updateInfo.manifest.changelog %>
|
||||
|
||||
<% } -%>
|
||||
@@ -29,10 +29,10 @@ Sent at: <%= new Date().toUTCString() %>
|
||||
<div style="width: 650px; text-align: left;">
|
||||
<% for (var i = 0; i < apps.length; i++) { -%>
|
||||
<p>
|
||||
A new version <%= apps[i].updateInfo.manifest.version %> of the app '<%= apps[i].app.manifest.title %>' installed at <a href="https://<%= apps[i].app.fqdn %>"><%= apps[i].app.fqdn %></a> is available.
|
||||
The app '<%= apps[i].app.manifest.title %>' installed at <a href="https://<%= apps[i].app.fqdn %>"><%= apps[i].app.fqdn %></a> has an update available.
|
||||
</p>
|
||||
|
||||
<h5>Changelog:</h5>
|
||||
<h5><%= apps[i].app.manifest.title %> v<%= apps[i].updateInfo.manifest.version %> changes:</h5>
|
||||
<%- apps[i].changelogHTML %>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<%if (format === 'text') { %>
|
||||
|
||||
Dear <%= cloudronName %> Admin,
|
||||
|
||||
Cloudron v<%= newBoxVersion %> is now available!
|
||||
|
||||
Changes:
|
||||
<% for (var i = 0; i < changelog.length; i++) { %>
|
||||
* <%- changelog[i] %>
|
||||
<% } %>
|
||||
|
||||
Powered by https://cloudron.io
|
||||
|
||||
Sent at: <%= new Date().toUTCString() %>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<center>
|
||||
|
||||
<img src="<%= cloudronAvatarUrl %>" width="128px" height="128px"/>
|
||||
|
||||
<h3>Dear <%= cloudronName %> Admin,</h3>
|
||||
|
||||
<div style="width: 650px; text-align: left;">
|
||||
<p>
|
||||
Cloudron v<b><%= newBoxVersion %></b> is now available!
|
||||
</p>
|
||||
|
||||
<h5>Changes:</h5>
|
||||
<ul>
|
||||
<% for (var i = 0; i < changelogHTML.length; i++) { %>
|
||||
<li><%- changelogHTML[i] %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 10px; color: #333333; background: #ffffff;">
|
||||
Powered by <a href="https://cloudron.io">Cloudron</a>.
|
||||
</div>
|
||||
|
||||
</center>
|
||||
|
||||
<% } %>
|
||||
+50
-12
@@ -1,22 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
passwordReset: passwordReset,
|
||||
appUpdatesAvailable: appUpdatesAvailable,
|
||||
passwordReset,
|
||||
boxUpdateAvailable,
|
||||
appUpdatesAvailable,
|
||||
|
||||
sendInvite: sendInvite,
|
||||
sendInvite,
|
||||
|
||||
appUp: appUp,
|
||||
appDied: appDied,
|
||||
appUpdated: appUpdated,
|
||||
oomEvent: oomEvent,
|
||||
appUp,
|
||||
appDied,
|
||||
appUpdated,
|
||||
oomEvent,
|
||||
|
||||
backupFailed: backupFailed,
|
||||
backupFailed,
|
||||
|
||||
certificateRenewalError: certificateRenewalError,
|
||||
boxUpdateError: boxUpdateError,
|
||||
certificateRenewalError,
|
||||
boxUpdateError,
|
||||
|
||||
sendTestMail: sendTestMail,
|
||||
sendTestMail,
|
||||
|
||||
_mailQueue: [] // accumulate mails in test mode
|
||||
};
|
||||
@@ -256,6 +257,43 @@ function appUpdated(mailTo, app, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function boxUpdateAvailable(mailTo, updateInfo, callback) {
|
||||
assert.strictEqual(typeof mailTo, 'string');
|
||||
assert.strictEqual(typeof updateInfo, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
getMailConfig(function (error, mailConfig) {
|
||||
if (error) return debug('Error getting mail details:', error);
|
||||
|
||||
var converter = new showdown.Converter();
|
||||
|
||||
var templateData = {
|
||||
webadminUrl: settings.adminOrigin(),
|
||||
newBoxVersion: updateInfo.version,
|
||||
changelog: updateInfo.changelog,
|
||||
changelogHTML: updateInfo.changelog.map(function (e) { return converter.makeHtml(e); }),
|
||||
cloudronName: mailConfig.cloudronName,
|
||||
cloudronAvatarUrl: settings.adminOrigin() + '/api/v1/cloudron/avatar'
|
||||
};
|
||||
|
||||
var templateDataText = JSON.parse(JSON.stringify(templateData));
|
||||
templateDataText.format = 'text';
|
||||
|
||||
var templateDataHTML = JSON.parse(JSON.stringify(templateData));
|
||||
templateDataHTML.format = 'html';
|
||||
|
||||
var mailOptions = {
|
||||
from: mailConfig.notificationFrom,
|
||||
to: mailTo,
|
||||
subject: 'Cloudron update available',
|
||||
text: render('box_update_available.ejs', templateDataText),
|
||||
html: render('box_update_available.ejs', templateDataHTML)
|
||||
};
|
||||
|
||||
sendMail(mailOptions, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function appUpdatesAvailable(mailTo, apps, callback) {
|
||||
assert.strictEqual(typeof mailTo, 'string');
|
||||
assert.strictEqual(typeof apps, 'object');
|
||||
@@ -285,7 +323,7 @@ function appUpdatesAvailable(mailTo, apps, callback) {
|
||||
var mailOptions = {
|
||||
from: mailConfig.notificationFrom,
|
||||
to: mailTo,
|
||||
subject: `New app updates available for ${mailConfig.cloudronName}`,
|
||||
subject: 'App updates available',
|
||||
text: render('app_updates_available.ejs', templateDataText),
|
||||
html: render('app_updates_available.ejs', templateDataHTML)
|
||||
};
|
||||
|
||||
+43
-5
@@ -1,11 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = {
|
||||
get: get,
|
||||
ack: ack,
|
||||
getAllPaged: getAllPaged,
|
||||
get,
|
||||
ack,
|
||||
getAllPaged,
|
||||
|
||||
onEvent: onEvent,
|
||||
onEvent,
|
||||
|
||||
appUpdatesAvailable,
|
||||
boxUpdateAvailable,
|
||||
|
||||
// NOTE: if you add an alert, be sure to add title below
|
||||
ALERT_BACKUP_CONFIG: 'backupConfig',
|
||||
@@ -20,11 +23,13 @@ exports = module.exports = {
|
||||
_add: add
|
||||
};
|
||||
|
||||
let assert = require('assert'),
|
||||
let apps = require('./apps.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
auditSource = require('./auditsource.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
changelog = require('./changelog.js'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:notifications'),
|
||||
eventlog = require('./eventlog.js'),
|
||||
mailer = require('./mailer.js'),
|
||||
@@ -217,6 +222,39 @@ function appUpdated(eventId, app, callback) {
|
||||
}, callback);
|
||||
}
|
||||
|
||||
function boxUpdateAvailable(updateInfo, callback) {
|
||||
assert.strictEqual(typeof updateInfo, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getAutoupdatePattern(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
if (result !== constants.AUTOUPDATE_PATTERN_NEVER) return callback();
|
||||
|
||||
forEachAdmin({ skip: [] }, function (admin, done) {
|
||||
mailer.boxUpdateAvailable(admin.email, updateInfo, done);
|
||||
}, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function appUpdatesAvailable(appUpdates, callback) {
|
||||
assert.strictEqual(typeof appUpdates, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
settings.getAutoupdatePattern(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// if we are auto updating, then just consider apps that cannot be auto updated
|
||||
if (result !== constants.AUTOUPDATE_PATTERN_NEVER) appUpdates = appUpdates.filter(update => !apps.canAutoupdateApp(update.app, update.updateInfo));
|
||||
|
||||
if (appUpdates.length === 0) return callback();
|
||||
|
||||
forEachAdmin({ skip: [] }, function (admin, done) {
|
||||
mailer.appUpdatesAvailable(admin.email, appUpdates, done);
|
||||
}, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function boxUpdated(eventId, oldVersion, newVersion, callback) {
|
||||
assert.strictEqual(typeof eventId, 'string');
|
||||
assert.strictEqual(typeof oldVersion, 'string');
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('updatechecker - box', function () {
|
||||
expect(updatechecker.getUpdateInfo().box.sourceTarballUrl).to.be('box.tar.gz');
|
||||
expect(scope.isDone()).to.be.ok();
|
||||
|
||||
checkMails(0, done); // it seems we stopped sending mails for box updates!
|
||||
checkMails(1, done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+26
-47
@@ -12,14 +12,10 @@ var apps = require('./apps.js'),
|
||||
appstore = require('./appstore.js'),
|
||||
assert = require('assert'),
|
||||
async = require('async'),
|
||||
constants = require('./constants.js'),
|
||||
debug = require('debug')('box:updatechecker'),
|
||||
mailer = require('./mailer.js'),
|
||||
notifications = require('./notifications.js'),
|
||||
paths = require('./paths.js'),
|
||||
safe = require('safetydance'),
|
||||
settings = require('./settings.js'),
|
||||
users = require('./users.js');
|
||||
safe = require('safetydance');
|
||||
|
||||
function setUpdateInfo(state) {
|
||||
// appid -> update info { creationDate, manifest }
|
||||
@@ -44,55 +40,38 @@ function checkAppUpdates(options, callback) {
|
||||
let state = getUpdateInfo();
|
||||
let newState = { }; // create new state so that old app ids are removed
|
||||
|
||||
settings.getAutoupdatePattern(function (error, result) {
|
||||
var pendingNotifications = [];
|
||||
|
||||
apps.getAll(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const autoupdatesEnabled = (result !== constants.AUTOUPDATE_PATTERN_NEVER);
|
||||
|
||||
var notificationPending = [];
|
||||
async.eachSeries(result, function (app, iteratorDone) {
|
||||
if (app.appStoreId === '') return iteratorDone(); // appStoreId can be '' for dev apps
|
||||
|
||||
apps.getAll(function (error, result) {
|
||||
if (error) return callback(error);
|
||||
appstore.getAppUpdate(app, options, function (error, updateInfo) {
|
||||
if (error) {
|
||||
debug('Error getting app update info for %s', app.id, error);
|
||||
return iteratorDone(); // continue to next
|
||||
}
|
||||
|
||||
async.eachSeries(result, function (app, iteratorDone) {
|
||||
if (app.appStoreId === '') return iteratorDone(); // appStoreId can be '' for dev apps
|
||||
if (!updateInfo) return iteratorDone(); // skip if no next version is found
|
||||
|
||||
appstore.getAppUpdate(app, options, function (error, updateInfo) {
|
||||
if (error) {
|
||||
debug('Error getting app update info for %s', app.id, error);
|
||||
return iteratorDone(); // continue to next
|
||||
}
|
||||
newState[app.id] = updateInfo;
|
||||
|
||||
if (!updateInfo) return iteratorDone(); // skip if no next version is found
|
||||
if (safe.query(state[app.id], 'manifest.version') === updateInfo.manifest.version) {
|
||||
debug(`Skipping app update notification of ${app.id} since user was already notified of ${updateInfo.manifest.version}`);
|
||||
return iteratorDone();
|
||||
}
|
||||
|
||||
newState[app.id] = updateInfo;
|
||||
|
||||
if (safe.query(state[app.id], 'manifest.version') === updateInfo.manifest.version) {
|
||||
debug(`Skipping app update notification of ${app.id} since user was already notified of ${updateInfo.manifest.version}`);
|
||||
return iteratorDone();
|
||||
}
|
||||
|
||||
const canAutoupdateApp = apps.canAutoupdateApp(app, updateInfo);
|
||||
if (autoupdatesEnabled && canAutoupdateApp) return iteratorDone();
|
||||
|
||||
debug(`Notifying of app update for ${app.id} from ${app.manifest.version} to ${updateInfo.manifest.version}`);
|
||||
notificationPending.push({ app, updateInfo });
|
||||
iteratorDone();
|
||||
});
|
||||
}, function () {
|
||||
if ('box' in state) newState.box = state.box; // preserve the latest box state information
|
||||
setUpdateInfo(newState);
|
||||
|
||||
if (notificationPending.length === 0) return callback();
|
||||
|
||||
users.getAdmins(function (error, admins) {
|
||||
if (error) {
|
||||
debug('checkAppUpdates: failed to get admins', error);
|
||||
return callback();
|
||||
}
|
||||
|
||||
async.eachSeries(admins, (admin, done) => mailer.appUpdatesAvailable(admin.email, notificationPending, done), callback);
|
||||
});
|
||||
pendingNotifications.push({ app, updateInfo });
|
||||
iteratorDone();
|
||||
});
|
||||
}, function () {
|
||||
if ('box' in state) newState.box = state.box; // preserve the latest box state information
|
||||
|
||||
setUpdateInfo(newState);
|
||||
|
||||
notifications.appUpdatesAvailable(pendingNotifications, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -123,7 +102,7 @@ function checkBoxUpdates(options, callback) {
|
||||
state.box = updateInfo;
|
||||
setUpdateInfo(state);
|
||||
|
||||
callback();
|
||||
notifications.boxUpdateAvailable(updateInfo, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user