diff --git a/dashboard/src/components/app/Eventlog.vue b/dashboard/src/components/app/Eventlog.vue
new file mode 100644
index 000000000..faf22e6da
--- /dev/null
+++ b/dashboard/src/components/app/Eventlog.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+ {{ prettyDate(event.creationTime) }}
+ {{ eventlogSource(event, app) }}
+
+
+
+
diff --git a/dashboard/src/models/AppsModel.js b/dashboard/src/models/AppsModel.js
index 6293392f1..45c8de34c 100644
--- a/dashboard/src/models/AppsModel.js
+++ b/dashboard/src/models/AppsModel.js
@@ -242,6 +242,17 @@ function create() {
if (result.status !== 202) return [result];
return [null];
},
+ async getEvents(id) {
+ let result;
+ try {
+ result = await fetcher.get(`${origin}/api/v1/apps/${id}/eventlog`, { page: 1, per_page: 100, access_token: accessToken });
+ } catch (e) {
+ return [e];
+ }
+
+ if (result.status !== 200) return [result];
+ return [null, result.body.eventlogs];
+ },
};
}
diff --git a/dashboard/src/utils.js b/dashboard/src/utils.js
index 9a4439c57..616d43af4 100644
--- a/dashboard/src/utils.js
+++ b/dashboard/src/utils.js
@@ -1,4 +1,6 @@
+import { prettyBinarySize } from 'pankow/utils';
+
// https://stackoverflow.com/questions/3665115/how-to-create-a-file-in-memory-for-user-to-download-but-not-through-server#18197341
function download(filename, text) {
var element = document.createElement('a');
@@ -26,11 +28,467 @@ function s3like(provider) {
|| provider === 'contabo-objectstorage';
}
+// TODO not sure what appIdContext is good for
+function eventlogDetails(eventLog, app = null, appIdContext = '') {
+ const ACTION_ACTIVATE = 'cloudron.activate';
+ const ACTION_PROVISION = 'cloudron.provision';
+ const ACTION_RESTORE = 'cloudron.restore';
+
+ const ACTION_APP_CLONE = 'app.clone';
+ const ACTION_APP_REPAIR = 'app.repair';
+ const ACTION_APP_CONFIGURE = 'app.configure';
+ const ACTION_APP_INSTALL = 'app.install';
+ const ACTION_APP_RESTORE = 'app.restore';
+ const ACTION_APP_IMPORT = 'app.import';
+ const ACTION_APP_UNINSTALL = 'app.uninstall';
+ const ACTION_APP_UPDATE = 'app.update';
+ const ACTION_APP_UPDATE_FINISH = 'app.update.finish';
+ const ACTION_APP_BACKUP = 'app.backup';
+ const ACTION_APP_BACKUP_FINISH = 'app.backup.finish';
+ const ACTION_APP_LOGIN = 'app.login';
+ const ACTION_APP_OOM = 'app.oom';
+ const ACTION_APP_UP = 'app.up';
+ const ACTION_APP_DOWN = 'app.down';
+ const ACTION_APP_START = 'app.start';
+ const ACTION_APP_STOP = 'app.stop';
+ const ACTION_APP_RESTART = 'app.restart';
+
+ const ACTION_ARCHIVES_ADD = 'archives.add';
+ const ACTION_ARCHIVES_DEL = 'archives.del';
+
+ const ACTION_BACKUP_FINISH = 'backup.finish';
+ const ACTION_BACKUP_START = 'backup.start';
+ const ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start';
+ const ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish';
+
+ const ACTION_BRANDING_AVATAR = 'branding.avatar';
+ const ACTION_BRANDING_NAME = 'branding.name';
+ const ACTION_BRANDING_FOOTER = 'branding.footer';
+
+ const ACTION_CERTIFICATE_NEW = 'certificate.new';
+ const ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
+ const ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup';
+
+ const ACTION_DASHBOARD_DOMAIN_UPDATE = 'dashboard.domain.update';
+
+ const ACTION_DIRECTORY_SERVER_CONFIGURE = 'directoryserver.configure';
+
+ const ACTION_DOMAIN_ADD = 'domain.add';
+ const ACTION_DOMAIN_UPDATE = 'domain.update';
+ const ACTION_DOMAIN_REMOVE = 'domain.remove';
+
+ const ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure';
+
+ const ACTION_GROUP_ADD = 'group.add';
+ const ACTION_GROUP_UPDATE = 'group.update';
+ const ACTION_GROUP_REMOVE = 'group.remove';
+ const ACTION_GROUP_MEMBERSHIP = 'group.membership';
+
+ const ACTION_INSTALL_FINISH = 'cloudron.install.finish';
+
+ const ACTION_START = 'cloudron.start';
+ const ACTION_SERVICE_CONFIGURE = 'service.configure';
+ const ACTION_SERVICE_REBUILD = 'service.rebuild';
+ const ACTION_SERVICE_RESTART = 'service.restart';
+ const ACTION_UPDATE = 'cloudron.update';
+ const ACTION_UPDATE_FINISH = 'cloudron.update.finish';
+ const ACTION_USER_ADD = 'user.add';
+ const ACTION_USER_LOGIN = 'user.login';
+ const ACTION_USER_LOGIN_GHOST = 'user.login.ghost';
+ const ACTION_USER_LOGOUT = 'user.logout';
+ const ACTION_USER_REMOVE = 'user.remove';
+ const ACTION_USER_UPDATE = 'user.update';
+ const ACTION_USER_TRANSFER = 'user.transfer';
+
+ const ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE = 'userdirectory.profileconfig.update';
+
+ const ACTION_MAIL_LOCATION = 'mail.location';
+ const ACTION_MAIL_ENABLED = 'mail.enabled';
+ const ACTION_MAIL_DISABLED = 'mail.disabled';
+ const ACTION_MAIL_MAILBOX_ADD = 'mail.box.add';
+ const ACTION_MAIL_MAILBOX_UPDATE = 'mail.box.update';
+ const ACTION_MAIL_MAILBOX_REMOVE = 'mail.box.remove';
+ const ACTION_MAIL_LIST_ADD = 'mail.list.add';
+ const ACTION_MAIL_LIST_UPDATE = 'mail.list.update';
+ const ACTION_MAIL_LIST_REMOVE = 'mail.list.remove';
+
+ const ACTION_SUPPORT_TICKET = 'support.ticket';
+ const ACTION_SUPPORT_SSH = 'support.ssh';
+
+ const ACTION_VOLUME_ADD = 'volume.add';
+ const ACTION_VOLUME_UPDATE = 'volume.update';
+ const ACTION_VOLUME_REMOVE = 'volume.remove';
+
+ const ACTION_DYNDNS_UPDATE = 'dyndns.update';
+
+ const data = eventLog.data;
+ const errorMessage = data.errorMessage;
+ let details;
+
+ function appName(pre, app, defaultValue) {
+ if (appIdContext) return defaultValue || '';
+
+ pre = pre ? (pre + ' ') : '';
+
+ return pre + (app.label || app.fqdn || app.subdomain) + ' (' + app.manifest.title + ') ';
+ }
+
+ function eventBy() {
+ if (eventLog.source && eventLog.source.username) return ' by ' + eventLog.source.username;
+ return '';
+ }
+
+ switch (eventLog.action) {
+ case ACTION_ACTIVATE:
+ return 'Cloudron was activated';
+
+ case ACTION_PROVISION:
+ return 'Cloudron was setup';
+
+ case ACTION_RESTORE:
+ return 'Cloudron was restored using backup at ' + data.remotePath;
+
+ case ACTION_APP_CONFIGURE: {
+ if (!data.app) return '';
+ app = data.app;
+
+ if ('accessRestriction' in data) { // since it can be null
+ return 'Access restriction ' + appName('of', app) + ' was changed';
+ } else if ('operators' in data) {
+ return 'Operators ' + appName('of', app) + ' was changed';
+ } else if (data.label) {
+ return `Label ${appName('of', app)} was set to ${data.label}`;
+ } else if (data.tags) {
+ return `Tags ${appName('of', app)} was set to ${data.tags.join(', ')}`;
+ } else if (data.icon) {
+ return 'Icon ' + appName('of', app) + ' was changed';
+ } else if (data.memoryLimit) {
+ return 'Memory limit ' + appName('of', app) + ' was set to ' + prettyBinarySize(data.memoryLimit);
+ } else if (data.cpuShares) { // replaced by cpuQuota in 8.0
+ return 'CPU shares ' + appName('of', app) + ' was set to ' + Math.round((data.cpuShares * 100)/1024) + '%';
+ } else if (data.cpuQuota) {
+ return 'CPU quota ' + appName('of', app) + ' was set to ' + data.cpuQuota + '%';
+ } else if (data.env) {
+ return 'Env vars ' + appName('of', app) + ' was changed';
+ } else if ('debugMode' in data) { // since it can be null
+ if (data.debugMode) return appName('', app, 'App') + ' was placed in repair mode';
+ else return appName('', app, 'App') + ' was taken out of repair mode';
+ } else if ('enableBackup' in data) {
+ return 'Automatic backups ' + appName('of', app) + ' was ' + (data.enableBackup ? 'enabled' : 'disabled');
+ } else if ('enableAutomaticUpdate' in data) {
+ return 'Automatic updates ' + appName('of', app) + ' was ' + (data.enableAutomaticUpdate ? 'enabled' : 'disabled');
+ } else if ('reverseProxyConfig' in data) {
+ return 'Reverse proxy configuration ' + appName('of', app) + ' was updated';
+ } else if ('upstreamUri' in data) {
+ return 'Upstream URI ' + appName('of', app) + ' was updated';
+ } else if ('cert' in data) {
+ if (data.cert) return 'Custom certificate was set ' + appName('for', app);
+ else return 'Certificate ' + appName('of', app) + ' was reset';
+ } else if (data.subdomain) {
+ if (data.fqdn !== data.app.fqdn) {
+ return 'Location ' + appName('of', app) + ' was changed to ' + data.fqdn;
+ // TODO below we used to use angular.equals(data.redirectDomains, data.app.redirectDomains)
+ } else if (data.redirectDomains !== data.app.redirectDomains) {
+ const altFqdns = data.redirectDomains.map(a => a.fqdn);
+ return 'Alternate domains ' + appName('of', app) + ' was ' + (altFqdns.length ? 'set to ' + altFqdns.join(', ') : 'reset');
+ // TODO below we used to use angular.equals(data.aliasDomains, data.app.aliasDomains)
+ } else if (data.aliasDomains !== data.app.aliasDomains) {
+ const aliasDomains = data.aliasDomains.map(a => a.fqdn);
+ return 'Alias domains ' + appName('of', app) + ' was ' + (aliasDomains.length ? 'set to ' + aliasDomains.join(', ') : 'reset');
+ // TODO below we used to use angular.equals(data.portBindings, data.app.portBindings)
+ } else if (data.portBindings !== data.app.portBindings) {
+ return 'Port bindings ' + appName('of', app) + ' was changed';
+ }
+ } else if ('dataDir' in data) {
+ if (data.dataDir) return 'Data directory ' + appName('of', app) + ' was set ' + data.dataDir;
+ else return 'Data directory ' + appName('of', app) + ' was reset';
+ } else if ('icon' in data) {
+ if (data.icon) return 'Icon ' + appName('of', app) + ' was set';
+ else return 'Icon ' + appName('of', app) + ' was reset';
+ } else if ('mailboxName' in data) {
+ if (data.mailboxName) return `Mailbox ${appName('of', app)} was set to ${data.mailboxDisplayName || '' } ${data.mailboxName}@${data.mailboxDomain}`;
+ else return 'Mailbox ' + appName('of', app) + ' was disabled';
+ }
+
+ return appName('', app, 'App ') + 'was re-configured';
+ }
+
+ case ACTION_APP_INSTALL:
+ if (!data.app) return '';
+ return data.app.manifest.title + ' (package v' + data.app.manifest.version + ') was installed ' + appName('at', data.app) + eventBy();
+
+ case ACTION_APP_RESTORE:
+ if (!data.app) return '';
+ details = appName('', data.app, 'App') + ' was restored';
+ // older versions (<3.5) did not have these fields
+ if (data.fromManifest) details += ' from version ' + data.fromManifest.version;
+ if (data.toManifest) details += ' to version ' + data.toManifest.version;
+ if (data.remotePath) details += ' using backup at ' + data.remotePath;
+ return details;
+
+ case ACTION_APP_IMPORT:
+ if (!data.app) return '';
+ details = appName('', data.app, 'App') + ' was imported';
+ if (data.toManifest) details += ' to version ' + data.toManifest.version;
+ if (data.remotePath) details += ' using backup at ' + data.remotePath;
+ return details;
+
+ case ACTION_APP_UNINSTALL:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' (package v' + data.app.manifest.version + ') was uninstalled';
+
+ case ACTION_APP_UPDATE:
+ if (!data.app) return '';
+ return 'Update ' + appName('of', data.app) + ' started from v' + data.fromManifest.version + ' to v' + data.toManifest.version;
+
+ case ACTION_APP_UPDATE_FINISH:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' was updated to v' + data.app.manifest.version;
+
+ case ACTION_APP_BACKUP:
+ if (!data.app) return '';
+ return 'Backup ' + appName('of', data.app) + ' started';
+
+ case ACTION_APP_BACKUP_FINISH:
+ if (!data.app) return '';
+ if (data.errorMessage) return 'Backup ' + appName('of', data.app) + ' failed: ' + data.errorMessage;
+ else return 'Backup ' + appName('of', data.app) + ' succeeded with backup id ' + data.backupId + ' at ' + data.remotePath;
+
+ case ACTION_APP_CLONE:
+ if (appIdContext === data.oldAppId) return 'App was cloned to ' + data.newApp.fqdn + ' using backup at ' + data.remotePath;
+ else if (appIdContext === data.appId) return 'App was cloned from ' + data.oldApp.fqdn + ' using backup at ' + data.remotePath;
+ else return appName('', data.newApp, 'App') + ' was cloned ' + appName('from', data.oldApp) + ' using backup at ' + data.remotePath;
+
+ case ACTION_APP_REPAIR:
+ return appName('', data.app, 'App') + ' was re-configured'; // re-configure of email apps is more common?
+
+ case ACTION_APP_LOGIN: {
+ // const app = getApp(data.appId);
+ if (!app) return '';
+ return 'App ' + app.fqdn + ' logged in';
+ }
+
+ case ACTION_APP_OOM:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' ran out of memory';
+
+ case ACTION_APP_DOWN:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' is down';
+
+ case ACTION_APP_UP:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' is back online';
+
+ case ACTION_APP_START:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' was started';
+
+ case ACTION_APP_STOP:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' was stopped';
+
+ case ACTION_APP_RESTART:
+ if (!data.app) return '';
+ return appName('', data.app, 'App') + ' was restarted';
+
+ case ACTION_ARCHIVES_ADD:
+ return 'Backup ' + data.backupId + ' added to archive';
+
+ case ACTION_ARCHIVES_DEL:
+ return 'Backup ' + data.backupId + ' deleted from archive';
+
+ case ACTION_BACKUP_START:
+ return 'Backup started';
+
+ case ACTION_BACKUP_FINISH:
+ if (!errorMessage) return 'Cloudron backup created at ' + data.remotePath;
+ else return 'Cloudron backup errored with error: ' + errorMessage;
+
+ case ACTION_BACKUP_CLEANUP_START:
+ return 'Backup cleaner started';
+
+ 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');
+
+ case ACTION_CERTIFICATE_RENEWAL:
+ return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : ' succeeded');
+
+ case ACTION_CERTIFICATE_CLEANUP:
+ return 'Certificate(s) of ' + data.domains.join(',') + ' was cleaned up since they expired 6 months ago';
+
+ case ACTION_DASHBOARD_DOMAIN_UPDATE:
+ return 'Dashboard domain set to ' + data.fqdn || (data.subdomain + '.' + data.domain);
+
+ case ACTION_DIRECTORY_SERVER_CONFIGURE:
+ if (data.fromEnabled !== data.toEnabled) return 'Directory server was ' + (data.toEnabled ? 'enabled' : 'disabled');
+ else return 'Directory server configuration was changed';
+
+ case ACTION_DOMAIN_ADD:
+ return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was added';
+
+ case ACTION_DOMAIN_UPDATE:
+ return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was updated';
+
+ case ACTION_DOMAIN_REMOVE:
+ return 'Domain ' + data.domain + ' was removed';
+
+ case ACTION_EXTERNAL_LDAP_CONFIGURE:
+ if (data.config.provider === 'noop') return 'External Directory disabled';
+ else return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')';
+
+ case ACTION_GROUP_ADD:
+ return 'Group ' + data.name + ' was added';
+
+ case ACTION_GROUP_UPDATE:
+ return 'Group name changed from ' + data.oldName + ' to ' + data.group.name;
+
+ case ACTION_GROUP_REMOVE:
+ return 'Group ' + data.group.name + ' was removed';
+
+ case ACTION_GROUP_MEMBERSHIP:
+ return 'Group membership of ' + data.group.name + ' changed. Now was ' + data.userIds.length + ' member(s).';
+
+ case ACTION_INSTALL_FINISH:
+ return 'Cloudron version ' + data.version + ' installed';
+
+ case ACTION_MAIL_LOCATION:
+ return 'Mail server location was changed to ' + data.subdomain + (data.subdomain ? '.' : '') + data.domain;
+
+ case ACTION_MAIL_ENABLED:
+ return 'Mail was enabled for domain ' + data.domain;
+
+ case ACTION_MAIL_DISABLED:
+ return 'Mail was disabled for domain ' + data.domain;
+
+ case ACTION_MAIL_MAILBOX_ADD:
+ return 'Mailbox ' + data.name + '@' + data.domain + ' was added';
+
+ case ACTION_MAIL_MAILBOX_UPDATE:
+ if (data.aliases) return 'Mailbox aliases of ' + data.name + '@' + data.domain + ' was updated';
+ else return 'Mailbox ' + data.name + '@' + data.domain + ' was updated';
+
+ case ACTION_MAIL_MAILBOX_REMOVE:
+ return 'Mailbox ' + data.name + '@' + data.domain + ' was removed';
+
+ case ACTION_MAIL_LIST_ADD:
+ return 'Mail list ' + data.name + '@' + data.domain + 'was added';
+
+ case ACTION_MAIL_LIST_UPDATE:
+ return 'Mail list ' + data.name + '@' + data.domain + ' was updated';
+
+ case ACTION_MAIL_LIST_REMOVE:
+ return 'Mail list ' + data.name + '@' + data.domain + ' was removed';
+
+ case ACTION_START:
+ return 'Cloudron started with version ' + data.version;
+
+ case ACTION_SERVICE_CONFIGURE:
+ return 'Service ' + data.id + ' was configured';
+
+ case ACTION_SERVICE_REBUILD:
+ return 'Service ' + data.id + ' was rebuilt';
+
+ case ACTION_SERVICE_RESTART:
+ return 'Service ' + data.id + ' was restarted';
+
+ case ACTION_UPDATE:
+ return 'Cloudron update to version ' + data.boxUpdateInfo.version + ' was started';
+
+ case ACTION_UPDATE_FINISH:
+ if (data.errorMessage) return 'Cloudron update errored. Error: ' + data.errorMessage;
+ else return 'Cloudron updated to version ' + data.newVersion;
+
+ case ACTION_USER_ADD:
+ return 'User ' + data.email + (data.user.username ? ' (' + data.user.username + ')' : '') + ' was added';
+
+ case ACTION_USER_UPDATE:
+ return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was updated';
+
+ case ACTION_USER_REMOVE:
+ return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was removed';
+
+ case ACTION_USER_TRANSFER:
+ return 'Apps of ' + data.oldOwnerId + ' was transferred to ' + data.newOwnerId;
+
+ case ACTION_USER_LOGIN:
+ if (data.mailboxId) {
+ return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to mailbox ' + data.mailboxId;
+ } else if (data.appId) {
+ // const app = getApp(data.appId);
+ return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to ' + (app ? app.fqdn : data.appId);
+ } else { // can happen with directoryserver
+ return 'User ' + (data.user ? data.user.username : data.userId) + ' authenticated';
+ }
+
+ case ACTION_USER_LOGIN_GHOST:
+ return 'User ' + (data.user ? data.user.username : data.userId) + ' was impersonated';
+
+ case ACTION_USER_LOGOUT:
+ return 'User ' + (data.user ? data.user.username : data.userId) + ' logged out';
+
+ case ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE:
+ return 'User directory profile config updated. Mandatory 2FA: ' + (data.config.mandatory2FA) + ' Lock profiles: ' + (data.config.lockUserProfiles);
+
+ case ACTION_DYNDNS_UPDATE: {
+ details = data.errorMessage ? 'Error updating DNS. ' : 'Updated DNS. ';
+ if (data.fromIpv4 !== data.toIpv4) details += 'From IPv4 ' + data.fromIpv4 + ' to ' + data.toIpv4 + '. ';
+ if (data.fromIpv6 !== data.toIpv6) details += 'From IPv6 ' + data.fromIpv6 + ' to ' + data.toIpv6 + '.';
+ if (data.errorMessage) details += ' ' + data.errorMessage;
+ return details;
+ }
+
+ case ACTION_SUPPORT_SSH:
+ return 'Remote Support was ' + (data.enable ? 'enabled' : 'disabled');
+
+ case ACTION_SUPPORT_TICKET:
+ return 'Support ticket was created';
+
+ case ACTION_VOLUME_ADD:
+ return 'Volume "' + data.volume.name + '" was added';
+
+ case ACTION_VOLUME_UPDATE:
+ return 'Volme "' + data.volume.name + '" was updated';
+
+ case ACTION_VOLUME_REMOVE:
+ return 'Volume "' + data.volume.name + '" was removed';
+
+ default:
+ return eventLog.action;
+ }
+}
+
+function eventlogSource(eventlog, app = null) {
+ const source = eventlog.source;
+
+ let line = source.username || source.userId || source.mailboxId || source.authType || 'system';
+ if (source.appId) {
+ // const app = null; //getApp(source.appId);
+ line += ' - ' + (app ? app.fqdn : source.appId);
+ }
+
+ return line;
+}
+
// named exports
export {
download,
mountlike,
s3like,
+ eventlogDetails,
+ eventlogSource,
};
// default export
@@ -38,4 +496,6 @@ export default {
download,
mountlike,
s3like,
+ eventlogDetails,
+ eventlogSource,
};
diff --git a/dashboard/src/views/AppConfigureView.vue b/dashboard/src/views/AppConfigureView.vue
index 3f0dae942..f80680b27 100644
--- a/dashboard/src/views/AppConfigureView.vue
+++ b/dashboard/src/views/AppConfigureView.vue
@@ -9,6 +9,7 @@ const t = i18n.t;
import { ref, onMounted, onUnmounted, useTemplateRef, computed } from 'vue';
import { Button, ButtonGroup, TabView } from 'pankow';
import Info from '../components/app/Info.vue';
+import Eventlog from '../components/app/Eventlog.vue';
import Uninstall from '../components/app/Uninstall.vue';
import AppsModel from '../models/AppsModel.js';
import { APP_TYPES } from '../constants.js';
@@ -167,7 +168,7 @@ onUnmounted(() => {
- Display
+
@@ -181,7 +182,7 @@ onUnmounted(() => {
-
+
diff --git a/dashboard/src/views/EventlogView.vue b/dashboard/src/views/EventlogView.vue
index 03580ca51..ac99570cf 100644
--- a/dashboard/src/views/EventlogView.vue
+++ b/dashboard/src/views/EventlogView.vue
@@ -6,9 +6,10 @@ const t = i18n.t;
import { ref, reactive, onMounted, watch } from 'vue';
import { Button, ButtonGroup, Dropdown, Spinner, TextInput, MultiSelect } from 'pankow';
-import { useDebouncedRef, prettyDate, prettyLongDate, prettyBinarySize } from 'pankow/utils';
+import { useDebouncedRef, prettyDate, prettyLongDate } from 'pankow/utils';
import AppsModel from '../models/AppsModel.js';
import EventlogsModel from '../models/EventlogsModel.js';
+import { eventlogDetails, eventlogSource } from '../utils.js';
const appsModel = AppsModel.create();
const eventlogsModel = EventlogsModel.create();
@@ -17,459 +18,6 @@ function getApp(id) {
return apps.value.find(a => a.id === id);
}
-function eventlogDetails(eventLog, appIdContext) {
- const ACTION_ACTIVATE = 'cloudron.activate';
- const ACTION_PROVISION = 'cloudron.provision';
- const ACTION_RESTORE = 'cloudron.restore';
-
- const ACTION_APP_CLONE = 'app.clone';
- const ACTION_APP_REPAIR = 'app.repair';
- const ACTION_APP_CONFIGURE = 'app.configure';
- const ACTION_APP_INSTALL = 'app.install';
- const ACTION_APP_RESTORE = 'app.restore';
- const ACTION_APP_IMPORT = 'app.import';
- const ACTION_APP_UNINSTALL = 'app.uninstall';
- const ACTION_APP_UPDATE = 'app.update';
- const ACTION_APP_UPDATE_FINISH = 'app.update.finish';
- const ACTION_APP_BACKUP = 'app.backup';
- const ACTION_APP_BACKUP_FINISH = 'app.backup.finish';
- const ACTION_APP_LOGIN = 'app.login';
- const ACTION_APP_OOM = 'app.oom';
- const ACTION_APP_UP = 'app.up';
- const ACTION_APP_DOWN = 'app.down';
- const ACTION_APP_START = 'app.start';
- const ACTION_APP_STOP = 'app.stop';
- const ACTION_APP_RESTART = 'app.restart';
-
- const ACTION_ARCHIVES_ADD = 'archives.add';
- const ACTION_ARCHIVES_DEL = 'archives.del';
-
- const ACTION_BACKUP_FINISH = 'backup.finish';
- const ACTION_BACKUP_START = 'backup.start';
- const ACTION_BACKUP_CLEANUP_START = 'backup.cleanup.start';
- const ACTION_BACKUP_CLEANUP_FINISH = 'backup.cleanup.finish';
-
- const ACTION_BRANDING_AVATAR = 'branding.avatar';
- const ACTION_BRANDING_NAME = 'branding.name';
- const ACTION_BRANDING_FOOTER = 'branding.footer';
-
- const ACTION_CERTIFICATE_NEW = 'certificate.new';
- const ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
- const ACTION_CERTIFICATE_CLEANUP = 'certificate.cleanup';
-
- const ACTION_DASHBOARD_DOMAIN_UPDATE = 'dashboard.domain.update';
-
- const ACTION_DIRECTORY_SERVER_CONFIGURE = 'directoryserver.configure';
-
- const ACTION_DOMAIN_ADD = 'domain.add';
- const ACTION_DOMAIN_UPDATE = 'domain.update';
- const ACTION_DOMAIN_REMOVE = 'domain.remove';
-
- const ACTION_EXTERNAL_LDAP_CONFIGURE = 'externalldap.configure';
-
- const ACTION_GROUP_ADD = 'group.add';
- const ACTION_GROUP_UPDATE = 'group.update';
- const ACTION_GROUP_REMOVE = 'group.remove';
- const ACTION_GROUP_MEMBERSHIP = 'group.membership';
-
- const ACTION_INSTALL_FINISH = 'cloudron.install.finish';
-
- const ACTION_START = 'cloudron.start';
- const ACTION_SERVICE_CONFIGURE = 'service.configure';
- const ACTION_SERVICE_REBUILD = 'service.rebuild';
- const ACTION_SERVICE_RESTART = 'service.restart';
- const ACTION_UPDATE = 'cloudron.update';
- const ACTION_UPDATE_FINISH = 'cloudron.update.finish';
- const ACTION_USER_ADD = 'user.add';
- const ACTION_USER_LOGIN = 'user.login';
- const ACTION_USER_LOGIN_GHOST = 'user.login.ghost';
- const ACTION_USER_LOGOUT = 'user.logout';
- const ACTION_USER_REMOVE = 'user.remove';
- const ACTION_USER_UPDATE = 'user.update';
- const ACTION_USER_TRANSFER = 'user.transfer';
-
- const ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE = 'userdirectory.profileconfig.update';
-
- const ACTION_MAIL_LOCATION = 'mail.location';
- const ACTION_MAIL_ENABLED = 'mail.enabled';
- const ACTION_MAIL_DISABLED = 'mail.disabled';
- const ACTION_MAIL_MAILBOX_ADD = 'mail.box.add';
- const ACTION_MAIL_MAILBOX_UPDATE = 'mail.box.update';
- const ACTION_MAIL_MAILBOX_REMOVE = 'mail.box.remove';
- const ACTION_MAIL_LIST_ADD = 'mail.list.add';
- const ACTION_MAIL_LIST_UPDATE = 'mail.list.update';
- const ACTION_MAIL_LIST_REMOVE = 'mail.list.remove';
-
- const ACTION_SUPPORT_TICKET = 'support.ticket';
- const ACTION_SUPPORT_SSH = 'support.ssh';
-
- const ACTION_VOLUME_ADD = 'volume.add';
- const ACTION_VOLUME_UPDATE = 'volume.update';
- const ACTION_VOLUME_REMOVE = 'volume.remove';
-
- const ACTION_DYNDNS_UPDATE = 'dyndns.update';
-
- const data = eventLog.data;
- const errorMessage = data.errorMessage;
- let details, app;
-
- function appName(pre, app, defaultValue) {
- if (appIdContext) return defaultValue || '';
-
- pre = pre ? (pre + ' ') : '';
-
- return pre + (app.label || app.fqdn || app.subdomain) + ' (' + app.manifest.title + ') ';
- }
-
- function eventBy() {
- if (eventLog.source && eventLog.source.username) return ' by ' + eventLog.source.username;
- return '';
- }
-
- switch (eventLog.action) {
- case ACTION_ACTIVATE:
- return 'Cloudron was activated';
-
- case ACTION_PROVISION:
- return 'Cloudron was setup';
-
- case ACTION_RESTORE:
- return 'Cloudron was restored using backup at ' + data.remotePath;
-
- case ACTION_APP_CONFIGURE: {
- if (!data.app) return '';
- app = data.app;
-
- if ('accessRestriction' in data) { // since it can be null
- return 'Access restriction ' + appName('of', app) + ' was changed';
- } else if ('operators' in data) {
- return 'Operators ' + appName('of', app) + ' was changed';
- } else if (data.label) {
- return `Label ${appName('of', app)} was set to ${data.label}`;
- } else if (data.tags) {
- return `Tags ${appName('of', app)} was set to ${data.tags.join(', ')}`;
- } else if (data.icon) {
- return 'Icon ' + appName('of', app) + ' was changed';
- } else if (data.memoryLimit) {
- return 'Memory limit ' + appName('of', app) + ' was set to ' + prettyBinarySize(data.memoryLimit);
- } else if (data.cpuShares) { // replaced by cpuQuota in 8.0
- return 'CPU shares ' + appName('of', app) + ' was set to ' + Math.round((data.cpuShares * 100)/1024) + '%';
- } else if (data.cpuQuota) {
- return 'CPU quota ' + appName('of', app) + ' was set to ' + data.cpuQuota + '%';
- } else if (data.env) {
- return 'Env vars ' + appName('of', app) + ' was changed';
- } else if ('debugMode' in data) { // since it can be null
- if (data.debugMode) return appName('', app, 'App') + ' was placed in repair mode';
- else return appName('', app, 'App') + ' was taken out of repair mode';
- } else if ('enableBackup' in data) {
- return 'Automatic backups ' + appName('of', app) + ' was ' + (data.enableBackup ? 'enabled' : 'disabled');
- } else if ('enableAutomaticUpdate' in data) {
- return 'Automatic updates ' + appName('of', app) + ' was ' + (data.enableAutomaticUpdate ? 'enabled' : 'disabled');
- } else if ('reverseProxyConfig' in data) {
- return 'Reverse proxy configuration ' + appName('of', app) + ' was updated';
- } else if ('upstreamUri' in data) {
- return 'Upstream URI ' + appName('of', app) + ' was updated';
- } else if ('cert' in data) {
- if (data.cert) return 'Custom certificate was set ' + appName('for', app);
- else return 'Certificate ' + appName('of', app) + ' was reset';
- } else if (data.subdomain) {
- if (data.fqdn !== data.app.fqdn) {
- return 'Location ' + appName('of', app) + ' was changed to ' + data.fqdn;
- // TODO below we used to use angular.equals(data.redirectDomains, data.app.redirectDomains)
- } else if (data.redirectDomains !== data.app.redirectDomains) {
- const altFqdns = data.redirectDomains.map(a => a.fqdn);
- return 'Alternate domains ' + appName('of', app) + ' was ' + (altFqdns.length ? 'set to ' + altFqdns.join(', ') : 'reset');
- // TODO below we used to use angular.equals(data.aliasDomains, data.app.aliasDomains)
- } else if (data.aliasDomains !== data.app.aliasDomains) {
- const aliasDomains = data.aliasDomains.map(a => a.fqdn);
- return 'Alias domains ' + appName('of', app) + ' was ' + (aliasDomains.length ? 'set to ' + aliasDomains.join(', ') : 'reset');
- // TODO below we used to use angular.equals(data.portBindings, data.app.portBindings)
- } else if (data.portBindings !== data.app.portBindings) {
- return 'Port bindings ' + appName('of', app) + ' was changed';
- }
- } else if ('dataDir' in data) {
- if (data.dataDir) return 'Data directory ' + appName('of', app) + ' was set ' + data.dataDir;
- else return 'Data directory ' + appName('of', app) + ' was reset';
- } else if ('icon' in data) {
- if (data.icon) return 'Icon ' + appName('of', app) + ' was set';
- else return 'Icon ' + appName('of', app) + ' was reset';
- } else if ('mailboxName' in data) {
- if (data.mailboxName) return `Mailbox ${appName('of', app)} was set to ${data.mailboxDisplayName || '' } ${data.mailboxName}@${data.mailboxDomain}`;
- else return 'Mailbox ' + appName('of', app) + ' was disabled';
- }
-
- return appName('', app, 'App ') + 'was re-configured';
- }
-
- case ACTION_APP_INSTALL:
- if (!data.app) return '';
- return data.app.manifest.title + ' (package v' + data.app.manifest.version + ') was installed ' + appName('at', data.app) + eventBy();
-
- case ACTION_APP_RESTORE:
- if (!data.app) return '';
- details = appName('', data.app, 'App') + ' was restored';
- // older versions (<3.5) did not have these fields
- if (data.fromManifest) details += ' from version ' + data.fromManifest.version;
- if (data.toManifest) details += ' to version ' + data.toManifest.version;
- if (data.remotePath) details += ' using backup at ' + data.remotePath;
- return details;
-
- case ACTION_APP_IMPORT:
- if (!data.app) return '';
- details = appName('', data.app, 'App') + ' was imported';
- if (data.toManifest) details += ' to version ' + data.toManifest.version;
- if (data.remotePath) details += ' using backup at ' + data.remotePath;
- return details;
-
- case ACTION_APP_UNINSTALL:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' (package v' + data.app.manifest.version + ') was uninstalled';
-
- case ACTION_APP_UPDATE:
- if (!data.app) return '';
- return 'Update ' + appName('of', data.app) + ' started from v' + data.fromManifest.version + ' to v' + data.toManifest.version;
-
- case ACTION_APP_UPDATE_FINISH:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' was updated to v' + data.app.manifest.version;
-
- case ACTION_APP_BACKUP:
- if (!data.app) return '';
- return 'Backup ' + appName('of', data.app) + ' started';
-
- case ACTION_APP_BACKUP_FINISH:
- if (!data.app) return '';
- if (data.errorMessage) return 'Backup ' + appName('of', data.app) + ' failed: ' + data.errorMessage;
- else return 'Backup ' + appName('of', data.app) + ' succeeded with backup id ' + data.backupId + ' at ' + data.remotePath;
-
- case ACTION_APP_CLONE:
- if (appIdContext === data.oldAppId) return 'App was cloned to ' + data.newApp.fqdn + ' using backup at ' + data.remotePath;
- else if (appIdContext === data.appId) return 'App was cloned from ' + data.oldApp.fqdn + ' using backup at ' + data.remotePath;
- else return appName('', data.newApp, 'App') + ' was cloned ' + appName('from', data.oldApp) + ' using backup at ' + data.remotePath;
-
- case ACTION_APP_REPAIR:
- return appName('', data.app, 'App') + ' was re-configured'; // re-configure of email apps is more common?
-
- case ACTION_APP_LOGIN: {
- const app = getApp(data.appId);
- if (!app) return '';
- return 'App ' + app.fqdn + ' logged in';
- }
-
- case ACTION_APP_OOM:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' ran out of memory';
-
- case ACTION_APP_DOWN:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' is down';
-
- case ACTION_APP_UP:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' is back online';
-
- case ACTION_APP_START:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' was started';
-
- case ACTION_APP_STOP:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' was stopped';
-
- case ACTION_APP_RESTART:
- if (!data.app) return '';
- return appName('', data.app, 'App') + ' was restarted';
-
- case ACTION_ARCHIVES_ADD:
- return 'Backup ' + data.backupId + ' added to archive';
-
- case ACTION_ARCHIVES_DEL:
- return 'Backup ' + data.backupId + ' deleted from archive';
-
- case ACTION_BACKUP_START:
- return 'Backup started';
-
- case ACTION_BACKUP_FINISH:
- if (!errorMessage) return 'Cloudron backup created at ' + data.remotePath;
- else return 'Cloudron backup errored with error: ' + errorMessage;
-
- case ACTION_BACKUP_CLEANUP_START:
- return 'Backup cleaner started';
-
- 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');
-
- case ACTION_CERTIFICATE_RENEWAL:
- return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : ' succeeded');
-
- case ACTION_CERTIFICATE_CLEANUP:
- return 'Certificate(s) of ' + data.domains.join(',') + ' was cleaned up since they expired 6 months ago';
-
- case ACTION_DASHBOARD_DOMAIN_UPDATE:
- return 'Dashboard domain set to ' + data.fqdn || (data.subdomain + '.' + data.domain);
-
- case ACTION_DIRECTORY_SERVER_CONFIGURE:
- if (data.fromEnabled !== data.toEnabled) return 'Directory server was ' + (data.toEnabled ? 'enabled' : 'disabled');
- else return 'Directory server configuration was changed';
-
- case ACTION_DOMAIN_ADD:
- return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was added';
-
- case ACTION_DOMAIN_UPDATE:
- return 'Domain ' + data.domain + ' with ' + data.provider + ' provider was updated';
-
- case ACTION_DOMAIN_REMOVE:
- return 'Domain ' + data.domain + ' was removed';
-
- case ACTION_EXTERNAL_LDAP_CONFIGURE:
- if (data.config.provider === 'noop') return 'External Directory disabled';
- else return 'External Directory set to ' + data.config.url + ' (' + data.config.provider + ')';
-
- case ACTION_GROUP_ADD:
- return 'Group ' + data.name + ' was added';
-
- case ACTION_GROUP_UPDATE:
- return 'Group name changed from ' + data.oldName + ' to ' + data.group.name;
-
- case ACTION_GROUP_REMOVE:
- return 'Group ' + data.group.name + ' was removed';
-
- case ACTION_GROUP_MEMBERSHIP:
- return 'Group membership of ' + data.group.name + ' changed. Now was ' + data.userIds.length + ' member(s).';
-
- case ACTION_INSTALL_FINISH:
- return 'Cloudron version ' + data.version + ' installed';
-
- case ACTION_MAIL_LOCATION:
- return 'Mail server location was changed to ' + data.subdomain + (data.subdomain ? '.' : '') + data.domain;
-
- case ACTION_MAIL_ENABLED:
- return 'Mail was enabled for domain ' + data.domain;
-
- case ACTION_MAIL_DISABLED:
- return 'Mail was disabled for domain ' + data.domain;
-
- case ACTION_MAIL_MAILBOX_ADD:
- return 'Mailbox ' + data.name + '@' + data.domain + ' was added';
-
- case ACTION_MAIL_MAILBOX_UPDATE:
- if (data.aliases) return 'Mailbox aliases of ' + data.name + '@' + data.domain + ' was updated';
- else return 'Mailbox ' + data.name + '@' + data.domain + ' was updated';
-
- case ACTION_MAIL_MAILBOX_REMOVE:
- return 'Mailbox ' + data.name + '@' + data.domain + ' was removed';
-
- case ACTION_MAIL_LIST_ADD:
- return 'Mail list ' + data.name + '@' + data.domain + 'was added';
-
- case ACTION_MAIL_LIST_UPDATE:
- return 'Mail list ' + data.name + '@' + data.domain + ' was updated';
-
- case ACTION_MAIL_LIST_REMOVE:
- return 'Mail list ' + data.name + '@' + data.domain + ' was removed';
-
- case ACTION_START:
- return 'Cloudron started with version ' + data.version;
-
- case ACTION_SERVICE_CONFIGURE:
- return 'Service ' + data.id + ' was configured';
-
- case ACTION_SERVICE_REBUILD:
- return 'Service ' + data.id + ' was rebuilt';
-
- case ACTION_SERVICE_RESTART:
- return 'Service ' + data.id + ' was restarted';
-
- case ACTION_UPDATE:
- return 'Cloudron update to version ' + data.boxUpdateInfo.version + ' was started';
-
- case ACTION_UPDATE_FINISH:
- if (data.errorMessage) return 'Cloudron update errored. Error: ' + data.errorMessage;
- else return 'Cloudron updated to version ' + data.newVersion;
-
- case ACTION_USER_ADD:
- return 'User ' + data.email + (data.user.username ? ' (' + data.user.username + ')' : '') + ' was added';
-
- case ACTION_USER_UPDATE:
- return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was updated';
-
- case ACTION_USER_REMOVE:
- return 'User ' + (data.user ? (data.user.email + (data.user.username ? ' (' + data.user.username + ')' : '')) : data.userId) + ' was removed';
-
- case ACTION_USER_TRANSFER:
- return 'Apps of ' + data.oldOwnerId + ' was transferred to ' + data.newOwnerId;
-
- case ACTION_USER_LOGIN:
- if (data.mailboxId) {
- return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to mailbox ' + data.mailboxId;
- } else if (data.appId) {
- const app = getApp(data.appId);
- return 'User ' + (data.user ? data.user.username : data.userId) + ' logged in to ' + (app ? app.fqdn : data.appId);
- } else { // can happen with directoryserver
- return 'User ' + (data.user ? data.user.username : data.userId) + ' authenticated';
- }
-
- case ACTION_USER_LOGIN_GHOST:
- return 'User ' + (data.user ? data.user.username : data.userId) + ' was impersonated';
-
- case ACTION_USER_LOGOUT:
- return 'User ' + (data.user ? data.user.username : data.userId) + ' logged out';
-
- case ACTION_USER_DIRECTORY_PROFILE_CONFIG_UPDATE:
- return 'User directory profile config updated. Mandatory 2FA: ' + (data.config.mandatory2FA) + ' Lock profiles: ' + (data.config.lockUserProfiles);
-
- case ACTION_DYNDNS_UPDATE: {
- details = data.errorMessage ? 'Error updating DNS. ' : 'Updated DNS. ';
- if (data.fromIpv4 !== data.toIpv4) details += 'From IPv4 ' + data.fromIpv4 + ' to ' + data.toIpv4 + '. ';
- if (data.fromIpv6 !== data.toIpv6) details += 'From IPv6 ' + data.fromIpv6 + ' to ' + data.toIpv6 + '.';
- if (data.errorMessage) details += ' ' + data.errorMessage;
- return details;
- }
-
- case ACTION_SUPPORT_SSH:
- return 'Remote Support was ' + (data.enable ? 'enabled' : 'disabled');
-
- case ACTION_SUPPORT_TICKET:
- return 'Support ticket was created';
-
- case ACTION_VOLUME_ADD:
- return 'Volume "' + data.volume.name + '" was added';
-
- case ACTION_VOLUME_UPDATE:
- return 'Volme "' + data.volume.name + '" was updated';
-
- case ACTION_VOLUME_REMOVE:
- return 'Volume "' + data.volume.name + '" was removed';
-
- default:
- return eventLog.action;
- }
-}
-
-function eventlogSource(eventlog) {
- const source = eventlog.source;
-
- let line = source.username || source.userId || source.mailboxId || source.authType || 'system';
- if (source.appId) {
- const app = null; //getApp(source.appId);
- line += ' - ' + (app ? app.fqdn : source.appId);
- }
-
- return line;
-}
-
const availableActions = [
{ id: 'app.backup' },
{ id: 'app.backup.finish' },
@@ -563,8 +111,8 @@ async function onRefresh() {
return {
id: Symbol(),
raw: e,
- details: eventlogDetails(e),
- source: eventlogSource(e)
+ details: eventlogDetails(e, e.appId ? getApp(e.appId) : null),
+ source: eventlogSource(e, e.appId ? getApp(e.appId) : null)
};
});