Compare commits

..

56 Commits

Author SHA1 Message Date
Johannes Zellner bce3d3f664 Use fqdn instead of location for naked domain apps 2018-03-09 10:17:01 +01:00
Girish Ramakrishnan 828d6f6cc8 Show the provider and format for caas 2018-03-09 00:40:55 -08:00
Girish Ramakrishnan 0a026cc143 Display caas as Managed Cloudron 2018-03-09 00:37:20 -08:00
Girish Ramakrishnan 3bc9a87933 Fix display of caas domain 2018-03-09 00:29:00 -08:00
Girish Ramakrishnan 769f9adc9d Update mail domain when domain is updated 2018-03-08 18:06:50 -08:00
Johannes Zellner b5f53d921e Replace app-request link to point to the new forum 2018-03-08 21:46:16 +01:00
Girish Ramakrishnan 105e9e7825 Use the new app update pattern 2018-03-06 21:30:42 -08:00
Girish Ramakrishnan c8cf050156 Keep it alphabetical 2018-03-05 10:28:22 -08:00
Girish Ramakrishnan b7baafbbe6 actions -> events
also make it all past tense
2018-03-05 10:17:44 -08:00
Girish Ramakrishnan 85dde71ec3 fix display of undefined id
remove id display altogether, it's not very interesting to see it
2018-03-05 10:09:06 -08:00
Girish Ramakrishnan 2970b086a3 Updates -> App Updates 2018-03-05 09:39:03 -08:00
Johannes Zellner 5910709008 Use the correct model attribute for appId in feedback form 2018-03-05 17:10:00 +01:00
Johannes Zellner 2b6ce4f813 Reduce feedback form options and add ability to specify failing app 2018-03-05 12:54:09 +01:00
Johannes Zellner 451c697fb7 Show email as fallback when a user has no username yet 2018-03-05 12:14:20 +01:00
Johannes Zellner 09149318b1 Better format the multiselect element 2018-03-05 12:10:17 +01:00
Johannes Zellner d2d8eb9485 Allow to select multiple actions in the eventlog filter 2018-03-05 12:03:02 +01:00
Johannes Zellner 91265613a9 Prettify eventlog source display 2018-03-02 19:21:24 +01:00
Johannes Zellner 31c414bbe1 Use more readable datetime tooltip format in activity log 2018-03-02 18:58:49 +01:00
Johannes Zellner e2a3654ed7 Give the time more space in the activity log 2018-03-02 18:50:49 +01:00
Johannes Zellner 96d7283534 Do not alternate the background color of the activity log 2018-03-02 18:50:32 +01:00
Girish Ramakrishnan 256a7e322b Keep it all to two words 2018-03-02 09:12:53 -08:00
Johannes Zellner e5b78337ac Show more readable user event data 2018-03-02 13:42:24 +01:00
Johannes Zellner 67ba5aa1c5 fix indentation 2018-03-02 13:19:57 +01:00
Johannes Zellner 848a617f98 Make eventlog entries expandable to show raw event data 2018-03-02 10:50:05 +01:00
Johannes Zellner 1fc7efef0d Improve app related eventlog display 2018-03-02 10:49:46 +01:00
Girish Ramakrishnan 576f6eafbb Rename Chat to Forum 2018-03-01 13:40:10 -08:00
Girish Ramakrishnan 2caf73b5e3 Do not list mail domains and aliases if username is not available 2018-02-28 15:21:42 -08:00
Girish Ramakrishnan 56abb68e0c Link admin link to docs 2018-02-28 13:40:06 -08:00
Girish Ramakrishnan 7aaac5a48a reword email address on domains 2018-02-28 13:26:15 -08:00
Girish Ramakrishnan 8326587886 Give indication that the test is for the relay 2018-02-27 09:24:36 -08:00
Girish Ramakrishnan 466b3f4784 Make the user edit dialog say "Primary email" 2018-02-24 16:42:15 -08:00
Girish Ramakrishnan bccdf548a8 Fix typo making the MX records hidden 2018-02-23 17:04:38 -08:00
Girish Ramakrishnan fa4b1b3d5b Add note that user and group mailboxes must be enabled 2018-02-23 17:02:18 -08:00
Girish Ramakrishnan 9d47fd198f replace chat with forum 2018-02-23 15:53:23 -08:00
Girish Ramakrishnan 5966ee6800 replace terms link with license 2018-02-23 15:25:27 -08:00
Johannes Zellner 2d20e3c13d Scroll to top on category activation 2018-02-23 11:34:39 -08:00
Johannes Zellner 2172f8532d Rework the appstore category list 2018-02-23 11:34:27 -08:00
Johannes Zellner 9dc4318152 Reduce category item size 2018-02-23 11:34:04 -08:00
Johannes Zellner e1a92e7127 Make primary email labels explicit 2018-02-23 10:29:09 -08:00
Girish Ramakrishnan 767b31caa2 Display the pretty domain provider name in the table
This is especially needed to distinguish wildcard/manual.
2018-02-21 10:14:17 -08:00
Johannes Zellner c2232936e0 Replace chat with forum in the support page 2018-02-20 11:55:57 -08:00
Johannes Zellner 4f1bbfd9e3 Make it clear that support ssh button should be enabled only if we ask the user to do so 2018-02-20 11:27:12 -08:00
Johannes Zellner caf57e37dc Add eventlog groups for apps and users 2018-02-20 11:13:51 -08:00
Johannes Zellner 64b8e4ad6c Shorten app ids in eventlog 2018-02-19 01:56:12 -08:00
Johannes Zellner c9d3907124 Add missing whitespace 2018-02-19 01:56:12 -08:00
Girish Ramakrishnan bf6bea800b Add note that user/group mailboxes must be enabled 2018-02-18 12:04:37 -08:00
Johannes Zellner 26f1673d47 Show full fqdn on app grid item hover 2018-02-17 16:01:36 -08:00
Johannes Zellner 08153454a2 Show tooltips immediately for app actions to guide the user 2018-02-09 10:11:23 +01:00
Girish Ramakrishnan efc26ab587 Specify which domain mail should be enabled for 2018-02-08 19:08:00 -08:00
Girish Ramakrishnan e24e0a7e87 br was removed by mistake in 23bc267c46 2018-02-08 15:25:37 -08:00
Johannes Zellner 23bc267c46 Show full fqdn in apps grid for now instead of the domain on the top 2018-02-08 16:17:47 +01:00
Johannes Zellner 35cc592d61 Remove altDomain ui bits 2018-02-08 09:44:35 +01:00
Girish Ramakrishnan 512f6a1166 Remove obsolete action 2018-02-06 23:14:37 -08:00
Johannes Zellner 3160ffec3f The update schedule is only set for the apps now 2018-02-06 19:39:06 +01:00
Johannes Zellner c543d4517f Adjust to new autoupdate pattern rest apis 2018-02-06 19:25:06 +01:00
Girish Ramakrishnan d7334b991b Add DO SGP1 2018-02-05 11:06:40 -08:00
28 changed files with 267 additions and 164 deletions
+1 -1
View File
@@ -59,6 +59,6 @@ the containers in the Cloudron.
## Community
* [Chat](https://chat.cloudron.io/)
* [Forum](https://forum.cloudron.io/)
* [Support](mailto:support@cloudron.io)
+1 -1
View File
@@ -68,7 +68,7 @@
<footer class="text-center">
<span class="text-muted"><a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter</a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
+1 -1
View File
@@ -92,7 +92,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
+2 -2
View File
@@ -155,7 +155,7 @@
You can update to the next version once you have selected a <a ng-href="{{ config.webServerOrigin + '/pricing.html' }}" target="_blank">paid plan</a>.
</p>
<p>
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://chat.cloudron.io">live chat</a>.
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://forum.cloudron.io">forum</a>.
</p>
</div>
<div class="modal-footer">
@@ -231,7 +231,7 @@
<span class="text-muted">&copy; 2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"> v{{config.version}}</span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</div>
+38 -15
View File
@@ -355,7 +355,6 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
cert: config.cert,
key: config.key,
memoryLimit: config.memoryLimit,
altDomain: config.altDomain || null,
xFrameOptions: config.xFrameOptions,
robotsTxt: config.robotsTxt || null,
enableBackup: config.enableBackup
@@ -453,13 +452,6 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
}).error(defaultErrorHandler(callback));
};
Client.prototype.setAutoupdatePattern = function (pattern, callback) {
post('/api/v1/settings/autoupdate_pattern', { pattern: pattern }).success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null);
}).error(defaultErrorHandler(callback));
};
Client.prototype.checkForUpdates = function (callback) {
var that = this;
@@ -475,8 +467,29 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
}).error(defaultErrorHandler(callback));
};
Client.prototype.getAutoupdatePattern = function (callback) {
get('/api/v1/settings/autoupdate_pattern').success(function(data, status) {
Client.prototype.setAppAutoupdatePattern = function (pattern, callback) {
post('/api/v1/settings/app_autoupdate_pattern', { pattern: pattern }).success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null);
}).error(defaultErrorHandler(callback));
};
Client.prototype.getAppAutoupdatePattern = function (callback) {
get('/api/v1/settings/app_autoupdate_pattern').success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null, data);
}).error(defaultErrorHandler(callback));
};
Client.prototype.setBoxAutoupdatePattern = function (pattern, callback) {
post('/api/v1/settings/box_autoupdate_pattern', { pattern: pattern }).success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null);
}).error(defaultErrorHandler(callback));
};
Client.prototype.getBoxAutoupdatePattern = function (callback) {
get('/api/v1/settings/box_autoupdate_pattern').success(function(data, status) {
if (status !== 200) return callback(new ClientError(status, data));
callback(null, data);
}).error(defaultErrorHandler(callback));
@@ -546,10 +559,10 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
}).error(defaultErrorHandler(callback));
};
Client.prototype.getEventLogs = function (action, search, page, perPage, callback) {
Client.prototype.getEventLogs = function (actions, search, page, perPage, callback) {
var config = {
params: {
action: action,
actions: actions,
search: search,
page: page,
per_page: perPage
@@ -846,11 +859,12 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
}).error(defaultErrorHandler(callback));
};
Client.prototype.feedback = function (type, subject, description, callback) {
Client.prototype.feedback = function (type, subject, description, appId /* optional */, callback) {
var data = {
type: type,
subject: subject,
description: description
description: description,
appId: appId || undefined
};
post('/api/v1/feedback', data).success(function (data, status) {
@@ -1130,6 +1144,13 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
}).error(defaultErrorHandler(callback)); // this doesn't call defaultErrorHandler till we fix domains code to use this directly
};
Client.prototype.updateMailDomain = function (domain, callback) {
post('/api/v1/mail/' + domain, { }).success(function (data, status) {
if (status !== 202) return callback(new ClientError(status, data));
callback(null);
}).error(defaultErrorHandler(callback)); // this doesn't call defaultErrorHandler till we fix domains code to use this directly
};
Client.prototype.addDomain = function (domain, provider, config, fallbackCertificate, tlsConfig, callback) {
var data = {
domain: domain,
@@ -1155,12 +1176,14 @@ angular.module('Application').service('Client', ['$http', '$interval', 'md5', 'N
config: config,
tlsConfig: tlsConfig
};
var that = this;
if (fallbackCertificate) data.fallbackCertificate = fallbackCertificate;
put('/api/v1/domains/' + domain, data).success(function (data, status) {
if (status !== 204) return callback(new ClientError(status, data));
callback(null);
that.updateMailDomain(domain, callback); // this is done so that an out-of-sync dkim key can be synced
}).error(defaultErrorHandler(callback));
};
+71 -20
View File
@@ -252,6 +252,12 @@ app.filter('prettyDate', function () {
};
});
app.filter('prettyLongDate', function () {
return function prettyLongDate(time) {
return moment(time).format('MMMM Do YYYY, h:mm:ss a');
};
});
app.filter('markdown2html', function () {
var converter = new showdown.Converter({
extensions: ['targetblank'],
@@ -297,7 +303,6 @@ var ACTION_BACKUP_FINISH = 'backup.finish';
var ACTION_BACKUP_START = 'backup.start';
var ACTION_BACKUP_CLEANUP = 'backup.cleanup';
var ACTION_CERTIFICATE_RENEWAL = 'certificate.renew';
var ACTION_CLI_MODE = 'settings.climode';
var ACTION_START = 'cloudron.start';
var ACTION_UPDATE = 'cloudron.update';
var ACTION_USER_ADD = 'user.add';
@@ -305,6 +310,23 @@ var ACTION_USER_LOGIN = 'user.login';
var ACTION_USER_REMOVE = 'user.remove';
var ACTION_USER_UPDATE = 'user.update';
app.filter('eventLogSource', function() {
return function(eventLog) {
var source = eventLog.source;
var data = eventLog.data;
var errorMessage = data.errorMessage;
// <span ng-show="eventLog.source.ip || eventLog.source.appId"> ({{ eventLog.source.ip || eventLog.source.appId }}) </span>
var line = source.username || source.userId || source.authType;
if (source.app) line += ' - ' + source.app.fqdn;
else if (source.ip) line += ' - ' + source.ip;
else if (source.appId) line += ' - ' + source.appId;
return line;
};
});
app.filter('eventLogDetails', function() {
// NOTE: if you change this, the CLI tool (cloudron machine eventlog) probably needs fixing as well
return function(eventLog) {
@@ -313,25 +335,54 @@ app.filter('eventLogDetails', function() {
var errorMessage = data.errorMessage;
switch (eventLog.action) {
case ACTION_ACTIVATE: return 'Cloudron activated';
case ACTION_APP_CONFIGURE: return 'App ' + data.appId + ' was configured';
case ACTION_APP_INSTALL: return 'App ' + data.manifest.id + '@' + data.manifest.version + ' installed at ' + data.location + ' with id ' + data.appId;
case ACTION_APP_RESTORE: return 'App ' + data.appId + ' restored';
case ACTION_APP_UNINSTALL: return 'App ' + data.appId + ' uninstalled';
case ACTION_APP_UPDATE: return 'App ' + data.appId + ' updated to version ' + data.toManifest.id + '@' + data.toManifest.version;
case ACTION_APP_LOGIN: return 'App ' + data.appId + ' logged in';
case ACTION_BACKUP_START: return 'Backup started';
case ACTION_BACKUP_FINISH: return 'Backup finished. ' + (errorMessage ? ('error: ' + errorMessage) : ('id: ' + data.filename));
case ACTION_BACKUP_CLEANUP: return 'Backup ' + data.backup.id + ' removed';
case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : 'succeeded');
case ACTION_CLI_MODE: return 'CLI mode was ' + (data.enabled ? 'enabled' : 'disabled');
case ACTION_START: return 'Cloudron started with version ' + data.version;
case ACTION_UPDATE: return 'Updating to version ' + data.boxUpdateInfo.version;
case ACTION_USER_ADD: return 'User ' + data.email + ' added with id ' + data.userId;
case ACTION_USER_LOGIN: return 'User ' + data.userId + ' logged in';
case ACTION_USER_REMOVE: return 'User ' + data.userId + ' removed';
case ACTION_USER_UPDATE: return 'User ' + data.userId + ' updated';
default: return eventLog.action;
case ACTION_ACTIVATE: return '';
case ACTION_APP_CONFIGURE:
case ACTION_APP_INSTALL:
case ACTION_APP_RESTORE:
case ACTION_APP_UNINSTALL: return (data.app ? ('<b>' + data.app.manifest.title + '</b> at <b>' + (data.app.fqdn || data.app.location) + '</b>') : '');
case ACTION_APP_UPDATE: return (data.app ? ('<b>' + data.app.manifest.title + '</b> at <b>' + (data.app.fqdn || data.app.location) + '</b>') : '') + ' to version <b>' + data.toManifest.id + '@' + data.toManifest.version + '</b>';
case ACTION_APP_LOGIN: return 'App ' + data.appId + ' logged in';
case ACTION_BACKUP_START: return 'Backup started';
case ACTION_BACKUP_FINISH: return 'Backup finished' + (errorMessage ? (' error: ' + errorMessage) : '');
case ACTION_BACKUP_CLEANUP: return 'Backup ' + data.backup.id + ' removed';
case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal for ' + data.domain + (errorMessage ? ' failed' : ' succeeded');
case ACTION_START: return 'Cloudron started with version ' + data.version;
case ACTION_UPDATE: return 'Updating to version ' + data.boxUpdateInfo.version;
case ACTION_USER_ADD: return data.email + (data.user.username ? ' as <b>' + data.user.username + '</b>' : '');
case ACTION_USER_UPDATE:
case ACTION_USER_REMOVE:
case ACTION_USER_LOGIN: return data.user ? (data.user.email + (data.user.username ? ' as <b>' + data.user.username + '</b>' : '')) : data.userId;
default: return eventLog.action;
}
};
});
app.filter('eventLogAction', function() {
// NOTE: if you change this, the CLI tool (cloudron machine eventlog) probably needs fixing as well
return function(eventLog) {
var source = eventLog.source;
var data = eventLog.data;
var errorMessage = data.errorMessage;
switch (eventLog.action) {
case ACTION_ACTIVATE: return 'Cloudron activated';
case ACTION_APP_CONFIGURE: return 'App configured';
case ACTION_APP_INSTALL: return 'App installed';
case ACTION_APP_RESTORE: return 'App restored';
case ACTION_APP_UNINSTALL: return 'App uninstalled';
case ACTION_APP_UPDATE: return 'App updated';
case ACTION_APP_LOGIN: return 'App login';
case ACTION_BACKUP_START: return 'Backup started';
case ACTION_BACKUP_FINISH: return 'Backup finished';
case ACTION_BACKUP_CLEANUP: return 'Backup removed';
case ACTION_CERTIFICATE_RENEWAL: return 'Certificate renewal';
case ACTION_START: return 'Cloudron started';
case ACTION_UPDATE: return 'Platform updated';
case ACTION_USER_ADD: return 'User added';
case ACTION_USER_LOGIN: return 'User login';
case ACTION_USER_REMOVE: return 'User removed';
case ACTION_USER_UPDATE: return 'User updated';
default: return eventLog.action;
}
};
});
+1 -1
View File
@@ -40,7 +40,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
<script>
+1 -1
View File
@@ -164,7 +164,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
+1 -1
View File
@@ -103,7 +103,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
+1 -1
View File
@@ -153,7 +153,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</body>
+22 -5
View File
@@ -310,12 +310,24 @@ h1, h2, h3 {
left: 32px !important;
}
multiselect.stretch {
button {
min-width: 120px;
multiselect {
&.stretch {
button {
min-width: 120px;
}
}
.dropdown-toggle {
background-color: white;
border-color: #ccc;
&:hover {
background-color: white;
}
}
}
// ----------------------------
// Appstore view
// ----------------------------
@@ -382,13 +394,13 @@ multiselect.stretch {
.appstore-category-link {
display: block;
padding: 10px;
padding: 6px 10px;
margin: 0;
overflow: hidden;
color: black;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 18px;
font-size: 16px;
&:hover,
&:focus,
@@ -1079,6 +1091,11 @@ footer {
}
}
.eventlog-details {
white-space: pre-wrap;
background-color: white;
}
// ----------------------------
// Tag Input
// ----------------------------
+1 -1
View File
@@ -59,7 +59,7 @@
<footer class="text-center">
<span class="text-muted">&copy;2018 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
<span class="text-muted"><a href="https://forum.cloudron.io" target="_blank">Forum <i class="fa fa-comments"></i></a></span>
</footer>
</div>
+2 -2
View File
@@ -48,7 +48,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Change main email address</h4>
<h4 class="modal-title">Change primary email address</h4>
</div>
<div class="modal-body">
<form name="emailChangeForm" role="form" novalidate ng-submit="emailchange.submit()" autocomplete="off">
@@ -151,7 +151,7 @@
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.displayName }} <a href="" ng-click="displayNameChange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Email</td>
<td class="text-muted" style="vertical-align: top;">Primary email</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ user.email }} <a href="" ng-click="emailchange.show()"><i class="fa fa-pencil text-small"></i></a></td>
</tr>
<tr>
+17 -12
View File
@@ -8,12 +8,12 @@
<div>
<div class="col-md-10 col-md-offset-1">
<div class="filter">
<input type="text" class="form-control" ng-model="search" ng-model-options="{ debounce: 1000 }" ng-change="updateFilter()" placeholder="Search"/>
<select class="form-control" ng-model="action" ng-options="a.name for a in actions" ng-change="updateFilter()">
<input type="text" class="form-control" style="min-width: 350px;" ng-model="search" ng-model-options="{ debounce: 1000 }" ng-change="updateFilter()" placeholder="Search"/>
<multiselect ng-model="selectedActions" ms-header="All Events" options="a.name for a in actions" data-multiple="true" ng-change="updateFilter(true)"></multiselect>
<select class="form-control" ng-model="pageItems" ng-options="a.name for a in pageItemCount" ng-change="updateFilter(true)"></select>
<!-- <select class="form-control" ng-model="action" ng-options="a.name for a in actions" ng-change="updateFilter()">
<option value="">-- All actions --</option>
</select>
<select class="form-control" ng-model="pageItems" ng-options="a.name for a in pageItemCount" ng-change="updateFilter(true)">
</select>
</select> -->
</div>
<div class="pagination pull-right">
<button class="btn btn-default btn-outline" ng-click="showPrevPage()" ng-disabled="busy || currentPage <= 1"><i class="fa fa-angle-double-left"></i> prev</button>
@@ -26,19 +26,24 @@
<div class="col-md-10 col-md-offset-1">
<div class="card card-block" style="max-width: 100%">
<center ng-show="busy"><h2><i class="fa fa-circle-o-notch fa-spin"></i></h2></center>
<table ng-hide="busy" class="table table-striped table-condensed table-hover">
<table ng-hide="busy" class="table table-condensed table-hover">
<thead>
<tr>
<th class="col-md-2">Time</th>
<th class="col-md-2">Event</th>
<th class="col-md-3">Source</th>
<th class="col-md-7">Action</th>
<th class="col-md-5">Details</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="eventLog in eventLogs">
<td><span uib-tooltip="{{eventLog.creationTime}}" class="arrow">{{ eventLog.creationTime | prettyDate }}</span></td>
<td>{{ eventLog.source.username || eventLog.source.userId || eventLog.source.authType }} <span ng-show="eventLog.source.ip || eventLog.source.appId"> ({{ eventLog.source.ip || eventLog.source.appId }}) </span> </td>
<td>{{ eventLog | eventLogDetails }}</td>
<tbody ng-repeat="eventLog in eventLogs">
<tr ng-click="showEventLogDetails(eventLog)" class="hand">
<td><span uib-tooltip="{{ eventLog.creationTime | prettyLongDate }}" class="arrow">{{ eventLog.creationTime | prettyDate }}</span></td>
<td>{{ eventLog | eventLogAction }}</td>
<td>{{ eventLog | eventLogSource }}</td>
<td ng-bind-html="eventLog | eventLogDetails"></td>
</tr>
<tr ng-show="activeEventLog === eventLog">
<td colspan="4"><pre class="eventlog-details">{{ eventLog.data | json }}</pre></td>
</tr>
</tbody>
</table>
+13 -3
View File
@@ -4,11 +4,13 @@ angular.module('Application').controller('ActivityController', ['$scope', '$loca
$scope.config = Client.getConfig();
$scope.busy = false;
$scope.eventLogs = [ ];
$scope.eventLogs = [];
$scope.activeEventLog = null;
// TODO sync this with the eventlog filter
$scope.actions = [
{ name: 'cloudron.activate', value: 'cloudron.activate' },
{ name: '-- All app events --', value: 'app.' },
{ name: '-- All user events --', value: 'user.' },
{ name: 'app.configure', value: 'app.configure' },
{ name: 'app.install', value: 'app.install' },
{ name: 'app.restore', value: 'app.restore' },
@@ -20,6 +22,7 @@ angular.module('Application').controller('ActivityController', ['$scope', '$loca
{ name: 'backup.start', value: 'backup.start' },
{ name: 'certificate.renew', value: 'certificate.renew' },
{ name: 'settings.climode', value: 'settings.climode' },
{ name: 'cloudron.activate', value: 'cloudron.activate' },
{ name: 'cloudron.start', value: 'cloudron.start' },
{ name: 'cloudron.update', value: 'cloudron.update' },
{ name: 'user.add', value: 'user.add' },
@@ -37,12 +40,14 @@ angular.module('Application').controller('ActivityController', ['$scope', '$loca
$scope.currentPage = 1;
$scope.pageItems = $scope.pageItemCount[0];
$scope.action = '';
$scope.selectedActions = [];
$scope.search = '';
function fetchEventLogs() {
$scope.busy = true;
var actions = $scope.selectedActions.map(function (a) { return a.value; }).join(', ');
Client.getEventLogs($scope.action ? $scope.action.value : null, $scope.search || null, $scope.currentPage, $scope.pageItems.value, function (error, eventLogs) {
Client.getEventLogs(actions, $scope.search || null, $scope.currentPage, $scope.pageItems.value, function (error, eventLogs) {
$scope.busy = false;
if (error) return console.error(error);
@@ -68,6 +73,11 @@ angular.module('Application').controller('ActivityController', ['$scope', '$loca
fetchEventLogs();
};
$scope.showEventLogDetails = function (eventLog) {
if ($scope.activeEventLog === eventLog) $scope.activeEventLog = null;
else $scope.activeEventLog = eventLog;
};
Client.onReady(function () {
fetchEventLogs();
});
+11 -24
View File
@@ -13,36 +13,23 @@
<div class="form-group" ng-class="{ 'has-error': (appConfigureForm.location.$dirty && appConfigureForm.location.$invalid) || (!appConfigureForm.location.$dirty && appConfigure.error.location) }">
<label class="control-label" for="appConfigureLocationInput">Location {{ appConfigure.error.location }} </label>
<div class="input-group form-inline">
<input type="text" class="form-control" ng-model="appConfigure.location" id="appConfigureLocationInput" name="location" placeholder="{{ appConfigure.usingAltDomain ? 'other.domain.com' : 'Leave empty to use bare domain' }}" autofocus>
<input type="text" class="form-control" ng-model="appConfigure.location" id="appConfigureLocationInput" name="location" placeholder="{{ 'Leave empty to use bare domain' }}" autofocus>
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
{{ appConfigure.usingAltDomain ? 'External Domain' : ((!appConfigure.location ? '' : (appConfigure.domain.provider !== 'caas' ? '.' : '-')) + appConfigure.domain.domain) }}
{{ (!appConfigure.location ? '' : (appConfigure.domain.provider !== 'caas' ? '.' : '-')) + appConfigure.domain.domain }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li ng-repeat="domain in domains">
<a href="" ng-click="useAltDomain(false, domain)">{{ domain.domain }}</a>
</li>
<li>
<a href="" ng-click="useAltDomain(true)"><i class="fa fa-star"></i> External Domain</a>
<a href="" ng-click="appConfigure.domain = domain">{{ domain.domain }}</a>
</li>
</ul>
</div>
</div>
</div>
<p class="text-center" ng-show="appConfigure.usingAltDomain && appConfigure.location && appConfigure.isAltDomainSubdomain()">
Add a CNAME record for <b>{{ appConfigure.location }}</b> to <b>{{ appConfigure.app.cnameTarget || appConfigure.app.fqdn }}</b>
<br>
</p>
<p class="text-center" ng-show="appConfigure.usingAltDomain && appConfigure.location && appConfigure.isAltDomainNaked()">
Add an A record for <b>{{ appConfigure.location }}</b> to this Cloudron's public IP</b>
<br>
</p>
<p class="text-center" ng-show="!appConfigure.usingAltDomain && appConfigure.location && dnsConfig.provider === 'manual' && !dnsConfig.wildcard">
<p class="text-center" ng-show="appConfigure.location && dnsConfig.provider === 'manual' && !dnsConfig.wildcard">
<b>Add an A record manually for {{ appConfigure.location }} to this Cloudron's public IP</b>
<br>
</p>
@@ -96,7 +83,7 @@
<div style="margin-left: 20px;">
<div class="col-md-5">
Users:
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.users" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="user.username for user in users" data-multiple="true"></multiselect>
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.users" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="user.display for user in users" data-multiple="true"></multiselect>
</div>
<div class="col-md-5">
@@ -111,7 +98,7 @@
</div>
<p ng-show="appConfigure.app.manifest.addons.email" class="text-danger">
This app requires <a href="#/email">Cloudron Email</a> to be enabled on this domain.
This app requires <a href="#/email">Cloudron Email</a> to be enabled on {{ appConfigure.domain.domain }}.
</p>
<a href="" ng-click="appConfigure.advancedVisible = true" ng-hide="appConfigure.advancedVisible">Advanced settings...</a>
@@ -164,13 +151,13 @@
</div>
</div>
<input class="ng-hide" type="submit" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()) || (appConfigure.usingAltDomain && !appConfigure.isAltDomainValid())"/>
<input class="ng-hide" type="submit" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())"/>
</form>
</fieldset>
</div>
<div class="modal-footer ">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" ng-click="appConfigure.submit()" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()) || (appConfigure.usingAltDomain && !appConfigure.isAltDomainValid())"><i class="fa fa-circle-o-notch fa-spin" ng-show="appConfigure.busy"></i> Save</button>
<button type="button" class="btn btn-success" ng-click="appConfigure.submit()" ng-disabled="appConfigureForm.$invalid || appConfigure.busy || (appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid())"><i class="fa fa-circle-o-notch fa-spin" ng-show="appConfigure.busy"></i> Save</button>
</div>
</div>
</div>
@@ -370,19 +357,19 @@
<div class="animateMeOpacity ng-hide" ng-show="installedApps.length > 0">
<div class="col-sm-1 grid-item" ng-repeat="app in installedApps | orderBy:'location'">
<div style="background-color: white;" class="highlight grid-item-content" uib-tooltip="{{ app.message | shortAppMessage }}">
<div style="background-color: white;" class="highlight grid-item-content" uib-tooltip="{{ app.fqdn }}">
<a ng-href="{{ app | applicationLink }}" ng-click="(app | installError) === true && showError(app)" target="_blank" ng-class="{ 'hand': !(app | installationActive) }">
<div class="grid-item-top">
<div class="row">
<div class="col-xs-12 text-center" style="padding-left: 5px; padding-right: 5px;">
<div class="text-small text-muted" ng-show="domains.length > 1">{{ app.domain }}</div><br/>
<br/>
<img ng-src="{{app.iconUrl || 'img/appicon_fallback.png'}}" fallback-icon="img/appicon_fallback.png" appstore-icon="{{ app.iconUrlStore }}" onerror="imageErrorHandler(this)" class="app-icon"/>
</div>
</div>
<br/>
<div class="row">
<div class="col-xs-12 text-center">
<div class="grid-item-top-title" data-fittext>{{ app.altDomain || app.location || app.fqdn }}</div>
<div class="grid-item-top-title" data-fittext>{{ app.location || app.fqdn }}</div>
<div class="text-muted status" style="text-overflow: ellipsis; white-space: nowrap; overflow: hidden">
{{ app | installationStateLabel }}
</div>
+6 -30
View File
@@ -18,7 +18,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
app: {},
domain: '',
location: '',
usingAltDomain: false,
advancedVisible: false,
portBindings: {},
portBindingsEnabled: {},
@@ -41,26 +40,13 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
return !!(tmp.users.length || tmp.groups.length);
},
isAltDomainValid: function () {
return ngTld.isValid($scope.appConfigure.location);
},
isAltDomainSubdomain: function () {
return ngTld.isSubdomain($scope.appConfigure.location);
},
isAltDomainNaked: function () {
return ngTld.isNakedDomain($scope.appConfigure.location);
},
show: function (app) {
$scope.reset();
// fill relevant info from the app
$scope.appConfigure.app = app;
$scope.appConfigure.location = app.altDomain || app.location;
$scope.appConfigure.location = app.location;
$scope.appConfigure.domain = $scope.domains.filter(function (d) { return d.domain === app.domain; })[0];
$scope.appConfigure.usingAltDomain = !!app.altDomain;
$scope.appConfigure.portBindingsInfo = app.manifest.tcpPorts || {}; // Portbinding map only for information
$scope. Option = app.accessRestriction ? 'groups' : 'any';
$scope.appConfigure.memoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
@@ -129,9 +115,8 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
}
var data = {
location: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.app.location : $scope.appConfigure.location,
altDomain: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.location : null,
domain: $scope.appConfigure.usingAltDomain ? undefined : $scope.appConfigure.domain.domain,
location: $scope.appConfigure.location,
domain: $scope.appConfigure.domain.domain,
portBindings: finalPortBindings,
accessRestriction: finalAccessRestriction,
cert: $scope.appConfigure.certificateFile,
@@ -275,7 +260,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
$scope.appConfigure.domain = null;
$scope.appConfigure.location = '';
$scope.appConfigure.advancedVisible = false;
$scope.appConfigure.usingAltDomain = false;
$scope.appConfigure.portBindings = {}; // This is the actual model holding the env:port pair
$scope.appConfigure.portBindingsEnabled = {}; // This is the actual model holding the enabled/disabled flag
$scope.appConfigure.certificateFile = null;
@@ -346,17 +330,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
});
};
$scope.useAltDomain = function (use, domain) {
$scope.appConfigure.usingAltDomain = use;
$scope.appConfigure.domain = domain;
if (use) {
$scope.appConfigure.location = '';
} else {
$scope.appConfigure.location = $scope.appConfigure.app.location;
}
};
$scope.showInformation = function (app) {
$scope.reset();
@@ -470,6 +443,9 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
return $timeout(fetchUsers, 5000);
}
// ensure we have something to work with in the access restriction dropdowns
users.forEach(function (user) { user.display = user.username || user.email; });
$scope.users = users;
});
}
+10 -5
View File
@@ -103,7 +103,7 @@
</div>
<p ng-show="appInstall.app.manifest.addons.email" class="text-danger">
This app requires <a href="#/email">Cloudron Email</a> to be enabled on this domain.
This app requires <a href="#/email">Cloudron Email</a> to be enabled on {{ appInstall.domain.domain }}
</p>
<div class="hide">
@@ -173,7 +173,7 @@
</div>
<div class="modal-body">
<p>
Please see all previously requested apps <a href="https://git.cloudron.io/cloudron/app-requests/issues" target="_blank">here</a> first.
Please see all previously requested apps <a href="https://forum.cloudron.io/category/5/app-requests" target="_blank">here</a> first.
If an app was already requested, leave a comment or give a +1, to help us prioritize better.
</p>
<br/>
@@ -247,7 +247,7 @@
<div class="checkbox" ng-show="appstoreLogin.register">
<label>
<input type="checkbox" ng-model="appstoreLogin.termsAccepted">I agree to Cloudron <a href="https://cloudron.io/legal/terms.html" target="_blank">terms</a>
<input type="checkbox" ng-model="appstoreLogin.termsAccepted">I accept the Cloudron <a href="https://cloudron.io/legal/license.html" target="_blank">license</a>
</label>
</div>
@@ -263,7 +263,7 @@
</div>
</div>
<div ng-show="ready && validAppstoreAccount" class="ng-cloak">
<div ng-show="ready && validAppstoreAccount" class="ng-cloak" id="appstoreGrid">
<div class="col-md-2">
<br/>
<div>
@@ -276,14 +276,19 @@
<br/>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === '' }" category="">All</a>
<br/>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'analytics' }" category="analytics"><i class="fa fa-bar-chart"></i> Analytics</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'blog' }" category="blog"><i class="fa fa-font"></i> Blog</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'chat' }" category="chat"><i class="fa fa-comments-o"></i> Chat</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'git' }" category="git"><i class="fa fa-code-fork"></i> Code Hosting</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'email' }" category="email"><i class="fa fa-envelope-o"></i> Email</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'sync' }" category="sync"><i class="fa fa-refresh"></i> File Sync</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'finance' }" category="finance"><i class="fa fa-dollar"></i> Finance</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'forum' }" category="forum"><i class="fa fa-users"></i> Forum</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'gallery' }" category="gallery"><i class="fa fa-picture-o"></i> Gallery</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'sync' }" category="sync"><i class="fa fa-refresh"></i> Sync</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'notes' }" category="notes"><i class="fa fa-sticky-note-o"></i> Notes</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'project' }" category="project"><i class="fa fa-line-chart"></i> Project Management</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'vpn' }" category="vpn"><i class="fa fa-user-secret"></i> VPN</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'hosting' }" category="hosting"><i class="fa fa-bars"></i> Web Hosting</a>
<a href="" class="appstore-category-link" ng-click="showCategory($event);" ng-class="{'category-active': category === 'wiki' }" category="wiki"><i class="fa fa-wikipedia-w"></i> Wiki</a>
<br/>
<br/>
+3 -1
View File
@@ -240,7 +240,7 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
$scope.feedback.busy = true;
$scope.feedback.error = null;
Client.feedback($scope.feedback.type, $scope.feedback.subject, $scope.feedback.description, function (error) {
Client.feedback($scope.feedback.type, $scope.feedback.subject, $scope.feedback.description, null, function (error) {
$scope.feedback.busy = false;
if (error) {
@@ -399,6 +399,8 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
}
$scope.ready = true;
document.getElementById('appstoreGrid').scrollIntoView();
});
};
+2 -2
View File
@@ -188,7 +188,7 @@
<div class="content">
<div class="text-left">
<h1>Domains <button class="btn btn-primary btn-outline pull-right" ng-show="false" ng-click="domainConfigure.show()"><i class="fa fa-plus"></i> Add Domain</button></h1>
<h1>Domains <button class="btn btn-primary btn-outline pull-right" ng-click="domainConfigure.show()"><i class="fa fa-plus"></i> Add Domain</button></h1>
</div>
<div class="card card-large">
@@ -214,7 +214,7 @@
{{ domain.domain }}
</td>
<td class="text-left elide-table-cell hidden-xs hidden-sm">
{{ domain.provider }}
{{ prettyProviderName(domain) }}
</td>
<td class="text-right no-wrap" style="vertical-align: bottom">
<button class="btn btn-xs btn-default" ng-click="domainMigrate.show(domain)" ng-show="domain.domain !== config.adminDomain && domain.provider !== 'caas' && provider === 'caas'" title="Migrate Domain"><i class="fa fa-exchange"></i></button>
+13
View File
@@ -22,6 +22,19 @@ angular.module('Application').controller('DomainsController', ['$scope', '$locat
{ name: 'No-op (only for development)', value: 'noop' }
];
$scope.prettyProviderName = function (domain) {
switch (domain.provider) {
case 'caas': return 'Managed Cloudron';
case 'route53': return 'AWS Route53';
case 'cloudflare': return 'Cloudflare (DNS only)';
case 'digitalocean': return 'Digital Ocean';
case 'gcdns': return 'Google Cloud';
case 'manual': return domain.config.wildcard ? 'Wildcard' : 'Manual';
case 'noop': return 'No-op';
default: return 'Unknown';
}
};
function readFileLocally(obj, file, fileName) {
return function (event) {
$scope.$apply(function () {
+7 -6
View File
@@ -12,11 +12,12 @@
<div class="modal-body" ng-hide="selectedDomain.provider === 'noop' || selectedDomain.provider === 'manual'">
Cloudron will setup Email related DNS records automatically.
If this domain is already configured to handle email with some other provider, it will <b>overwrite</b> those records.
<br/><br/>
Disabling Cloudron Email later will <b>not</b> put the old records back.
<br/><br/>
Status of DNS Records will show an error while DNS is propagating (~5 minutes).
<br/>
<br/><br/>
<b>Be sure to enable user and group mailboxes for this domain from the <i>Users</i> view.</b>
<br/><br/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
@@ -67,7 +68,7 @@
<div class="text-left">
<h1>
Email
<select class="form-control pull-right" style="display: inline-block; width: 200px;" ng-model="selectedDomain" ng-options="a.domain for a in domains" ng-show="false" ng-change="refreshDomain()"></select>
<select class="form-control pull-right" style="display: inline-block; width: 200px;" ng-model="selectedDomain" ng-options="a.domain for a in domains" ng-change="refreshDomain()"></select>
</h1>
</div>
@@ -85,7 +86,7 @@
<div class="row" ng-show="selectedDomain.mailConfig.enabled">
<br/>
<div class="col-md-12">
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a>
<b><a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a></b>
<div id="mail_settings" class="panel-collapse collapse">
<br/>
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 993 (TLS)</p>
@@ -217,7 +218,7 @@
<br/><br/>
<div ng-repeat="record in expectedDnsRecordsTypes">
<div class="row" ng-if="expectedDnsRecords[record.value] && (mailConfig.enabled || (record.name !== 'DMARC' && record.name !== 'MX'))">
<div class="row" ng-if="expectedDnsRecords[record.value] && (selectedDomain.mailConfig.enabled || (record.name !== 'DMARC' && record.name !== 'MX'))">
<div class="col-xs-12">
<p class="text-muted">
<i ng-hide="refreshBusy" ng-class="expectedDnsRecords[record.value].status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
@@ -254,7 +255,7 @@
<p class="text-muted">
<i ng-hide="refreshBusy" ng-class="selectedDomain.mailStatus.relay.status ? 'fa fa-check-circle text-success' : 'fa fa-exclamation-triangle text-danger'"></i> &nbsp;
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#collapse_outbound_smtp">
Outbound SMTP
{{ selectedDomain.mailConfig.relay.provider === 'cloudron-smtp' ? 'Outbound SMTP (Direct)' : 'Outbound SMTP (Relay)' }}
</a>
<button class="btn btn-xs btn-default" ng-click="refreshStatus()" ng-disabled="refreshBusy" ng-show="!selectedDomain.mailStatus.relay.status"><i class="fa fa-refresh" ng-class="{ 'fa-pulse': refreshBusy }"></i></button>
</p>
+8 -8
View File
@@ -272,7 +272,7 @@
</tr>
<tr>
<td class="text-muted" style="vertical-align: top;">Provider</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ config.provider }}</td>
<td class="text-right" style="vertical-align: top; white-space: nowrap;">{{ prettyProviderName(config.provider) }}</td>
</tr>
</table>
</div>
@@ -318,7 +318,7 @@
<div class="card" style="margin-bottom: 15px;" ng-show="backupConfig.provider !== 'caas'">
<div class="row" ng-show="currentSubscription.plan.id === 'free' || currentSubscription.plan.id === 'undecided'">
<div class="col-xs-12">
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://chat.cloudron.io">live chat</a>.
With a paid plan, you get continuous updates for the Cloudron and apps. This ensures you are running the latest versions of apps and keeps your server secure. All paid plans come with support via <a href="mailto:support@cloudron.io">email</a> and <a target="_blank" href="https://forum.cloudron.io">forum</a>.
</div>
</div>
<br/>
@@ -360,12 +360,12 @@
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Provider</span>
</div>
<div class="col-xs-6 text-right">
<span>{{ backupConfig.provider === 'caas' ? 'cloudron.io' : backupConfig.provider }}</span>
<span>{{ prettyProviderName(backupConfig.provider) }}</span>
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
@@ -379,7 +379,7 @@
</div>
</div>
<div class="row" ng-show="backupConfig.provider !== 'caas'">
<div class="row">
<div class="col-xs-6">
<span class="text-muted">Storage Format</span>
</div>
@@ -443,13 +443,13 @@
</div>
<div class="text-left">
<h3>Updates</h3>
<h3>App Updates</h3>
</div>
<div class="card" style="margin-bottom: 15px;">
<div class="row">
<div class="col-md-12">
<p>Configure the update schedule for the platform and the apps</p>
<p>Configure the update schedule for the apps</p>
<p class="text-danger" ng-show="autoUpdate.error"><br/>{{ autoUpdate.error }}</p>
</div>
</div>
@@ -458,7 +458,7 @@
<div class="col-md-12">
<div class="radio">
<label>
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 00 1,3,5,23 * * *">
<input type="radio" name="scheduleRadio" ng-change="autoUpdate.success = false" ng-model="autoUpdate.pattern" value="00 30 1,3,5,23 * * *">
Every night
</label>
</div>
+9 -2
View File
@@ -69,6 +69,13 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
{ name: 'rsync', value: 'rsync' }
];
$scope.prettyProviderName = function (provider) {
switch (provider) {
case 'caas': return 'Managed Cloudron';
default: return provider;
}
};
$scope.planChange = {
busy: false,
error: {},
@@ -540,7 +547,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
$scope.autoUpdate.busy = true;
$scope.autoUpdate.success = false;
Client.setAutoupdatePattern($scope.autoUpdate.pattern, function (error) {
Client.setAppAutoupdatePattern($scope.autoUpdate.pattern, function (error) {
if (error) $scope.autoUpdate.error = error.message;
else $scope.autoUpdate.currentPattern = $scope.autoUpdate.pattern;
@@ -581,7 +588,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
}
function getAutoupdatePattern() {
Client.getAutoupdatePattern(function (error, result) {
Client.getAppAutoupdatePattern(function (error, result) {
if (error) return console.error(error);
$scope.autoUpdate.currentPattern = result.pattern;
+12 -8
View File
@@ -5,7 +5,7 @@
</div>
<div class="text-left">
<h3>Documentation and Chat</h3>
<h3>Documentation and Forum</h3>
</div>
<div class="card">
@@ -14,7 +14,7 @@
<div class="col-lg-12">
For user manuals and app development related questions, please refer to our <a href="{{ config.webServerOrigin }}/documentation.html" target="_blank"> documentation</a>.
<br/><br/>
For any other questions, chat with us live at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a> write to <a href="mailto:support@cloudron.io">support@cloudron.io</a>.
For any other questions, search and ask in our <a href="https://forum.cloudron.io/" target="_blank">forum</a> or write to <a href="mailto:support@cloudron.io">support@cloudron.io</a>.
</div>
</div>
</div>
@@ -34,17 +34,21 @@
<form name="feedbackForm" ng-submit="submitFeedback()">
<div class="form-group">
<select class="form-control" name="type" style="width: 50%;" ng-model="feedback.type" required>
<option value="feedback">Enhancement / Idea</option>
<option value="app_error">App Error</option>
<option value="ticket">Bug Report</option>
<option value="app_missing">Missing App</option>
<option value="app_error">App Error/Failing</option>
</select>
</div>
<div class="form-group" ng-show="feedback.type === 'app_error'">
<select class="form-control" name="type" style="width: 50%;" ng-model="feedback.appId" ng-required="feedback.type === 'app_error'">
<option value="" disabled selected>Select App</option>
<option ng-repeat="app in apps" value="{{ app.id }}">{{ app.fqdn }}</option>
</select>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.subject.$dirty && feedbackForm.subject.$invalid) }">
<input type="text" class="form-control" name="subject" placeholder="Enter your idea or issue" ng-model="feedback.subject" ng-maxlength="512" ng-minlength="1" required>
<input type="text" class="form-control" name="subject" placeholder="Topic" ng-model="feedback.subject" ng-maxlength="512" ng-minlength="1" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (feedbackForm.description.$dirty && feedbackForm.description.$invalid) }">
<textarea class="form-control" name="description" rows="3" placeholder="Describe your idea or issue" ng-model="feedback.description" ng-minlength="1" required></textarea>
<textarea class="form-control" name="description" rows="3" placeholder="Describe your issue" ng-model="feedback.description" ng-minlength="1" required></textarea>
</div>
<button type="submit" class="btn btn-primary" ng-disabled="feedbackForm.$invalid || feedback.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="feedback.busy"></i> Submit</button>
<span ng-show="feedback.error" class="text-danger text-bold">{{feedback.error}}</span>
@@ -66,7 +70,7 @@
Enable this option to allow Cloudron engineers to connect to this server via SSH.
<br/>
<br/>
Do not enable this option before contacting us first at <a href="https://chat.cloudron.io/" target="_blank">chat.cloudron.io</a>.
<b>Only enable this option if you were asked to do so from Cloudron support!</b>
<br/>
<br/>
<button class="btn" ng-class="{ 'btn-danger': !sshSupportEnabled, 'btn-primary': sshSupportEnabled }" ng-click="toggleSshSupport()">{{ sshSupportEnabled ? 'Disable SSH support access' : 'Enable SSH support access' }}</button>
+7 -4
View File
@@ -3,14 +3,16 @@
angular.module('Application').controller('SupportController', ['$scope', '$location', 'Client', function ($scope, $location, Client) {
$scope.config = Client.getConfig();
$scope.user = Client.getUserInfo();
$scope.apps = Client.getInstalledApps();
$scope.feedback = {
error: null,
success: false,
busy: false,
subject: '',
type: '',
description: ''
type: 'ticket',
description: '',
appId: ''
};
$scope.sshSupportEnabled = false;
@@ -18,7 +20,8 @@ angular.module('Application').controller('SupportController', ['$scope', '$locat
function resetFeedback() {
$scope.feedback.subject = '';
$scope.feedback.description = '';
$scope.feedback.type = '';
$scope.feedback.type = 'ticket';
$scope.feedback.appId = '';
$scope.feedbackForm.$setUntouched();
$scope.feedbackForm.$setPristine();
@@ -29,7 +32,7 @@ angular.module('Application').controller('SupportController', ['$scope', '$locat
$scope.feedback.success = false;
$scope.feedback.error = null;
Client.feedback($scope.feedback.type, $scope.feedback.subject, $scope.feedback.description, function (error) {
Client.feedback($scope.feedback.type, $scope.feedback.subject, $scope.feedback.description, $scope.feedback.appId, function (error) {
if (error) {
$scope.feedback.error = error.message;
} else {
+5 -6
View File
@@ -86,13 +86,13 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Edit user {{ useredit.userInfo.username }}</h4>
<h4 class="modal-title">Edit user {{ useredit.userInfo.username || useredit.userInfo.email }}</h4>
</div>
<div class="modal-body">
<form name="useredit_form" role="form" ng-submit="useredit.submit()" autocomplete="off">
<input type="password" style="display: none;">
<div class="form-group" ng-class="{ 'has-error': (useredit_form.email.$dirty && useredit_form.email.$invalid) || (!useredit_form.email.$dirty && useredit.error.email) }">
<label class="control-label" >Email</label>
<label class="control-label">Primary email</label>
<div class="control-label" ng-show="(!useredit_form.email.$dirty && useredit.error.email) || (useredit_form.email.$dirty && useredit_form.email.$invalid) || (!useredit_form.email.$dirty && useredit.error.email)">
<small ng-show="useredit_form.email.$error.required">An email is required</small>
<small ng-show="useredit_form.email.$error.email">This is not a valid email</small>
@@ -101,7 +101,7 @@
<input type="email" class="form-control" ng-model="useredit.email" name="email" required>
</div>
<div class="form-group" ng-class="{ 'has-error': (useredit_form.fallbackEmail.$dirty && useredit_form.fallbackEmail.$invalid) || (!useredit_form.fallbackEmail.$dirty && useredit.error.fallbackEmail) }">
<label class="control-label" >Password recovery email</label>
<label class="control-label">Password recovery email</label>
<div class="control-label" ng-show="(!useredit_form.fallbackEmail.$dirty && useredit.error.fallbackEmail) || (useredit_form.fallbackEmail.$dirty && useredit_form.fallbackEmail.$invalid) || (!useredit_form.fallbackEmail.$dirty && useredit.error.fallbackEmail)">
<small ng-show="useredit_form.fallbackEmail.$error.required">An email is required</small>
<small ng-show="useredit_form.fallbackEmail.$error.fallbackEmail">This is not a valid email</small>
@@ -120,7 +120,7 @@
</div>
<h2 ng-show="useredit.busyFetching"><center><i class="fa fa-circle-o-notch fa-spin"></i></center></h2>
<div ng-hide="useredit.busyFetching || useredit.availableEmailDomains.length === 0" class="form-group">
<label class="control-label">Email address on domains</label>
<label class="control-label">Email mailboxes</label>
<div>
<multiselect ng-model="useredit.selectedEmailDomains" options="d.address for d in useredit.availableEmailDomains" data-multiple="true"></multiselect>
</div>
@@ -139,11 +139,10 @@
</div>
</div>
</div>
<br/>
<div class="form-group" ng-hide="isMe(useredit.userInfo)">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="useredit.superuser"> Make this user a Cloudron admin
<input type="checkbox" ng-model="useredit.superuser"> User is an <a href="https://cloudron.io/documentation/user-management/#administrators" target="_blank">administrator</a>
</label>
</div>
</div>
+1 -1
View File
@@ -177,7 +177,7 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
$scope.useredit.groupIds = angular.copy(userInfo.groupIds);
$scope.useredit.superuser = userInfo.groupIds.indexOf('admin') !== -1;
$scope.useredit.availableEmailDomains = $scope.emailDomains.map(function (d) { return { domain: d, address: userInfo.username + '@' + d.domain }; });
$scope.useredit.availableEmailDomains = userInfo.username ? $scope.emailDomains.map(function (d) { return { domain: d, address: userInfo.username + '@' + d.domain }; }) : []; // username can be null if invited user has not signed up yet
$scope.useredit.currentEmailDomains = [];
$scope.useredit.selectedEmailDomains = [];
$scope.useredit.aliases = {};