diff --git a/dashboard/public/js/client.js b/dashboard/public/js/client.js index 20ba3335c..b00511c77 100644 --- a/dashboard/public/js/client.js +++ b/dashboard/public/js/client.js @@ -3670,6 +3670,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout var ACTION_BACKUP_START = 'backup.start'; var ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start'; var ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish'; + + var ACTION_BRANDING_AVATAR = 'branding.avatar'; + var ACTION_BRANDING_NAME = 'branding.name'; + var ACTION_BRANDING_FOOTER = 'branding.footer'; + var ACTION_CERTIFICATE_NEW = 'certificate.new'; var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew'; var ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup'; @@ -3927,6 +3932,15 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout case ACTION_BACKUP_CLEANUP_FINISH: return data.errorMessage ? 'Backup cleaner errored: ' + data.errorMessage : 'Backup cleaner removed ' + (data.removedBoxBackupPaths ? data.removedBoxBackupPaths.length : '0') + ' backups'; + case ACTION_BRANDING_AVATAR: + return 'Cloudron Avatar Changed'; + + case ACTION_BRANDING_NAME: + return 'Cloudron Name set to ' + data.name; + + case ACTION_BRANDING_FOOTER: + return 'Cloudron Footer set to ' + data.footer; + case ACTION_CERTIFICATE_NEW: return 'Certificate install for ' + data.domain + (errorMessage ? ' failed' : ' succeeded'); diff --git a/dashboard/public/views/eventlog.js b/dashboard/public/views/eventlog.js index 4e0abc111..e543c09a0 100644 --- a/dashboard/public/views/eventlog.js +++ b/dashboard/public/views/eventlog.js @@ -36,6 +36,9 @@ angular.module('Application').controller('EventLogController', ['$scope', '$loca { name: 'backup.cleanup.finish', value: 'backup.cleanup.finish' }, { name: 'backup.finish', value: 'backup.finish' }, { name: 'backup.start', value: 'backup.start' }, + { name: 'branding.avatar', value: 'branding.avatar' }, + { name: 'branding.footer', value: 'branding.footer' }, + { name: 'branding.name', value: 'branding.name' }, { name: 'certificate.new', value: 'certificate.new' }, { name: 'certificate.renew', value: 'certificate.renew' }, { name: 'certificate.cleanup', value: 'certificate.cleanup' }, diff --git a/src/branding.js b/src/branding.js index be1a925d3..3e8ffd943 100644 --- a/src/branding.js +++ b/src/branding.js @@ -21,6 +21,7 @@ const apps = require('./apps.js'), BoxError = require('./boxerror.js'), constants = require('./constants.js'), debug = require('debug')('box:branding'), + eventlog = require('./eventlog.js'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'); @@ -45,6 +46,7 @@ async function setCloudronName(name, auditSource) { await safe(apps.configureApps(installedApps.filter((a) => !!a.manifest.addons?.oidc), { scheduleNow: true }, auditSource), { debug }); await settings.set(settings.CLOUDRON_NAME_KEY, name); + await eventlog.add(eventlog.ACTION_BRANDING_NAME, auditSource, { name }); } async function getCloudronAvatar() { @@ -58,10 +60,12 @@ async function getCloudronAvatar() { throw new BoxError(BoxError.FS_ERROR, `Could not read avatar: ${safe.error.message}`); } -async function setCloudronAvatar(avatar) { +async function setCloudronAvatar(avatar, auditSource) { assert(Buffer.isBuffer(avatar)); + assert(auditSource && typeof auditSource === 'object'); await settings.setBlob(settings.CLOUDRON_AVATAR_KEY, avatar); + await eventlog.add(eventlog.ACTION_BRANDING_AVATAR, auditSource, {}); } async function getCloudronBackground() { @@ -90,8 +94,10 @@ async function getFooter() { return value || constants.FOOTER; } -async function setFooter(footer) { +async function setFooter(footer, auditSource) { assert.strictEqual(typeof footer, 'string'); + assert(auditSource && typeof auditSource === 'object'); await settings.set(settings.FOOTER_KEY, footer); + await eventlog.add(eventlog.ACTION_BRANDING_FOOTER, auditSource, { footer }); } diff --git a/src/eventlog.js b/src/eventlog.js index 44575aebe..543180571 100644 --- a/src/eventlog.js +++ b/src/eventlog.js @@ -35,6 +35,10 @@ exports = module.exports = { ACTION_BACKUP_CLEANUP_START: 'backup.cleanup.start', // obsolete ACTION_BACKUP_CLEANUP_FINISH: 'backup.cleanup.finish', + ACTION_BRANDING_NAME: 'branding.name', + ACTION_BRANDING_FOOTER: 'branding.footer', + ACTION_BRANDING_AVATAR: 'branding.avatar', + ACTION_CERTIFICATE_NEW: 'certificate.new', ACTION_CERTIFICATE_RENEWAL: 'certificate.renew', // obsolete ACTION_CERTIFICATE_CLEANUP: 'certificate.cleanup', diff --git a/src/routes/branding.js b/src/routes/branding.js index 1a87c6e12..251614d30 100644 --- a/src/routes/branding.js +++ b/src/routes/branding.js @@ -31,7 +31,7 @@ async function setFooter(req, res, next) { if (typeof req.body.footer !== 'string') return next(new HttpError(400, 'footer is required')); - const [error] = await safe(branding.setFooter(req.body.footer)); + const [error] = await safe(branding.setFooter(req.body.footer, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -63,7 +63,7 @@ async function setCloudronAvatar(req, res, next) { safe.fs.unlinkSync(req.files.avatar.path); if (!avatar) return next(500, safe.error.message); - const [error] = await safe(branding.setCloudronAvatar(avatar)); + const [error] = await safe(branding.setCloudronAvatar(avatar, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); diff --git a/src/test/branding-test.js b/src/test/branding-test.js index 6e75d1282..783b2438d 100644 --- a/src/test/branding-test.js +++ b/src/test/branding-test.js @@ -43,12 +43,12 @@ describe('Branding', function () { }); it('can render footer', async function () { - await branding.setFooter('BigFoot Inc'); + await branding.setFooter('BigFoot Inc', auditSource); expect(await branding.renderFooter()).to.be('BigFoot Inc'); }); it('can render footer with YEAR', async function () { - await branding.setFooter('BigFoot Inc %YEAR%'); + await branding.setFooter('BigFoot Inc %YEAR%', auditSource); expect(await branding.renderFooter()).to.be('BigFoot Inc 2024'); }); });