Merge remote-tracking branch 'origin/master' into feature/gcs
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" ng-show="(appConfigure.app | installError)">Repair</h4>
|
||||
<h4 class="modal-title" ng-hide="(appConfigure.app | installError)">Configure</h4>
|
||||
<h4 class="modal-title" ng-show="(appConfigure.app | installError)">Repair {{ appConfigure.app.fqdn }}</h4>
|
||||
<h4 class="modal-title" ng-hide="(appConfigure.app | installError)">Configure {{ appConfigure.app.fqdn }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<fieldset>
|
||||
@@ -88,18 +88,26 @@
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="groups" ng-disabled="groups.length <= 1">
|
||||
Only allow the following user groups <span class="label label-danger" ng-show="appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()">Select at least one group</span>
|
||||
<input type="radio" ng-model="appConfigure.accessRestrictionOption" value="groups">
|
||||
Only allow the following users and groups <span class="label label-danger" ng-show="appConfigure.accessRestrictionOption === 'groups' && !appConfigure.isAccessRestrictionValid()">Select at least one user or group</span>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-show="groups.length <= 1" style="margin-left: 20px;">No groups available. <a href="#/users">Create groups</a></div>
|
||||
<div>
|
||||
<div style="margin-left: 20px;">
|
||||
<span ng-repeat="group in groups | ignoreAdminGroup">
|
||||
<button class="btn btn-default" type="button" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" ng-click="appConfigureToggleGroup(group);" ng-class="{ 'btn-primary': (appConfigure.accessRestriction.groups && appConfigure.accessRestriction.groups.indexOf(group.id) !== -1) }">{{ group.name }}</button>
|
||||
</span>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
Groups:
|
||||
<multiselect class="input-sm stretch" ng-model="appConfigure.accessRestriction.groups" ng-disabled="appConfigure.accessRestrictionOption !== 'groups'" options="group.name for group in (groups | ignoreAdminGroup)" data-multiple="true"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<p ng-show="!mailConfig.enabled && appConfigure.app.manifest.addons.email" class="text-danger">
|
||||
@@ -109,23 +117,19 @@
|
||||
<a href="" ng-click="appConfigure.advancedVisible = true" ng-hide="appConfigure.advancedVisible">Advanced settings...</a>
|
||||
<div uib-collapse="!appConfigure.advancedVisible">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="memoryLimit">Maximum Memory Limit: <b>{{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
|
||||
<label class="control-label" for="memoryLimit">Memory Limit <sup><a ng-href="{{ config.webServerOrigin }}/documentation/apps/#increasing-the-memory-limit-of-an-app" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup> : <b>{{ appConfigure.memoryLimit ? appConfigure.memoryLimit / 1024 / 1024 + 'MB' : 'Default (256 MB)' }}</b></label>
|
||||
<br/>
|
||||
<div style="padding: 0 10px;">
|
||||
<slider id="memoryLimit" ng-model="appConfigure.memoryLimit" step="134217728" tooltip="hide" ticks="appConfigure.memoryTicks" ticks-snap-bounds="67108864"></slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': !appConfigureForm.xFrameOptions.$dirty && appConfigure.error.xFrameOptions }">
|
||||
<label class="control-label">Allow embedding from the following site</label>
|
||||
<div class="control-label" ng-show="appConfigure.error.xFrameOptions"><small>Must be empty of a valid URL</small></div>
|
||||
<input type="text" class="form-control" id="appConfigureXFrameOptionsInput" name="xFrameOptions" placeholder="https://example.com" ng-model="appConfigure.xFrameOptions" uib-tooltip="Leave blank to not allow embedding">
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Specify robots.txt file content</label>
|
||||
<textarea ng-model="appConfigure.robotsTxt" placeholder="Leave empty to allow all bots to index this app." class="form-control" rows="3"></textarea>
|
||||
@@ -179,8 +183,11 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Restore {{ appRestore.app.fqdn }}</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="appRestore.backups.length === 0">
|
||||
<p class="text-danger">This app has no backups.</p>
|
||||
<div class="modal-body" ng-show="appRestore.busyFetching">
|
||||
<h4 class="text-center"><i class="fa fa-circle-o-notch fa-spin"></i> Fetching backups</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="appRestore.backups.length === 0 && !appRestore.busyFetching">
|
||||
<h4 class="text-danger">This app has no backups.</h4>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="appRestore.backups.length !== 0">
|
||||
<p>Restoring the app will lose all content generated since the backup.</p>
|
||||
@@ -195,7 +202,7 @@
|
||||
</div>
|
||||
<br/>
|
||||
<fieldset>
|
||||
<form role="form" name="appRestoreForm" ng-submit="doRestore()" autocomplete="off">
|
||||
<form role="form" name="appRestoreForm" ng-submit="appRestore.submit()" autocomplete="off">
|
||||
<div class="form-group" ng-class="{ 'has-error': (appRestoreForm.password.$dirty && appRestoreForm.password.$invalid) || (!appRestoreForm.password.$dirty && appRestore.error.password) }">
|
||||
<label class="control-label" for="appRestorePasswordInput">Provide your password to confirm this action</label>
|
||||
<div class="control-label" ng-show="(appRestoreForm.password.$dirty && appRestoreForm.password.$invalid) || (!appRestoreForm.password.$dirty && appRestore.error.password)">
|
||||
@@ -211,22 +218,28 @@
|
||||
</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="doRestore()" ng-disabled="appRestoreForm.$invalid || appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-o-notch fa-spin" ng-show="appRestore.busy"></i> Restore</button>
|
||||
<button type="button" class="btn btn-success" ng-click="appRestore.submit()" ng-show="appRestore.backups.length !== 0" ng-disabled="appRestoreForm.$invalid || appRestore.busy || !appRestore.selectedBackup"><i class="fa fa-circle-o-notch fa-spin" ng-show="appRestore.busy"></i> Restore</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal post install message app -->
|
||||
<div class="modal fade" id="appPostInstallModal" tabindex="-1" role="dialog">
|
||||
<!-- Modal information of app -->
|
||||
<div class="modal fade" id="appInfoModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Installation notes for {{ appPostInstall.app.manifest.title }}</h4>
|
||||
<img ng-src="{{appInfo.app.iconUrl}}" onerror="this.onerror=null;this.src='img/appicon_fallback.png'" class="app-info-icon"/>
|
||||
<h5 class="app-info-title">{{ appInfo.app.manifest.title }}</h5>
|
||||
<br/>
|
||||
<span class="app-info-meta">Package version <a ng-href="/#/appstore/{{appInfo.app.manifest.id}}?version={{appInfo.app.manifest.version}}">{{ appInfo.app.manifest.version }}</a> </span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="app-postinstall-message">
|
||||
<div ng-bind-html="appPostInstall.message | postInstallMessage:appPostInstall.app | markdown2html"></div>
|
||||
<div class="app-postinstall-message" ng-hide="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
|
||||
This package has no special usage information.
|
||||
</div>
|
||||
<div class="app-postinstall-message" ng-show="appInfo.app.manifest && appInfo.app.manifest.postInstallMessage">
|
||||
<div ng-bind-html="appInfo.message | postInstallMessage:appInfo.app | markdown2html"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -295,34 +308,11 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Recent Changes for new version <b>{{ appUpdate.manifest.version}}</b>:</p>
|
||||
<pre>{{ appUpdate.manifest.changelog }}</pre>
|
||||
<br/>
|
||||
<fieldset>
|
||||
<form role="form" name="appUpdateForm" ng-submit="doUpdate(appUpdateForm)" autocomplete="off">
|
||||
<div ng-repeat="(env, info) in appUpdate.portBindingsInfo" ng-class="{ 'newPort': info.isNew }">
|
||||
<ng-form name="portInfo_form">
|
||||
<div class="form-group" ng-class="{ 'has-error': portInfo_form.itemName{{$index}}.$dirty && portInfo_form.itemName{{$index}}.$invalid }">
|
||||
<label class="control-label" for="inputPortInfo{{env}}"><input type="checkbox" ng-model="appUpdate.portBindingsEnabled[env]"> <span ng-show="info.isNew">New - </span> {{ info.description }} ({{ HOST_PORT_MIN }} - {{ HOST_PORT_MAX }})</label>
|
||||
<input type="number" class="form-control" ng-model="appUpdate.portBindings[env]" ng-disabled="!appUpdate.portBindingsEnabled[env]" id="inputPortInfo{{env}}" later-name="itemName{{$index}}" min="{{HOST_PORT_MIN}}" max="{{HOST_PORT_MAX}}" required>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
<div ng-repeat="(env, port) in appUpdate.obsoletePortBindings" class="obsoletePort">
|
||||
<ng-form name="obsoletePortInfo_form">
|
||||
<div class="form-group">
|
||||
Obsolete -
|
||||
<label class="control-label">{{ env }}</label>
|
||||
<input type="number" class="form-control" ng-model="port" disabled>
|
||||
</div>
|
||||
</ng-form>
|
||||
</div>
|
||||
<input class="ng-hide" type="submit" ng-disabled="appUpdateForm.$invalid || busy"/>
|
||||
</form>
|
||||
</fieldset>
|
||||
<div ng-bind-html="appUpdate.manifest.changelog | markdown2html"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="doUpdate(appUpdateForm)" ng-disabled="appUpdateForm.$invalid || appUpdate.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="appUpdate.busy"></i> Update</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="doUpdate()" ng-disabled="appUpdate.busy"><i class="fa fa-circle-o-notch fa-spin" ng-show="appUpdate.busy"></i> Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -401,7 +391,7 @@
|
||||
<div class="grid-item-bottom-mobile" ng-show="user.admin">
|
||||
<div class="row">
|
||||
<div class="col-xs-4 text-left">
|
||||
<a href="" ng-click="showRestore(app)" ng-show="backupConfig.provider !== 'noop'">
|
||||
<a href="" ng-click="appRestore.show(app)" ng-show="backupConfig.provider !== 'noop'">
|
||||
<i class="fa fa-undo scale"></i>
|
||||
</a>
|
||||
|
||||
@@ -424,7 +414,7 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="" ng-click="showRestore(app)" ng-show="backupConfig.provider !== 'noop'" title="Restore App"><i class="fa fa-undo scale"></i></a>
|
||||
<a href="" ng-click="appRestore.show(app)" ng-show="backupConfig.provider !== 'noop'" title="Restore App"><i class="fa fa-undo scale"></i></a>
|
||||
</div>
|
||||
|
||||
<div ng-show="(app.installationState === 'installed' || app.installationState === 'pending_configure') && !(app | installError)">
|
||||
@@ -435,8 +425,8 @@
|
||||
<a href="" ng-click="showConfigure(app)" title="Repair App"><i class="fa fa-wrench scale"></i></a>
|
||||
</div>
|
||||
|
||||
<div ng-show="hasPostInstallMessage(app)">
|
||||
<a href="" ng-click="showPostInstall(app)" title="Information"><i class="fa fa-info-circle scale"></i></a>
|
||||
<div>
|
||||
<a href="" ng-click="showInformation(app)" title="Information"><i class="fa fa-info-circle scale"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+77
-150
@@ -63,6 +63,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
|
||||
$scope.appRestore = {
|
||||
busy: false,
|
||||
busyFetching: false,
|
||||
error: {},
|
||||
app: {},
|
||||
password: '',
|
||||
@@ -71,10 +72,51 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
|
||||
selectBackup: function (backup) {
|
||||
$scope.appRestore.selectedBackup = backup;
|
||||
},
|
||||
|
||||
show: function (app) {
|
||||
$scope.reset();
|
||||
|
||||
$scope.appRestore.app = app;
|
||||
$scope.appRestore.busyFetching = true;
|
||||
|
||||
$('#appRestoreModal').modal('show');
|
||||
|
||||
Client.getAppBackups(app.id, function (error, backups) {
|
||||
if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$scope.appRestore.backups = backups;
|
||||
if (backups.length) $scope.appRestore.selectedBackup = backups[0]; // pre-select first backup
|
||||
$scope.appRestore.busyFetching = false;
|
||||
}
|
||||
});
|
||||
|
||||
return false; // prevent propagation and default
|
||||
},
|
||||
|
||||
submit: function () {
|
||||
$scope.appRestore.busy = true;
|
||||
$scope.appRestore.error.password = null;
|
||||
|
||||
Client.restoreApp($scope.appRestore.app.id, $scope.appRestore.selectedBackup.id, $scope.appRestore.password, function (error) {
|
||||
if (error && error.statusCode === 403) {
|
||||
$scope.appRestore.password = '';
|
||||
$scope.appRestore.error.password = true;
|
||||
$scope.appRestoreForm.password.$setPristine();
|
||||
$('#appRestorePasswordInput').focus();
|
||||
} else if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$('#appRestoreModal').modal('hide');
|
||||
}
|
||||
|
||||
$scope.appRestore.busy = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.appPostInstall = {
|
||||
$scope.appInfo = {
|
||||
app: {},
|
||||
message: ''
|
||||
};
|
||||
@@ -97,7 +139,7 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
$('#appConfigureModal').modal('hide');
|
||||
$('#appRestoreModal').modal('hide');
|
||||
$('#appUpdateModal').modal('hide');
|
||||
$('#appPostInstallModal').modal('hide');
|
||||
$('#appInfoModal').modal('hide');
|
||||
$('#appUninstallModal').modal('hide');
|
||||
|
||||
// reset configure dialog
|
||||
@@ -136,10 +178,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
$scope.appUpdate.error = {};
|
||||
$scope.appUpdate.app = {};
|
||||
$scope.appUpdate.manifest = {};
|
||||
$scope.appUpdate.portBindings = {};
|
||||
|
||||
$scope.appUpdateForm.$setPristine();
|
||||
$scope.appUpdateForm.$setUntouched();
|
||||
|
||||
// reset restore dialog
|
||||
$scope.appRestore.error = {};
|
||||
@@ -180,14 +218,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
});
|
||||
};
|
||||
|
||||
$scope.appConfigureToggleGroup = function (group) {
|
||||
var groups = $scope.appConfigure.accessRestriction.groups;
|
||||
var pos = groups.indexOf(group.id);
|
||||
|
||||
if (pos === -1) groups.push(group.id);
|
||||
else groups.splice(pos, 1);
|
||||
};
|
||||
|
||||
$scope.useAltDomain = function (use) {
|
||||
$scope.appConfigure.usingAltDomain = use;
|
||||
|
||||
@@ -206,27 +236,36 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
$scope.appConfigure.location = app.altDomain || app.location;
|
||||
$scope.appConfigure.usingAltDomain = !!app.altDomain;
|
||||
$scope.appConfigure.portBindingsInfo = app.manifest.tcpPorts || {}; // Portbinding map only for information
|
||||
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
|
||||
$scope.appConfigure.accessRestriction = app.accessRestriction || { users: [], groups: [] };
|
||||
$scope. Option = app.accessRestriction ? 'groups' : 'any';
|
||||
$scope.appConfigure.memoryLimit = app.memoryLimit || app.manifest.memoryLimit || (256 * 1024 * 1024);
|
||||
$scope.appConfigure.xFrameOptions = app.xFrameOptions.indexOf('ALLOW-FROM') === 0 ? app.xFrameOptions.split(' ')[1] : '';
|
||||
$scope.appConfigure.customAuth = !(app.manifest.addons['ldap'] || app.manifest.addons['oauth']);
|
||||
$scope.appConfigure.robotsTxt = app.robotsTxt;
|
||||
$scope.appConfigure.enableBackup = app.enableBackup;
|
||||
|
||||
// create ticks starting from manifest memory limit
|
||||
$scope.appConfigure.memoryTicks = [
|
||||
256 * 1024 * 1024,
|
||||
512 * 1024 * 1024,
|
||||
1024 * 1024 * 1024,
|
||||
2048 * 1024 * 1024,
|
||||
4096 * 1024 * 1024
|
||||
].filter(function (t) { return t >= (app.manifest.memoryLimit || 0); });
|
||||
// create ticks starting from manifest memory limit. the memory limit here is currently split into ram+swap (and thus *2 below)
|
||||
// TODO: the *2 will overallocate since 4GB is max swap that cloudron itself allocates
|
||||
$scope.appConfigure.memoryTicks = [ ];
|
||||
var npow2 = Math.pow(2, Math.ceil(Math.log($scope.config.memory)/Math.log(2)));
|
||||
for (var i = 256; i <= (npow2*2/1024/1024); i *= 2) {
|
||||
if (i >= (app.manifest.memoryLimit/1024/1024 || 0)) $scope.appConfigure.memoryTicks.push(i * 1024 * 1024);
|
||||
}
|
||||
if (app.manifest.memoryLimit && $scope.appConfigure.memoryTicks[0] !== app.manifest.memoryLimit) {
|
||||
$scope.appConfigure.memoryTicks.unshift(app.manifest.memoryLimit);
|
||||
}
|
||||
|
||||
$scope.appConfigure.accessRestrictionOption = app.accessRestriction ? 'groups' : 'any';
|
||||
$scope.appConfigure.accessRestriction = { users: [], groups: [] };
|
||||
|
||||
if (app.accessRestriction) {
|
||||
var userSet = { };
|
||||
app.accessRestriction.users.forEach(function (uid) { userSet[uid] = true; });
|
||||
$scope.users.forEach(function (u) { if (userSet[u.id] === true) $scope.appConfigure.accessRestriction.users.push(u); });
|
||||
|
||||
var groupSet = { };
|
||||
app.accessRestriction.groups.forEach(function (gid) { groupSet[gid] = true; });
|
||||
$scope.groups.forEach(function (g) { if (groupSet[g.id] === true) $scope.appConfigure.accessRestriction.groups.push(g); });
|
||||
}
|
||||
|
||||
// fill the portBinding structures. There might be holes in the app.portBindings, which signalizes a disabled port
|
||||
for (var env in $scope.appConfigure.portBindingsInfo) {
|
||||
@@ -256,11 +295,18 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
}
|
||||
}
|
||||
|
||||
var finalAccessRestriction = null;
|
||||
if ($scope.appConfigure.accessRestrictionOption === 'groups') {
|
||||
finalAccessRestriction = { users: [], groups: [] };
|
||||
finalAccessRestriction.users = $scope.appConfigure.accessRestriction.users.map(function (u) { return u.id; });
|
||||
finalAccessRestriction.groups = $scope.appConfigure.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
var data = {
|
||||
location: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.app.location : $scope.appConfigure.location,
|
||||
altDomain: $scope.appConfigure.usingAltDomain ? $scope.appConfigure.location : null,
|
||||
portBindings: finalPortBindings,
|
||||
accessRestriction: $scope.appConfigure.accessRestrictionOption === 'groups' ? $scope.appConfigure.accessRestriction : null,
|
||||
accessRestriction: finalAccessRestriction,
|
||||
cert: $scope.appConfigure.certificateFile,
|
||||
key: $scope.appConfigure.keyFile,
|
||||
xFrameOptions: $scope.appConfigure.xFrameOptions ? ('ALLOW-FROM ' + $scope.appConfigure.xFrameOptions) : 'SAMEORIGIN',
|
||||
@@ -303,13 +349,13 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showPostInstall = function (app) {
|
||||
$scope.showInformation = function (app) {
|
||||
$scope.reset();
|
||||
|
||||
$scope.appPostInstall.app = app;
|
||||
$scope.appPostInstall.message = app.manifest.postInstallMessage;
|
||||
$scope.appInfo.app = app;
|
||||
$scope.appInfo.message = app.manifest.postInstallMessage;
|
||||
|
||||
$('#appPostInstallModal').modal('show');
|
||||
$('#appInfoModal').modal('show');
|
||||
|
||||
return false; // prevent propagation and default
|
||||
};
|
||||
@@ -324,48 +370,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
return false; // prevent propagation and default
|
||||
};
|
||||
|
||||
$scope.showRestore = function (app) {
|
||||
$scope.reset();
|
||||
|
||||
$scope.appRestore.app = app;
|
||||
$scope.appRestore.busy = true;
|
||||
|
||||
$('#appRestoreModal').modal('show');
|
||||
|
||||
Client.getAppBackups(app.id, function (error, backups) {
|
||||
if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$scope.appRestore.backups = backups;
|
||||
if (backups.length) $scope.appRestore.selectedBackup = backups[0]; // pre-select first backup
|
||||
$scope.appRestore.busy = false;
|
||||
}
|
||||
});
|
||||
|
||||
return false; // prevent propagation and default
|
||||
};
|
||||
|
||||
$scope.doRestore = function () {
|
||||
$scope.appRestore.busy = true;
|
||||
$scope.appRestore.error.password = null;
|
||||
|
||||
Client.restoreApp($scope.appRestore.app.id, $scope.appRestore.selectedBackup.id, $scope.appRestore.password, function (error) {
|
||||
if (error && error.statusCode === 403) {
|
||||
$scope.appRestore.password = '';
|
||||
$scope.appRestore.error.password = true;
|
||||
$scope.appRestoreForm.password.$setPristine();
|
||||
$('#appRestorePasswordInput').focus();
|
||||
} else if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$('#appRestoreModal').modal('hide');
|
||||
$scope.reset();
|
||||
}
|
||||
|
||||
$scope.appRestore.busy = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showUninstall = function (app) {
|
||||
$scope.reset();
|
||||
|
||||
@@ -408,90 +412,17 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
$scope.appUpdate.app = app;
|
||||
$scope.appUpdate.manifest = angular.copy(updateManifest);
|
||||
|
||||
// ensure we always operate on objects here
|
||||
app.portBindings = app.portBindings || {};
|
||||
app.manifest.tcpPorts = app.manifest.tcpPorts || {};
|
||||
updateManifest.tcpPorts = updateManifest.tcpPorts || {};
|
||||
|
||||
// Activate below two lines for testing the UI
|
||||
// updateManifest.tcpPorts['TEST_HTTP'] = { defaultValue: 1337, description: 'HTTP server'};
|
||||
// app.manifest.tcpPorts['TEST_FOOBAR'] = { defaultValue: 1338, description: 'FOOBAR server'};
|
||||
// app.portBindings['TEST_SSH'] = 1339;
|
||||
|
||||
var portBindingsInfo = {}; // Portbinding map only for information
|
||||
var portBindings = {}; // This is the actual model holding the env:port pair
|
||||
var portBindingsEnabled = {}; // This is the actual model holding the enabled/disabled flag
|
||||
var obsoletePortBindings = {}; // Info map for obsolete port bindings, this is for display use only and thus not in the model
|
||||
var portsChanged = false;
|
||||
var env;
|
||||
|
||||
// detect new portbindings and copy all from manifest.tcpPorts
|
||||
for (env in updateManifest.tcpPorts) {
|
||||
portBindingsInfo[env] = updateManifest.tcpPorts[env];
|
||||
if (!app.manifest.tcpPorts[env]) {
|
||||
portBindingsInfo[env].isNew = true;
|
||||
portBindingsEnabled[env] = true;
|
||||
|
||||
// use default integer port value in model
|
||||
portBindings[env] = updateManifest.tcpPorts[env].defaultValue || 0;
|
||||
|
||||
portsChanged = true;
|
||||
} else {
|
||||
// detect if the port binding was enabled
|
||||
if (app.portBindings[env]) {
|
||||
portBindings[env] = app.portBindings[env];
|
||||
portBindingsEnabled[env] = true;
|
||||
} else {
|
||||
portBindings[env] = updateManifest.tcpPorts[env].defaultValue || 0;
|
||||
portBindingsEnabled[env] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detect obsolete portbindings (mappings in app.portBindings, but not anymore in updateManifest.tcpPorts)
|
||||
for (env in app.manifest.tcpPorts) {
|
||||
// only list the port if it is not in the new manifest and was enabled previously
|
||||
if (!updateManifest.tcpPorts[env] && app.portBindings[env]) {
|
||||
obsoletePortBindings[env] = app.portBindings[env];
|
||||
portsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// now inject the maps into the $scope, we only show those if ports have changed
|
||||
$scope.appUpdate.portBindings = portBindings; // always inject the model, so it gets used in the actual update call
|
||||
$scope.appUpdate.portBindingsEnabled = portBindingsEnabled; // always inject the model, so it gets used in the actual update call
|
||||
|
||||
if (portsChanged) {
|
||||
$scope.appUpdate.portBindingsInfo = portBindingsInfo;
|
||||
$scope.appUpdate.obsoletePortBindings = obsoletePortBindings;
|
||||
} else {
|
||||
$scope.appUpdate.portBindingsInfo = {};
|
||||
$scope.appUpdate.obsoletePortBindings = {};
|
||||
}
|
||||
|
||||
$('#appUpdateModal').modal('show');
|
||||
};
|
||||
|
||||
$scope.doUpdate = function (form) {
|
||||
$scope.doUpdate = function () {
|
||||
$scope.appUpdate.busy = true;
|
||||
|
||||
// only use enabled ports from portBindings
|
||||
var finalPortBindings = {};
|
||||
for (var env in $scope.appUpdate.portBindings) {
|
||||
if ($scope.appUpdate.portBindingsEnabled[env]) {
|
||||
finalPortBindings[env] = $scope.appUpdate.portBindings[env];
|
||||
}
|
||||
}
|
||||
|
||||
Client.updateApp($scope.appUpdate.app.id, $scope.appUpdate.manifest, finalPortBindings, function (error) {
|
||||
Client.updateApp($scope.appUpdate.app.id, $scope.appUpdate.manifest, function (error) {
|
||||
if (error) {
|
||||
Client.error(error);
|
||||
} else {
|
||||
$scope.appUpdate.app = {};
|
||||
|
||||
form.$setPristine();
|
||||
form.$setUntouched();
|
||||
|
||||
$('#appUpdateModal').modal('hide');
|
||||
}
|
||||
|
||||
@@ -512,10 +443,6 @@ angular.module('Application').controller('AppsController', ['$scope', '$location
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
$scope.hasPostInstallMessage = function (app) {
|
||||
return app.manifest && app.manifest.postInstallMessage;
|
||||
};
|
||||
|
||||
function fetchUsers() {
|
||||
Client.getUsers(function (error, users) {
|
||||
if (error) {
|
||||
|
||||
@@ -72,18 +72,26 @@
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups" ng-disabled="groups.length <= 1">
|
||||
Only allow the following user groups <span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">Select at least one group</span>
|
||||
<input type="radio" ng-model="appInstall.accessRestrictionOption" value="groups">
|
||||
Only allow the following users and groups <span class="label label-danger" ng-show="appInstall.accessRestrictionOption === 'groups' && !appInstall.isAccessRestrictionValid()">Select at least one user or group</span>
|
||||
</label>
|
||||
</div>
|
||||
<div ng-show="groups.length <= 1" style="margin-left: 20px;">No groups available. <a href="" ng-click="showView('/users')">Create groups</a></div>
|
||||
<div>
|
||||
<div style="margin-left: 20px;">
|
||||
<span ng-repeat="group in groups | ignoreAdminGroup">
|
||||
<button class="btn btn-default" type="button" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" ng-click="appInstall.toggleGroup(group);" ng-class="{ 'btn-primary': (appInstall.accessRestriction.groups && appInstall.accessRestriction.groups.indexOf(group.id) !== -1) }">{{ group.name }}</button>
|
||||
</span>
|
||||
<div class="col-md-5">
|
||||
Users:
|
||||
<multiselect ng-model="appInstall.accessRestriction.users" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="user.username for user in users" data-multiple="true"></multiselect>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
Groups:
|
||||
<multiselect ng-model="appInstall.accessRestriction.groups" ng-disabled="appInstall.accessRestrictionOption !== 'groups'" options="group.name for group in (groups | ignoreAdminGroup)" data-multiple="true"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<p ng-show="!mailConfig.enabled && appInstall.app.manifest.addons.email" class="text-danger">
|
||||
|
||||
@@ -53,14 +53,6 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
return !!(tmp.users.length || tmp.groups.length);
|
||||
},
|
||||
|
||||
toggleGroup: function (group) {
|
||||
var groups = $scope.appInstall.accessRestriction.groups;
|
||||
var pos = groups.indexOf(group.id);
|
||||
|
||||
if (pos === -1) groups.push(group.id);
|
||||
else groups.splice(pos, 1);
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
$scope.appInstall.app = {};
|
||||
$scope.appInstall.error = {};
|
||||
@@ -144,13 +136,17 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
}
|
||||
}
|
||||
|
||||
// translate to accessRestriction object
|
||||
var accessRestriction = $scope.appInstall.accessRestrictionOption === 'groups' ? $scope.appInstall.accessRestriction : null;
|
||||
var finalAccessRestriction = null;
|
||||
if ($scope.appInstall.accessRestrictionOption === 'groups') {
|
||||
finalAccessRestriction = { users: [], groups: [] };
|
||||
finalAccessRestriction.users = $scope.appInstall.accessRestriction.users.map(function (u) { return u.id; });
|
||||
finalAccessRestriction.groups = $scope.appInstall.accessRestriction.groups.map(function (g) { return g.id; });
|
||||
}
|
||||
|
||||
var data = {
|
||||
location: $scope.appInstall.location || '',
|
||||
portBindings: finalPortBindings,
|
||||
accessRestriction: accessRestriction,
|
||||
accessRestriction: finalAccessRestriction,
|
||||
cert: $scope.appInstall.certificateFile,
|
||||
key: $scope.appInstall.keyFile,
|
||||
sso: !$scope.appInstall.optionalSso ? undefined : ($scope.appInstall.accessRestrictionOption !== 'nosso')
|
||||
@@ -353,18 +349,6 @@ angular.module('Application').controller('AppStoreController', ['$scope', '$loca
|
||||
});
|
||||
|
||||
return callback(null, apps);
|
||||
|
||||
// Client.getNonApprovedApps(function (error, result) {
|
||||
// if (error) return callback(error);
|
||||
|
||||
// // add testing tag to the manifest for UI and search reasons
|
||||
// result.forEach(function (app) {
|
||||
// if (!app.manifest.tags) app.manifest.tags = [];
|
||||
// app.manifest.tags.push('testing');
|
||||
// });
|
||||
|
||||
// callback(null, apps.concat(result));
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="form-group" ng-class="{ 'has-error': false }" ng-show="dnsCredentials.provider === 'gcdns'">
|
||||
<div class="input-group">
|
||||
<input type="file" id="gcdnsKeyFileInput" style="display:none"/>
|
||||
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="dnsCredentials.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="dnsCredentials.busy" required>
|
||||
<input type="text" class="form-control" placeholder="Service Account Key" ng-model="dnsCredentials.gcdnsKey.keyFileName" id="gcdnsKeyInput" name="cert" onclick="getElementById('gcdnsKeyFileInput').click();" style="cursor: pointer;" ng-disabled="dnsCredentials.busy" ng-required="dnsCredentials.provider === 'gcdns'">
|
||||
<span class="input-group-addon">
|
||||
<i class="fa fa-upload" onclick="getElementById('gcdnsKeyFileInput').click();"></i>
|
||||
</span>
|
||||
@@ -90,7 +90,7 @@
|
||||
</p>
|
||||
|
||||
<p ng-show="dnsCredentials.provider === 'manual'">
|
||||
Setup an <i>A</i> record for <b>my.{{ dnsCredentials.customDomain || 'example.com' }}</b> to this server's IP. All DNS records have to be setup manually <i>before</i> each app installation.
|
||||
Setup an <i>A</i> record for <b>{{ config.adminLocation }}.{{ dnsCredentials.customDomain || 'example.com' }}</b> to this server's IP. All DNS records have to be setup manually <i>before</i> each app installation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer ">
|
||||
|
||||
@@ -65,9 +65,9 @@
|
||||
<a href="" data-toggle="collapse" data-parent="#accordion" data-target="#mail_settings">Mail server settings for email clients</a>
|
||||
<div id="mail_settings" class="panel-collapse collapse">
|
||||
<br/>
|
||||
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 993 (TLS)</p>
|
||||
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 587 (STARTTLS)</p>
|
||||
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>my.{{config.fqdn}}</span><br/>Port: 4190 (TLS)</p>
|
||||
<p><b>Incoming Mail (IMAP)</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 993 (TLS)</p>
|
||||
<p><b>Outgoing Mail (SMTP)</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 587 (STARTTLS)</p>
|
||||
<p><b>ManageSieve</b><br/>Server: <span ng-click-select>{{config.mailFqdn}}</span><br/>Port: 4190 (TLS)</p>
|
||||
<p>All the servers require your Cloudron credentials for authentication.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -142,10 +142,18 @@
|
||||
<input type="text" class="form-control" ng-model="configureBackup.backupFolder" id="inputConfigureBackupFolder" name="backupFolder" ng-disabled="configureBackup.busy" placeholder="Directory for backups" ng-required="configureBackup.provider === 'filesystem'">
|
||||
|
||||
<p class="has-error" ng-show="configureBackup.provider === 'filesystem'">
|
||||
Please ensure that the backup directory is an external disk
|
||||
Please ensure that the backup directory is an external ext4 disk
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="checkbox" ng-show="configureBackup.provider === 'filesystem'">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="configureBackup.useHardlinks" id="inputConfigureUseHardlinks">
|
||||
Use hardlinks
|
||||
</input>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- S3/Minio/SOS/GCS -->
|
||||
<div class="form-group" ng-class="{ 'has-error': configureBackup.error.endpoint }" ng-show="configureBackup.provider === 'minio' || configureBackup.provider === 's3-v4-compat'">
|
||||
<label class="control-label" for="inputConfigureBackupEndpoint">Endpoint</label>
|
||||
|
||||
@@ -345,6 +345,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
backupFolder: '',
|
||||
retentionSecs: 7 * 24 * 60 * 60,
|
||||
acceptSelfSignedCerts: false,
|
||||
useHardlinks: true,
|
||||
format: 'tgz',
|
||||
|
||||
clearForm: function () {
|
||||
@@ -360,6 +361,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
$scope.configureBackup.retentionSecs = 7 * 24 * 60 * 60;
|
||||
$scope.configureBackup.format = 'tgz';
|
||||
$scope.configureBackup.acceptSelfSignedCerts = false;
|
||||
$scope.configureBackup.useHardlinks = true;
|
||||
},
|
||||
|
||||
show: function () {
|
||||
@@ -385,6 +387,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
$scope.configureBackup.retentionSecs = $scope.backupConfig.retentionSecs;
|
||||
$scope.configureBackup.format = $scope.backupConfig.format;
|
||||
$scope.configureBackup.acceptSelfSignedCerts = !!$scope.backupConfig.acceptSelfSignedCerts;
|
||||
$scope.configureBackup.useHardlinks = !$scope.backupConfig.noHardlinks;
|
||||
|
||||
$('#configureBackupModal').modal('show');
|
||||
},
|
||||
@@ -443,6 +446,7 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
}
|
||||
} else if (backupConfig.provider === 'filesystem') {
|
||||
backupConfig.backupFolder = $scope.configureBackup.backupFolder;
|
||||
backupConfig.noHardlinks = !$scope.configureBackup.useHardlinks;
|
||||
}
|
||||
|
||||
Client.setBackupConfig(backupConfig, function (error) {
|
||||
@@ -559,8 +563,8 @@ angular.module('Application').controller('SettingsController', ['$scope', '$loca
|
||||
|
||||
$scope.backupConfig = backupConfig;
|
||||
|
||||
// Check if a proper storage backend is configured
|
||||
if (backupConfig.provider === 'filesystem') {
|
||||
// Check if a proper storage backend is configured. TODO: this check fails if /var/backups is actually external
|
||||
if (backupConfig.provider === 'filesystem' && backupConfig.backupFolder === '/var/backups') {
|
||||
var actionScope = $scope.$new(true);
|
||||
actionScope.action = '/#/settings';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user