Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eef360673b | |||
| 36e298c758 | |||
| 275157f27b | |||
| e776deaa3f | |||
| 4fc8e9b45e | |||
| fe41eec7c5 | |||
| d1d1d22734 | |||
| da8b76957a | |||
| 305f9fd1cf | |||
| cd2a94ddb8 | |||
| a2df4db504 | |||
| b7740a4758 | |||
| 62c24de5c4 | |||
| 5ed3e67b76 | |||
| c7f2314a15 | |||
| 420c7ebd67 | |||
| b93b1a6eec | |||
| 7d52be6e99 | |||
| 9b1f0e394a | |||
| 1b0cb5d455 | |||
| 9b79d59d93 | |||
| 3e12316ea1 | |||
| 1b38c0111f | |||
| 5542393eb5 | |||
| ad48bc0ee8 | |||
| ba0e5d0b59 | |||
| 1c5ff88e3c | |||
| bf7d4a550e | |||
| 324bc763fc | |||
| f9fb2ca3a1 | |||
| b5eac7c91b | |||
| 3c858ca0fd | |||
| da9d634b83 | |||
| 128704400f | |||
| a3594322bd | |||
| fe4b3d5f1d | |||
| da08da2b54 | |||
| 5deb5f79bd | |||
| 9f0d694f0a | |||
| 4153fb7d1e | |||
| 6994ec0f03 | |||
| e1af60cfa9 | |||
| 7bcec61e6d | |||
| dde287f05d |
+1
-1
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||
|
||||
<!-- this gets changed once we get the config (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title> Cloudron </title>
|
||||
<title>‎</title>
|
||||
|
||||
<link id="favicon" type="image/png" rel="icon" href="/api/v1/cloudron/avatar">
|
||||
<link rel="apple-touch-icon" href="/api/v1/cloudron/avatar">
|
||||
|
||||
+23
-38
@@ -1879,15 +1879,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.addMailDomain = function (domain, callback) {
|
||||
post('/api/v1/mail', { domain: domain }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 201) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.addDomain = function (domain, zoneName, provider, config, fallbackCertificate, tlsConfig, callback) {
|
||||
var data = {
|
||||
domain: domain,
|
||||
@@ -1905,7 +1896,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
if (error) return callback(error);
|
||||
if (status !== 201) return callback(new ClientError(status, data));
|
||||
|
||||
that.addMailDomain(domain, callback);
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1928,24 +1919,6 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.removeMailDomain = function (domain, callback) {
|
||||
var config = {
|
||||
data: {
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME
|
||||
del('/api/v1/mail/' + domain, config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.renewCerts = function (domain, callback) {
|
||||
post('/api/v1/cloudron/renew_certs', { domain: domain || null }, null, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
@@ -1964,14 +1937,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
}
|
||||
};
|
||||
|
||||
this.removeMailDomain(domain, function () {
|
||||
// hack: ignore errors until we fix the domains.js
|
||||
del('/api/v1/domains/' + domain, config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
del('/api/v1/domains/' + domain, config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 204) return callback(new ClientError(status, data));
|
||||
|
||||
callback(null);
|
||||
});
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2002,10 +1972,11 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
};
|
||||
|
||||
// Email
|
||||
Client.prototype.getMailEventLogs = function (search, page, perPage, callback) {
|
||||
Client.prototype.getMailEventLogs = function (search, types, page, perPage, callback) {
|
||||
var config = {
|
||||
params: {
|
||||
page: page,
|
||||
types: types,
|
||||
per_page: perPage,
|
||||
search: search
|
||||
}
|
||||
@@ -2097,7 +2068,14 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
|
||||
// Mailboxes
|
||||
Client.prototype.getMailboxes = function (domain, callback) {
|
||||
get('/api/v1/mail/' + domain + '/mailboxes', null, function (error, data, status) {
|
||||
var config = {
|
||||
params: {
|
||||
page: 1,
|
||||
per_page: 1000
|
||||
}
|
||||
};
|
||||
|
||||
get('/api/v1/mail/' + domain + '/mailboxes', config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
@@ -2160,7 +2138,14 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout
|
||||
};
|
||||
|
||||
Client.prototype.getAliases = function (domain, name, callback) {
|
||||
get('/api/v1/mail/' + domain + '/aliases/' + name, null, function (error, data, status) {
|
||||
var config = {
|
||||
params: {
|
||||
page: 1,
|
||||
per_page: 1000
|
||||
}
|
||||
};
|
||||
|
||||
get('/api/v1/mail/' + domain + '/aliases/' + name, config, function (error, data, status) {
|
||||
if (error) return callback(error);
|
||||
if (status !== 200) return callback(new ClientError(status, data));
|
||||
|
||||
|
||||
+26
-17
@@ -281,25 +281,27 @@ app.filter('prettyDomains', function () {
|
||||
};
|
||||
});
|
||||
|
||||
// we use 1024 unit in memory limit in manifest
|
||||
app.filter('prettyMemory', function () {
|
||||
return function (memory) {
|
||||
// Adjust the default memory limit if it changes
|
||||
return memory ? Math.floor(memory / 1024 / 1024) : 256;
|
||||
};
|
||||
});
|
||||
|
||||
// df -H style (si) output
|
||||
app.filter('prettyMailSize', function () {
|
||||
return function (size) {
|
||||
if (!size) return '0 kB';
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; };
|
||||
var i = Math.floor(Math.log(size) / Math.log(1000));
|
||||
return (size / Math.pow(1000, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; };
|
||||
});
|
||||
|
||||
// df -H style (si) output
|
||||
app.filter('prettyDiskSize', function () {
|
||||
return function (size) {
|
||||
if (!size) return 'Not available yet';
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; };
|
||||
var i = Math.floor(Math.log(size) / Math.log(1000));
|
||||
return (size / Math.pow(1000, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; };
|
||||
});
|
||||
|
||||
app.filter('installationActive', function () {
|
||||
@@ -581,7 +583,8 @@ app.directive('tagInput', function () {
|
||||
scope: {
|
||||
inputTags: '=taglist'
|
||||
},
|
||||
link: function ($scope, element, attrs) {
|
||||
require: '^form',
|
||||
link: function ($scope, element, attrs, formCtrl) {
|
||||
$scope.defaultWidth = 200;
|
||||
$scope.tagText = ''; // current tag being edited
|
||||
$scope.placeholder = attrs.placeholder;
|
||||
@@ -589,18 +592,20 @@ app.directive('tagInput', function () {
|
||||
if ($scope.inputTags === undefined) {
|
||||
return [];
|
||||
}
|
||||
return $scope.inputTags.split(',').filter(function (tag) {
|
||||
return $scope.inputTags.split(' ').filter(function (tag) {
|
||||
return tag !== '';
|
||||
});
|
||||
};
|
||||
$scope.addTag = function () {
|
||||
var tagArray;
|
||||
if ($scope.tagText.length === 0) {
|
||||
return;
|
||||
var tagArray = $scope.tagArray();
|
||||
|
||||
// prevent adding empty or existing items
|
||||
if ($scope.tagText.length === 0 || tagArray.indexOf($scope.tagText) !== -1) {
|
||||
return $scope.tagText = '';
|
||||
}
|
||||
tagArray = $scope.tagArray();
|
||||
|
||||
tagArray.push($scope.tagText);
|
||||
$scope.inputTags = tagArray.join(',');
|
||||
$scope.inputTags = tagArray.join(' ');
|
||||
return $scope.tagText = '';
|
||||
};
|
||||
$scope.deleteTag = function (key) {
|
||||
@@ -613,7 +618,8 @@ app.directive('tagInput', function () {
|
||||
tagArray.splice(key, 1);
|
||||
}
|
||||
}
|
||||
return $scope.inputTags = tagArray.join(',');
|
||||
formCtrl.$setDirty();
|
||||
return $scope.inputTags = tagArray.join(' ');
|
||||
};
|
||||
$scope.$watch('tagText', function (newVal, oldVal) {
|
||||
var tempEl;
|
||||
@@ -626,6 +632,9 @@ app.directive('tagInput', function () {
|
||||
return tempEl.remove();
|
||||
}
|
||||
});
|
||||
element.bind('click', function () {
|
||||
element[0].firstChild.lastChild.focus();
|
||||
});
|
||||
element.bind('keydown', function (e) {
|
||||
var key = e.which;
|
||||
if (key === 9 || key === 13) {
|
||||
@@ -637,7 +646,7 @@ app.directive('tagInput', function () {
|
||||
});
|
||||
element.bind('keyup', function (e) {
|
||||
var key = e.which;
|
||||
if (key === 9 || key === 13 || key === 32 || key === 188) {
|
||||
if (key === 9 || key === 13 || key === 32) {
|
||||
e.preventDefault();
|
||||
return $scope.$apply('addTag()');
|
||||
}
|
||||
@@ -645,9 +654,9 @@ app.directive('tagInput', function () {
|
||||
},
|
||||
template:
|
||||
'<div class="tag-input-container">' +
|
||||
'<div class="input-tag" data-ng-repeat="tag in tagArray()">' +
|
||||
'{{tag}}' +
|
||||
'<div class="delete-tag" data-ng-click="deleteTag($index)">×</div>' +
|
||||
'<div class="btn-group input-tag" data-ng-repeat="tag in tagArray()">' +
|
||||
'<button type="button" class="btn btn-xs btn-primary" disabled>{{ tag }}</button>' +
|
||||
'<button type="button" class="btn btn-xs btn-primary" data-ng-click="deleteTag($index)">×</button>' +
|
||||
'</div>' +
|
||||
'<input type="text" data-ng-model="tagText" ng-blur="addTag()" placeholder="{{placeholder}}"/>' +
|
||||
'</div>'
|
||||
|
||||
+2
-1
@@ -120,7 +120,7 @@ app.controller('LoginController', ['$scope', '$http', function ($scope, $http) {
|
||||
};
|
||||
|
||||
$scope.showLogin = function () {
|
||||
window.document.title = 'Cloudron Login';
|
||||
if ($scope.status) window.document.title = $scope.status.cloudronName + ' Login';
|
||||
$scope.mode = 'login';
|
||||
$scope.error = false;
|
||||
setTimeout(function () { $('#inputUsername').focus(); }, 200);
|
||||
@@ -137,6 +137,7 @@ app.controller('LoginController', ['$scope', '$http', function ($scope, $http) {
|
||||
|
||||
if (status !== 200) return;
|
||||
|
||||
if ($scope.mode === 'login') window.document.title = data.cloudronName + ' Login';
|
||||
$scope.status = data;
|
||||
}).error(function () {
|
||||
$scope.initialized = false;
|
||||
|
||||
@@ -94,6 +94,7 @@ app.controller('LogsController', ['$scope', 'Client', function ($scope, Client)
|
||||
{ name: 'Docker', type: 'service', value: 'docker', url: Client.makeURL('/api/v1/services/docker/logs') },
|
||||
{ name: 'Unbound', type: 'service', value: 'unbound', url: Client.makeURL('/api/v1/services/unbound/logs') },
|
||||
{ name: 'SFTP', type: 'service', value: 'sftp', url: Client.makeURL('/api/v1/services/sftp/logs') },
|
||||
{ name: 'TURN/STUN', type: 'service', value: 'turn', url: Client.makeURL('/api/v1/services/turn/logs') },
|
||||
];
|
||||
|
||||
$scope.selected = BUILT_IN_LOGS.find(function (e) { return e.value === ids.id; });
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src <%= apiOrigin %> 'unsafe-inline' 'unsafe-eval' 'self'; img-src <%= apiOrigin %> 'self'" />
|
||||
|
||||
<title>Cloudron Login</title>
|
||||
<!-- this gets changed once we get the status (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title>‎</title>
|
||||
|
||||
<link id="favicon" href="<%= apiOrigin %>/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||
|
||||
<!-- this gets changed once we get the config (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title> Logs </title>
|
||||
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||
|
||||
<!-- this gets changed once we get the config (because angular has not loaded yet, we see template string for a flash) -->
|
||||
<title> Terminal </title>
|
||||
|
||||
<link id="favicon" href="/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||
|
||||
+139
-34
@@ -269,6 +269,10 @@ h1, h2, h3 {
|
||||
left: -999em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Apps view
|
||||
// ----------------------------
|
||||
@@ -480,6 +484,15 @@ multiselect {
|
||||
// Mail view
|
||||
// ----------------------------
|
||||
|
||||
.maillog-filter {
|
||||
display: inline-block;
|
||||
|
||||
.form-control {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.email-domain-list-item {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
@@ -1032,7 +1045,7 @@ select.purpose:invalid {
|
||||
color: $brand-danger;
|
||||
|
||||
a {
|
||||
color: $brand-danger;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,8 +1168,8 @@ footer {
|
||||
.settings-avatar {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
@@ -1267,6 +1280,18 @@ footer {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.users-filter {
|
||||
display: inline-block;
|
||||
padding-left: 0;
|
||||
margin: 20px 0;
|
||||
border-radius: 2px;
|
||||
|
||||
.form-control {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Upgrade
|
||||
// ----------------------------
|
||||
@@ -1327,7 +1352,7 @@ footer {
|
||||
// Eventlog/Activity
|
||||
// ----------------------------
|
||||
|
||||
.filter {
|
||||
.eventlog-filter {
|
||||
display: inline-block;
|
||||
padding-left: 0;
|
||||
margin: 20px 0;
|
||||
@@ -1356,42 +1381,122 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Branding
|
||||
// ----------------------------
|
||||
|
||||
.branding-avatar {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
border: 1px solid gray;
|
||||
border-radius: 3px;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(127, 127, 127 ,0.3);
|
||||
background-image: url('/img/plus.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: all 150ms;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.branding-avatar-selector {
|
||||
text-align: center;
|
||||
|
||||
.grid {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.preview-avatar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: inline-block;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transform: scale(1.0);
|
||||
transition: all 150ms;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&.add {
|
||||
border-radius: 2px;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
background-color: $brand-primary;
|
||||
background-image: url('/img/plus.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Tag Input
|
||||
// ----------------------------
|
||||
// https://codepen.io/webmatze/pen/isuHh
|
||||
|
||||
.tag-input-container {
|
||||
input {
|
||||
float: left;
|
||||
height: 18px;
|
||||
padding: 0px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: black;
|
||||
border: 0px;
|
||||
margin: 1px;
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0px;
|
||||
tag-input {
|
||||
height: auto !important;
|
||||
cursor: text;
|
||||
|
||||
.tag-input-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
padding: 0px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: black;
|
||||
border: 0px;
|
||||
margin: 4px 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-tag {
|
||||
padding: 2px 4px;
|
||||
line-height: 12px;
|
||||
font-size: 11px;
|
||||
background-color: #e3eaf6;
|
||||
float: left;
|
||||
border-radius: 2px;
|
||||
margin: 2px 5px 2px 0px;
|
||||
border: 1px solid #a9b6d2;
|
||||
.delete-tag {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 0px 2px;
|
||||
&:hover {
|
||||
background-color: #96b4d2;
|
||||
|
||||
.input-tag {
|
||||
margin: 2px 0;
|
||||
margin-right: 4px;
|
||||
|
||||
:first-child {
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<div>
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="filter">
|
||||
<div class="eventlog-filter">
|
||||
<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)" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<select class="form-control" ng-model="pageItems" ng-options="a.name for a in pageItemCount" ng-change="updateFilter(true)"></select>
|
||||
|
||||
+16
-13
@@ -155,7 +155,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="storageProvider">Storage provider <sup><a ng-href="{{ config.webServerOrigin }}/documentation/backups/#storage-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<select class="form-control" id="storageProvider" ng-model="importBackup.provider" ng-options="a.value as a.name for a in storageProvider" ng-change=importBackup.clearForm()></select>
|
||||
<select class="form-control" id="storageProvider" ng-model="importBackup.provider" ng-options="a.value as a.name for a in storageProvider" ng-change="importBackup.clearForm()" ng-disabled="importBackup.busy"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': importBackup.error.key }">
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="storageFormat">Storage Format <sup><a ng-href="{{ config.webServerOrigin }}/documentation/backups/#backup-formats" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<select class="form-control" id="storageFormat" ng-change="importBackup.key = ''" ng-model="importBackup.format" ng-options="a.value as a.name for a in formats"></select>
|
||||
<select class="form-control" id="storageFormat" ng-change="importBackup.key = ''" ng-model="importBackup.format" ng-options="a.value as a.name for a in formats" ng-disabled="importBackup.busy"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': importBackup.error.key }">
|
||||
@@ -254,7 +254,7 @@
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="importBackup.submit()" ng-disabled="importBackupForm.$invalid || importBackup.busy"><i class="fa fa-circle-notch fa-spin" ng-show="importBackup.busy"></i><span>Import</span></button>
|
||||
<button type="submit" class="btn btn-outline btn-success pull-right" ng-click="importBackup.submit()" ng-disabled="importBackupForm.$invalid || importBackup.busy"><i class="fa fa-circle-notch fa-spin" ng-show="importBackup.busy"></i> Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -404,7 +404,7 @@
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" ng-show="view">
|
||||
<div class="col-sm-2 text-center">
|
||||
<img ng-src="{{ app.iconUrl || 'img/appicon_fallback.png' }}" fallback-icon="img/appicon_fallback.png" onerror="imageErrorHandler(this)" class="app-icon"/>
|
||||
</div>
|
||||
@@ -433,15 +433,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-8 col-sm-offset-2" style="height: 10px;">
|
||||
<div class="progress progress-striped active animateMeOpacity" ng-show="app.taskId" style="height: 10px;">
|
||||
<div class="row" ng-show="app.taskId">
|
||||
<div class="col-sm-8 col-sm-offset-2" style="height: 10px; display: flex; align-items: center;">
|
||||
<div class="progress progress-striped active animateMeOpacity" style="height: 10px; flex-grow: 1;">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ app.taskProgress }}%"></div>
|
||||
</div>
|
||||
<div ng-show="app.taskMinutesActive >= 5" class="text-danger hand" style="margin: 0 4px;" ng-click="stopAppTask(app.taskId)" uib-tooltip="Cancel Task"><i class="fas fa-times"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row app-configure-links-container">
|
||||
<div class="row" ng-hide="view">
|
||||
<div class="col-md-12 text-center">
|
||||
<br/><br/><h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row app-configure-links-container" ng-show="view">
|
||||
<div class="col-sm-2">
|
||||
<div class="app-configure-links">
|
||||
<div ng-click="setView('display')" ng-class="{ 'active': view === 'display' }">Display</div>
|
||||
@@ -470,7 +476,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Tags</label>
|
||||
<tag-input class="form-control" placeholder="Use comma to separate tags" taglist="display.tags" name="tags" uib-tooltip="For grouping in the dashboard"></tag-input>
|
||||
<tag-input class="form-control" placeholder="Use space to separate tags" taglist="display.tags" name="tags" uib-tooltip="For grouping in the dashboard"></tag-input>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
@@ -725,7 +731,7 @@
|
||||
<label class="control-label" for="resourcesEnableDataDir">Storage <sup><a ng-href="{{ config.webServerOrigin }}/documentation/storage/#app-data-directory" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<p>
|
||||
By default, this app's data is located at <code>/home/yellowtent/appsdata/{{ app.id }}</code>. If the server is running out of disk space,
|
||||
you can mount an external disk and move this app's data there.
|
||||
you can mount an external disk and move this app's data there. Only Ext4 and NFS mounts are supported.
|
||||
</p>
|
||||
<form role="form" name="resourcesDataDirForm" ng-submit="resources.submitDataDir()" autocomplete="off">
|
||||
<fieldset>
|
||||
@@ -959,9 +965,6 @@
|
||||
<p>If a configuration, update, restore or backup action resulted in an error, you can retry the task.</p>
|
||||
<p ng-show="app.error">An error occurred during the <b>{{ app.error.installationState | taskName }}</b> operation: <span class="text-danger"><b>{{ app.error.reason + ': ' + app.error.message }}</b></span></p>
|
||||
<button class="btn btn-primary pull-right" ng-click="repair.confirm()" ng-disabled="app.taskId || !app.error" tooltip-enable="app.taskId" uib-tooltip="App is busy">Retry {{ app.error.installationState | taskName }}</button>
|
||||
|
||||
<!-- this is hidden for now, use the CLI instead -->
|
||||
<button class="btn btn-danger pull-right" ng-click="repair.stopAppTask(app.taskId)" ng-show="false && app.taskId">Cancel Current Task</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+13
-9
@@ -7,6 +7,7 @@
|
||||
/* global RSTATES */
|
||||
/* global ISTATES */
|
||||
/* global ERROR */
|
||||
/* global moment */
|
||||
|
||||
angular.module('Application').controller('AppController', ['$scope', '$location', '$timeout', '$interval', '$route', '$routeParams', 'Client', function ($scope, $location, $timeout, $interval, $route, $routeParams, Client) {
|
||||
Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
|
||||
@@ -116,6 +117,13 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
$scope.view = view;
|
||||
};
|
||||
|
||||
$scope.stopAppTask = function (taskId) {
|
||||
Client.stopTask(taskId, function (error) {
|
||||
// we can ignore a call trying to cancel an already done task
|
||||
if (error && error.statusCode !== 409) Client.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
$scope.postInstallMessage = {
|
||||
confirmed: false,
|
||||
openApp: false,
|
||||
@@ -173,7 +181,7 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
$scope.display.error = {};
|
||||
|
||||
// translate for tag-input
|
||||
$scope.display.tags = app.tags ? app.tags.join(',') : '';
|
||||
$scope.display.tags = app.tags ? app.tags.join(' ') : '';
|
||||
|
||||
$scope.display.label = $scope.app.label || '';
|
||||
$scope.display.icon = { data: null };
|
||||
@@ -204,7 +212,7 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
configureLabel(function (error) {
|
||||
if (error) return done(error);
|
||||
|
||||
var tags = $scope.display.tags.split(',').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; });
|
||||
var tags = $scope.display.tags.split(' ').map(function (t) { return t.trim(); }).filter(function (t) { return !!t; });
|
||||
|
||||
var configureTags = angular.equals(tags, $scope.app.tags) ? NOOP : Client.configureApp.bind(null, $scope.app.id, 'tags', { tags: tags });
|
||||
|
||||
@@ -1194,6 +1202,7 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
}
|
||||
|
||||
repairFunc(function (error) {
|
||||
$scope.repair.retryBusy = false;
|
||||
if (error) return Client.error(error);
|
||||
|
||||
$scope.repair.retryBusy = false;
|
||||
@@ -1201,13 +1210,6 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
});
|
||||
},
|
||||
|
||||
stopAppTask: function (taskId) {
|
||||
Client.stopTask(taskId, function (error) {
|
||||
// we can ignore a call trying to cancel an already done task
|
||||
if (error && error.statusCode !== 409) Client.error(error);
|
||||
});
|
||||
},
|
||||
|
||||
restartBusy: false,
|
||||
restartApp: function () {
|
||||
$scope.repair.restartBusy = true;
|
||||
@@ -1349,12 +1351,14 @@ angular.module('Application').controller('AppController', ['$scope', '$location'
|
||||
|
||||
$scope.app.taskProgress = task && task.percent ? task.percent : 5; // start with 5 to avoid empty progress bar
|
||||
$scope.app.taskProgressMessage = task ? task.message : '';
|
||||
$scope.app.taskMinutesActive = task ? moment.duration(moment.utc().diff(moment.utc(task.creationTime))).asMinutes() : 0;
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
$scope.app.taskProgress = 0;
|
||||
$scope.app.taskProgressMessage = '';
|
||||
$scope.app.taskMinutesActive = 0;
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
@@ -174,7 +174,14 @@
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 15px;">
|
||||
<p>Cloudron makes a complete backup of your system based on this configuration.</p>
|
||||
<p>Cloudron makes a complete backup of your system based on this configuration.
|
||||
<span ng-show="manualBackupApps.length">
|
||||
The following apps have automatic backups disabled:
|
||||
<span ng-repeat="app in manualBackupApps">
|
||||
<a ng-href="/#/app/{{app.id}}/backups">{{app.label || app.fqdn}}</a><span ng-hide="$last">,</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
|
||||
@@ -9,6 +9,8 @@ angular.module('Application').controller('BackupsController', ['$scope', '$locat
|
||||
$scope.config = Client.getConfig();
|
||||
$scope.user = Client.getUserInfo();
|
||||
|
||||
$scope.manualBackupApps = [];
|
||||
|
||||
$scope.backupConfig = {};
|
||||
$scope.lastBackup = null;
|
||||
$scope.backups = [];
|
||||
@@ -419,6 +421,8 @@ angular.module('Application').controller('BackupsController', ['$scope', '$locat
|
||||
fetchBackups();
|
||||
getBackupConfig();
|
||||
|
||||
$scope.manualBackupApps = Client.getInstalledApps().filter(function (app) { return !app.enableBackup; });
|
||||
|
||||
// show backup status
|
||||
$scope.createBackup.checkStatus();
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Choose Cloudron Avatar</h4>
|
||||
</div>
|
||||
<div class="modal-body settings-avatar-selector">
|
||||
<div class="modal-body branding-avatar-selector">
|
||||
<img id="previewAvatar" width="128" height="128" ng-src="{{ avatarChange.avatarUrl() }}"/>
|
||||
<input type="file" id="avatarFileInput" style="display: none" accept="image/png"/>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<div>
|
||||
<label class="control-label">Logo</label>
|
||||
</div>
|
||||
<div class="settings-avatar" ng-click="avatarChange.showChangeAvatar()">
|
||||
<div class="branding-avatar" ng-click="avatarChange.showChangeAvatar()">
|
||||
<img ng-src="{{ about.avatarUrl() }}"/>
|
||||
<div class="overlay"></div>
|
||||
</div>
|
||||
|
||||
@@ -196,7 +196,11 @@ angular.module('Application').controller('BrandingController', ['$scope', '$loca
|
||||
busy: false,
|
||||
|
||||
refresh: function () {
|
||||
$scope.footer.content = $scope.config.footer;
|
||||
Client.getFooter(function (error, result) {
|
||||
if (error) return console.error('Failed to get footer.', error);
|
||||
|
||||
$scope.footer.content = result;
|
||||
});
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
|
||||
@@ -318,6 +318,7 @@
|
||||
{{ mailbox.ownerDisplayName }}
|
||||
</td>
|
||||
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
|
||||
<!-- aliases is spaces separated, so it will wrap as needed -->
|
||||
{{ mailbox.aliases }}
|
||||
</td>
|
||||
<td class="hand" ng-click="mailboxes.edit.show(mailbox)">
|
||||
|
||||
+5
-9
@@ -299,14 +299,10 @@ angular.module('Application').controller('EmailController', ['$scope', '$locatio
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.mailboxes.add.reset();
|
||||
$scope.mailboxes.refresh(function (error) {
|
||||
if (error) return console.error(error);
|
||||
$scope.mailboxes.refresh();
|
||||
$scope.catchall.refresh();
|
||||
|
||||
$scope.catchall.refresh();
|
||||
|
||||
$('#mailboxAddModal').modal('hide');
|
||||
});
|
||||
$('#mailboxAddModal').modal('hide');
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -337,7 +333,7 @@ angular.module('Application').controller('EmailController', ['$scope', '$locatio
|
||||
return;
|
||||
}
|
||||
|
||||
var aliases = $scope.mailboxes.edit.aliases.split(',').map(function (a) { return a.trim(); }).filter(function (a) { return !!a; });
|
||||
var aliases = $scope.mailboxes.edit.aliases.split(' ').map(function (a) { return a.trim(); }).filter(function (a) { return !!a; });
|
||||
|
||||
Client.setAliases($scope.domain.domain, $scope.mailboxes.edit.name, aliases, function (error) {
|
||||
if (error) {
|
||||
@@ -398,7 +394,7 @@ angular.module('Application').controller('EmailController', ['$scope', '$locatio
|
||||
if (error) return callback(error);
|
||||
|
||||
$scope.mailboxes.mailboxes = mailboxes.map(function (m) {
|
||||
m.aliases = aliases.filter(function (a) { return a.aliasTarget === m.name; }).map(function (a) { return a.name; }).join(',');
|
||||
m.aliases = aliases.filter(function (a) { return a.aliasTarget === m.name; }).map(function (a) { return a.name; }).join(' ');
|
||||
m.owner = $scope.users.find(function (u) { return u.id === m.ownerId; }); // owner may not exist
|
||||
m.ownerDisplayName = m.owner ? m.owner.display : ''; // this meta property is set when we get the user list
|
||||
|
||||
|
||||
+17
-10
@@ -91,18 +91,25 @@
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-left">
|
||||
<h3>Event Log
|
||||
|
||||
<button class="btn btn-sm btn-default btn-outline pull-right" ng-click="activity.showNextPage()" ng-disabled="activity.busy || activity.perPage > activity.eventLogs.length">next <i class="fa fa-angle-double-right"></i></button>
|
||||
<button class="btn btn-sm btn-default btn-outline pull-right" ng-click="activity.showPrevPage()" ng-disabled="activity.busy || activity.currentPage <= 1"><i class="fa fa-angle-double-left"></i> prev</button>
|
||||
<button class="btn btn-sm btn-primary btn-outline pull-right" ng-click="activity.refresh()" ng-disabled="activity.busy"><i class="fa fa-sync"></i></button>
|
||||
|
||||
<input class="form-control pull-right" style="width: 200px;" placeholder="Search" type="text" ng-model="activity.search" ng-model-options="{ debounce: 1000 }" ng-change="activity.updateFilter()" />
|
||||
</h3>
|
||||
<div class="text-left" ng-show="user.role === 'owner'">
|
||||
<h3>Event Log</h3>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-bottom: 15px;">
|
||||
<div class="row" ng-show="user.role === 'owner'">
|
||||
<div class="col-md-12">
|
||||
<div class="maillog-filter">
|
||||
<input class="form-control" style="width: 200px;" placeholder="Search" type="text" ng-model="activity.search" ng-model-options="{ debounce: 1000 }" ng-change="activity.updateFilter()" />
|
||||
<multiselect ng-model="activity.selectedTypes" ms-header="All Events" options="a.name for a in activityTypes" data-multiple="true" ng-change="activity.updateFilter(true)" filter-after-rows="5" scroll-after-rows="10"></multiselect>
|
||||
<select class="form-control" ng-model="activity.pageItems" ng-options="a.name for a in pageItemCount" ng-change="activity.updateFilter(true)"></select>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button class="btn btn-default btn-outline" ng-click="activity.showPrevPage()" ng-disabled="activity.busy || activity.currentPage <= 1"><i class="fa fa-angle-double-left"></i> prev</button>
|
||||
<button class="btn btn-default btn-outline" ng-click="activity.showNextPage()" ng-disabled="activity.busy || activity.perPage > activity.eventLogs.length">next <i class="fa fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-large" style="margin-top: 10px; margin-bottom: 15px;" ng-show="user.role === 'owner'">
|
||||
<div class="row ng-hide" ng-hide="ready">
|
||||
<div class="col-lg-12 text-center">
|
||||
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
|
||||
+24
-4
@@ -11,18 +11,37 @@ angular.module('Application').controller('EmailsController', ['$scope', '$locati
|
||||
$scope.user = Client.getUserInfo();
|
||||
$scope.domains = [];
|
||||
|
||||
$scope.pageItemCount = [
|
||||
{ name: 'Show 20 per page', value: 20 },
|
||||
{ name: 'Show 50 per page', value: 50 },
|
||||
{ name: 'Show 100 per page', value: 100 }
|
||||
];
|
||||
|
||||
$scope.activityTypes = [
|
||||
{ name: 'Bounce', value: 'bounce' },
|
||||
{ name: 'Deferred', value: 'deferred' },
|
||||
{ name: 'Delivered', value: 'delivered' },
|
||||
{ name: 'Denied', value: 'denied' },
|
||||
{ name: 'Queued', value: 'queued' },
|
||||
{ name: 'Received', value: 'received' },
|
||||
];
|
||||
|
||||
$scope.activity = {
|
||||
busy: true,
|
||||
eventLogs: [],
|
||||
activeEventLog: null,
|
||||
currentPage: 1,
|
||||
perPage: 20,
|
||||
pageItems: $scope.pageItemCount[0],
|
||||
selectedTypes: [],
|
||||
search: '',
|
||||
|
||||
refresh: function () {
|
||||
$scope.activity.busy = true;
|
||||
|
||||
Client.getMailEventLogs($scope.activity.search, $scope.activity.currentPage, $scope.activity.perPage, function (error, result) {
|
||||
var types = $scope.activity.selectedTypes.map(function (a) { return a.value; }).join(',');
|
||||
|
||||
Client.getMailEventLogs($scope.activity.search, types, $scope.activity.currentPage, $scope.activity.pageItems.value, function (error, result) {
|
||||
if (error) return console.error('Failed to fetch mail eventlogs.', error);
|
||||
|
||||
$scope.activity.busy = false;
|
||||
@@ -47,8 +66,8 @@ angular.module('Application').controller('EmailsController', ['$scope', '$locati
|
||||
else $scope.activity.activeEventLog = eventLog;
|
||||
},
|
||||
|
||||
updateFilter: function () {
|
||||
$scope.activity.currentPage = 1;
|
||||
updateFilter: function (fresh) {
|
||||
if (fresh) $scope.activity.currentPage = 1;
|
||||
$scope.activity.refresh();
|
||||
}
|
||||
};
|
||||
@@ -141,7 +160,8 @@ angular.module('Application').controller('EmailsController', ['$scope', '$locati
|
||||
$scope.domains = domains;
|
||||
$scope.ready = true;
|
||||
|
||||
$scope.activity.refresh();
|
||||
if ($scope.user.role === 'owner') $scope.activity.refresh();
|
||||
|
||||
refreshDomainStatuses();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<div class="row" ng-if="errorMessage">
|
||||
<br>
|
||||
<div class="alert alert-danger text-center">
|
||||
<div class="alert alert-warning text-center">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ angular.module('Application').controller('GraphsController', ['$scope', '$locati
|
||||
|
||||
$scope.installedApps = Client.getInstalledApps();
|
||||
|
||||
// we use 1024 to match free -m output (which is not si units)
|
||||
function bytesToMegaBytes(value) {
|
||||
return (value/1024/1024).toFixed(2);
|
||||
}
|
||||
@@ -39,7 +40,7 @@ angular.module('Application').controller('GraphsController', ['$scope', '$locati
|
||||
}
|
||||
|
||||
$scope.setError = function (context, error) {
|
||||
$scope.errorMessage = 'Error loading ' + context + ' stats : ' + error.message + '. Try restarting the graphite service.';
|
||||
$scope.errorMessage = 'Error loading ' + context + ' : ' + error.message;
|
||||
};
|
||||
|
||||
$scope.setMemoryApp = function (app, color) {
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
<div class="grid-item-top">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<p>These passwords can be used as a security measure in desktop, email & mobile clients.</p>
|
||||
<p>App passwords are a security measure to protect your Cloudron user account. If you need to access a Cloudron app from an untrusted mobile app or client, you can log in with your username and the alternate password generated here.</p>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -417,6 +417,7 @@
|
||||
<div class="grid-item-top">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<p>Use these personal access tokens to authenticate to the <a target="_blank" href="https://cloudron.io/documentation/api/">Cloudron API</a></p>
|
||||
<table class="table table-hover" style="margin: 0;">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
+11
-12
@@ -399,15 +399,16 @@ angular.module('Application').controller('ProfileController', ['$scope', '$locat
|
||||
$scope.appPassword.identifiers = [];
|
||||
var appsById = {};
|
||||
$scope.apps.forEach(function (app) {
|
||||
// ignore apps without ldap or with email
|
||||
if (!app.manifest.addons || !app.manifest.addons.ldap || app.manifest.addons.email || !app.sso) return;
|
||||
if (!app.manifest.addons) return;
|
||||
var ftp = app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
|
||||
|
||||
// ignore apps without ftp and ldap or email
|
||||
if (!ftp && (!app.manifest.addons.ldap || app.manifest.addons.email || !app.sso)) return;
|
||||
|
||||
appsById[app.id] = app;
|
||||
if (app.label) {
|
||||
$scope.appPassword.identifiers.push({ id: app.id, label: app.label + ' (' + app.fqdn + ')' });
|
||||
} else {
|
||||
$scope.appPassword.identifiers.push({ id: app.id, label: app.fqdn });
|
||||
}
|
||||
var labelSuffix = ftp ? ' - SFTP' : '';
|
||||
var label = app.label ? app.label + ' (' + app.fqdn + ')' + labelSuffix : app.fqdn + labelSuffix;
|
||||
$scope.appPassword.identifiers.push({ id: app.id, label: label });
|
||||
});
|
||||
$scope.appPassword.identifiers.push({ id: 'mail', label: 'Mail client' });
|
||||
|
||||
@@ -417,11 +418,9 @@ angular.module('Application').controller('ProfileController', ['$scope', '$locat
|
||||
var app = appsById[password.identifier];
|
||||
if (!app) return password.label = password.identifier + ' (App not found)';
|
||||
|
||||
if (app.label) {
|
||||
password.label = app.label + ' (' + app.fqdn + ')';
|
||||
} else {
|
||||
password.label = app.fqdn;
|
||||
}
|
||||
var ftp = app.manifest.addons && app.manifest.addons.localstorage && app.manifest.addons.localstorage.ftp;
|
||||
var labelSuffix = ftp ? ' - SFTP' : '';
|
||||
password.label = app.label ? app.label + ' (' + app.fqdn + ')' + labelSuffix : app.fqdn + labelSuffix;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -221,22 +221,24 @@
|
||||
|
||||
<div class="row">
|
||||
<br/>
|
||||
<div class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div ng-show="update.busy" class="progress progress-striped active animateMe">
|
||||
<div ng-if="update.busy" class="col-md-12" style="margin-bottom: 10px;">
|
||||
<div class="progress progress-striped active animateMe">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" style="width: {{ update.percent }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-7">
|
||||
<p ng-show="update.busy">{{ update.message }}</p>
|
||||
<p ng-hide="update.busy">
|
||||
<div class="has-error" ng-show="update.errorMessage">{{ update.errorMessage }}</div>
|
||||
<p ng-if="update.busy">
|
||||
<div class="has-error" ng-show="update.errorMessage">
|
||||
{{ update.errorMessage }}. <a ng-class="warning" ng-href="/logs.html?taskId={{update.taskId}}" target="_blank">Show Logs</a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 text-right">
|
||||
<div class="col-md-5 text-right">
|
||||
<button class="btn btn-outline btn-primary pull-right" ng-click="autoUpdate.submit()" ng-disabled="autoUpdate.pattern === autoUpdate.currentPattern">
|
||||
<i class="fa fa-circle-notch fa-spin" ng-show="autoUpdate.busy"></i> Save
|
||||
</button>
|
||||
|
||||
@@ -230,7 +230,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
currentConfig: {},
|
||||
|
||||
reset: function () {
|
||||
$scope.cloudronNameChange.busy = false;
|
||||
$scope.registryConfig.busy = false;
|
||||
$scope.registryConfig.error = null;
|
||||
|
||||
$scope.registryConfig.serverAddress = $scope.registryConfig.currentConfig.serverAddress;
|
||||
|
||||
@@ -64,6 +64,13 @@
|
||||
</div>
|
||||
|
||||
<div class="card card-large">
|
||||
<div class="row" ng-if="errorMessage">
|
||||
<br>
|
||||
<div class="alert alert-warning text-center">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row ng-hide" ng-show="disks.length === 0">
|
||||
<div class="col-md-12 text-center">
|
||||
<h2><i class="fa fa-circle-notch fa-spin"></i></h2>
|
||||
@@ -71,7 +78,7 @@
|
||||
</div>
|
||||
<div class="row" ng-repeat="disk in disks" style="margin-bottom: 20px;">
|
||||
<div class="col-md-12">
|
||||
<h3>{{ disk.filesystem }} <small>mounted at</small> {{ disk.mountpoint }} <span class="pull-right small"><b>{{ disk.available | prettyDiskSize }}</b> of <b>{{ disk.size | prettyDiskSize }}</b> still available</span></h3>
|
||||
<h3>{{ disk.filesystem }} <small>mounted at</small> {{ disk.mountpoint }} <span class="pull-right small"><b>{{ disk.available | prettyDiskSize }}</b> of <b>{{ disk.size | prettyDiskSize }}</b> available</span></h3>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" ng-repeat="content in disk.contains" style="width: {{ content.usage / disk.size * 100 }}%; background-color: {{ content.color }};" uib-tooltip="{{ content.label + ' ' + (content.usage | prettyDiskSize) }}"></div>
|
||||
</div>
|
||||
|
||||
+22
-12
@@ -11,6 +11,11 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati
|
||||
$scope.services = [];
|
||||
$scope.disks = [];
|
||||
$scope.memory = null;
|
||||
$scope.errorMessage = '';
|
||||
|
||||
$scope.setError = function (context, error) {
|
||||
$scope.errorMessage = 'Error loading ' + context + ' : ' + error.message;
|
||||
};
|
||||
|
||||
// http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
|
||||
function getRandomColor() {
|
||||
@@ -153,6 +158,7 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati
|
||||
Client.disks(function (error, result) {
|
||||
if (error) return $scope.setError('disk', error);
|
||||
|
||||
// segregate locations into the correct disks based on 'filesystem'
|
||||
result.disks.forEach(function (disk, index) {
|
||||
disk.id = index;
|
||||
disk.contains = [];
|
||||
@@ -170,25 +176,26 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati
|
||||
});
|
||||
});
|
||||
|
||||
$scope.disks = result.disks;
|
||||
$scope.disks = result.disks; // [ { filesystem, type, size, used, available, capacity, mountpoint }]
|
||||
|
||||
// lazy fetch graphite data
|
||||
$scope.disks.forEach(function (disk) {
|
||||
// render data of each disk
|
||||
$scope.disks.forEach(function (disk, index) {
|
||||
// /dev/sda1 -> sda1
|
||||
// /dev/mapper/foo -> mapper_foo (see #348)
|
||||
var diskName = disk.filesystem.slice(disk.filesystem.indexOf('/', 1) + 1);
|
||||
diskName = diskName.replace(/\//g, '_');
|
||||
|
||||
// use collectd instead of df data so the timeframe matches with the du data
|
||||
Client.graphs([
|
||||
'absolute(collectd.localhost.df-' + diskName + '.df_complex-free)',
|
||||
'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)',
|
||||
'absolute(collectd.localhost.df-' + diskName + '.df_complex-reserved)', // reserved for root (default: 5%) tune2fs -l/m
|
||||
'absolute(collectd.localhost.df-' + diskName + '.df_complex-used)'
|
||||
], '-1min', {}, function (error, data) {
|
||||
if (error) return $scope.setError('disk', error);
|
||||
|
||||
disk.size = data[2].datapoints[0][0] + data[1].datapoints[0][0] + data[0].datapoints[0][0];
|
||||
disk.free = data[0].datapoints[0][0];
|
||||
disk.occupied = data[2].datapoints[0][0] + data[1].datapoints[0][0];
|
||||
disk.occupied = data[2].datapoints[0][0];
|
||||
|
||||
colorIndex = 0;
|
||||
disk.contains.forEach(function (content) {
|
||||
@@ -216,13 +223,16 @@ angular.module('Application').controller('SystemController', ['$scope', '$locati
|
||||
usageOther -= tmp;
|
||||
});
|
||||
|
||||
// add content container for other non tracked data
|
||||
disk.contains.push({
|
||||
label: 'Ubuntu',
|
||||
id: 'other',
|
||||
color: '#27CE65',
|
||||
usage: usageOther
|
||||
});
|
||||
if (index === 0) { // the root mount point is the first disk
|
||||
disk.contains.push({
|
||||
label: 'Everything else (Ubuntu, Swap, etc)',
|
||||
id: 'other',
|
||||
color: '#27CE65',
|
||||
usage: usageOther
|
||||
});
|
||||
}
|
||||
|
||||
disk.contains.sort(function (x, y) { return x.usage > y.usage; }); // sort by usage
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="filter">
|
||||
<div class="users-filter">
|
||||
<input type="text" class="form-control" style="min-width: 350px;" ng-model="userSearchString" ng-model-options="{ debounce: 1000 }" ng-change="updateFilter()" placeholder="Search"/>
|
||||
<select class="form-control" ng-model="pageItems" ng-options="a.name for a in pageItemCount" ng-change="updateFilter(true)"></select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user