Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bce3d3f664 | |||
| 828d6f6cc8 | |||
| 0a026cc143 | |||
| 3bc9a87933 | |||
| 769f9adc9d | |||
| b5f53d921e | |||
| 105e9e7825 | |||
| c8cf050156 | |||
| b7baafbbe6 | |||
| 85dde71ec3 | |||
| 2970b086a3 | |||
| 5910709008 | |||
| 2b6ce4f813 | |||
| 451c697fb7 | |||
| 09149318b1 | |||
| d2d8eb9485 | |||
| 91265613a9 | |||
| 31c414bbe1 | |||
| e2a3654ed7 | |||
| 96d7283534 | |||
| 256a7e322b | |||
| e5b78337ac | |||
| 67ba5aa1c5 | |||
| 848a617f98 | |||
| 1fc7efef0d | |||
| 576f6eafbb | |||
| 2caf73b5e3 | |||
| 56abb68e0c | |||
| 7aaac5a48a | |||
| 8326587886 | |||
| 466b3f4784 | |||
| bccdf548a8 | |||
| fa4b1b3d5b | |||
| 9d47fd198f | |||
| 5966ee6800 | |||
| 2d20e3c13d | |||
| 2172f8532d | |||
| 9dc4318152 | |||
| e1a92e7127 | |||
| 767b31caa2 | |||
| c2232936e0 | |||
| 4f1bbfd9e3 | |||
| caf57e37dc | |||
| 64b8e4ad6c | |||
| c9d3907124 | |||
| bf6bea800b | |||
| 26f1673d47 | |||
| 08153454a2 | |||
| efc26ab587 | |||
| e24e0a7e87 | |||
| 23bc267c46 | |||
| 35cc592d61 | |||
| 512f6a1166 | |||
| 3160ffec3f | |||
| c543d4517f | |||
| d7334b991b |
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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>
|
||||
|
||||
@@ -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">© 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
@@ -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
@@ -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;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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>
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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>
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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
@@ -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
|
||||
// ----------------------------
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<footer class="text-center">
|
||||
<span class="text-muted">©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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
<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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
Reference in New Issue
Block a user