1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 15:25:22 +02:00

chore(project): add prettier for code format (#3645)

* chore(project): install prettier and lint-staged

* chore(project): apply prettier to html too

* chore(project): git ignore eslintcache

* chore(project): add a comment about format script

* chore(prettier): update printWidth

* chore(prettier): remove useTabs option

* chore(prettier): add HTML validation

* refactor(prettier): fix closing tags

* feat(prettier): define angular parser for html templates

* style(prettier): run prettier on codebase

Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>
This commit is contained in:
Chaim Lev-Ari 2020-04-11 00:54:53 +03:00 committed by GitHub
parent 6663073be1
commit cf5056d9c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
714 changed files with 31228 additions and 28305 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,35 @@
<div class="datatable">
<rd-widget>
<rd-widget-header icon="{{$ctrl.titleIcon}}" title-text="{{ $ctrl.titleText }}">
</rd-widget-header>
<rd-widget-header icon="{{ $ctrl.titleIcon }}" title-text="{{ $ctrl.titleText }}"> </rd-widget-header>
<rd-widget-body classes="no-padding">
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>inherited</code> are inherited from the group access. They cannot be
removed or modified at the endpoint level but they can be overriden.
</div>
<div class="toolBar small" ng-if="$ctrl.inheritFrom">
Access tagged as <code>override</code> are overriding the group access for the related users/teams.
Access tagged as <code>inherited</code> are inherited from the group access. They cannot be removed or modified at the endpoint level but they can be overriden.
</div>
<div class="toolBar small" ng-if="$ctrl.inheritFrom"> Access tagged as <code>override</code> are overriding the group access for the related users/teams. </div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button ng-if="$ctrl.rbacEnabled" type="button" class="btn btn-sm btn-primary" ng-disabled="($ctrl.dataset | filter:{ Updated: true}).length === 0 "
ng-click="$ctrl.updateAction()">
<button
ng-if="$ctrl.rbacEnabled"
type="button"
class="btn btn-sm btn-primary"
ng-disabled="($ctrl.dataset | filter:{ Updated: true}).length === 0 "
ng-click="$ctrl.updateAction()"
>
<i class="fa fa-check space-right" aria-hidden="true"></i>Update
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..." ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -31,34 +37,27 @@
<tr>
<th>
<span class="md-checkbox">
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll"
ng-change="$ctrl.selectAll()" />
<input id="select_all" type="checkbox" ng-model="$ctrl.state.selectAll" ng-change="$ctrl.selectAll()" />
<label for="select_all"></label>
</span>
<a ng-click="$ctrl.changeOrderBy('Name')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
<a ng-click="$ctrl.changeOrderBy('Type')">
Type
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Type' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th ng-if="$ctrl.rbacEnabled">
<a ng-click="$ctrl.changeOrderBy('RoleName')">
Role
<i class="fa fa-sort-alpha-down" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true"
ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'RoleName' && $ctrl.state.reverseOrder"></i>
</a>
</th>
</tr>
@ -66,18 +65,16 @@
<tbody>
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index"
ng-class="{active: item.Checked}">
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-disabled="item.Inherited"
ng-click="$ctrl.selectItem(item, $event)" />
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-disabled="item.Inherited" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
{{ item.Name }}
<span ng-if="item.Inherited" class="text-muted small" style="margin-left: 2px;"><code
style="font-size: 85% !important;">inherited</code></span>
<span ng-if="item.Override" class="text-muted small" style="margin-left: 2px;"><code
style="font-size: 85% !important;">override</code></span>
<span ng-if="item.Inherited" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">inherited</code></span>
<span ng-if="item.Override" class="text-muted small" style="margin-left: 2px;"><code style="font-size: 85% !important;">override</code></span>
</td>
<td>{{ item.Type }}</td>
<td ng-if="$ctrl.rbacEnabled">
@ -88,9 +85,7 @@
</a>
</span>
<span ng-if="item.Updated">
<select ng-model="item.Role"
ng-options="role.Name for role in $ctrl.roles">
</select>
<select ng-model="item.Role" ng-options="role.Name for role in $ctrl.roles"> </select>
<a class="interactive" ng-click="item.Updated = false; item.Role = item.OldRole; item.OldRole = null; $event.stopPropagation();"><i class="fa fa-times"></i></a>
</span>
</td>
@ -105,17 +100,14 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit"
ng-change="$ctrl.changePaginationLimit()">
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
@ -129,4 +121,4 @@
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View file

@ -12,6 +12,6 @@ angular.module('portainer.app').component('accessDatatable', {
updateAction: '<',
reverseOrder: '<',
rbacEnabled: '<',
inheritFrom: '<'
}
inheritFrom: '<',
},
});

View file

@ -1,47 +1,49 @@
angular.module('portainer.app')
.controller('AccessDatatableController', ['$scope', '$controller', 'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
angular.module('portainer.app').controller('AccessDatatableController', [
'$scope',
'$controller',
'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
this.disableRemove = function(item) {
return item.Inherited;
};
this.disableRemove = function (item) {
return item.Inherited;
};
this.allowSelection = function(item) {
return !this.disableRemove(item);
};
this.allowSelection = function (item) {
return !this.disableRemove(item);
};
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
}
]);
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
},
]);

View file

@ -1,14 +1,14 @@
angular.module('portainer.app').component('accessTable', {
templateUrl: './accessTable.html',
controller: function() {
controller: function () {
this.state = {
orderBy: 'Name',
reverseOrder: false,
paginatedItemLimit: '10',
textFilter: ''
textFilter: '',
};
this.changeOrderBy = function(orderField) {
this.changeOrderBy = function (orderField) {
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
this.state.orderBy = orderField;
};
@ -16,6 +16,6 @@ angular.module('portainer.app').component('accessTable', {
bindings: {
dataset: '<',
entryClick: '<',
emptyDatasetMessage: '@'
}
emptyDatasetMessage: '@',
},
});

View file

@ -2,7 +2,14 @@
<table class="table table-hover">
<div class="col-sm-12">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<thead>
<tr>
@ -23,7 +30,11 @@
</tr>
</thead>
<tbody>
<tr ng-click="!item.Inherited && $ctrl.entryClick(item)" ng-class="{ 'interactive': !item.Inherited }" dir-paginate="item in $ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit">
<tr
ng-click="!item.Inherited && $ctrl.entryClick(item)"
ng-class="{ interactive: !item.Inherited }"
dir-paginate="item in $ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit"
>
<td>
{{ item.Name }}
<!-- <span class="image-tag label label-">inherited</span> -->
@ -37,7 +48,12 @@
<tr ng-if="!$ctrl.dataset">
<td colspan="2" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.dataset.length === 0 || ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit).length === 0">
<tr
ng-if="
$ctrl.dataset.length === 0 ||
($ctrl.dataset | filter: $ctrl.state.textFilter | orderBy: $ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit).length === 0
"
>
<td colspan="2" class="text-center text-muted">{{ $ctrl.emptyDatasetMessage }}</td>
</tr>
</tbody>

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('porAccessControlForm', {
formData: '=',
// Optional. An existing resource control object that will be used to set
// the default values of the component.
resourceControl: '<'
}
resourceControl: '<',
},
});

View file

@ -9,17 +9,15 @@
Enable access control
<portainer-tooltip position="bottom" message="When enabled, you can restrict the access and management of this resource."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input name="ownership" type="checkbox" ng-model="$ctrl.formData.AccessControlEnabled"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input name="ownership" type="checkbox" ng-model="$ctrl.formData.AccessControlEnabled" /><i></i> </label>
</div>
</div>
<!-- !access-control-switch -->
<!-- restricted-access -->
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled" style="margin-bottom: 0">
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators">
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
<label for="access_administrators">
<div class="boxselector_header">
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -29,7 +27,7 @@
</label>
</div>
<div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted">
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
<label for="access_restricted">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -41,7 +39,7 @@
</label>
</div>
<div ng-if="!$ctrl.isAdmin">
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private">
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" />
<label for="access_private">
<div class="boxselector_header">
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -53,14 +51,15 @@
</label>
</div>
<div ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 0">
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted">
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
<label for="access_restricted">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>
<p ng-if="$ctrl.availableTeams.length === 1">
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b>) to be able to manage this resource
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b
>) to be able to manage this resource
</p>
<p ng-if="$ctrl.availableTeams.length > 1">
I want to restrict the management of this resource to one or more of my teams
@ -71,17 +70,29 @@
</div>
<!-- restricted-access -->
<!-- authorized-teams -->
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled && $ctrl.formData.Ownership === $ctrl.RCO.RESTRICTED && ($ctrl.isAdmin || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1))" >
<div
class="form-group"
ng-if="$ctrl.formData.AccessControlEnabled && $ctrl.formData.Ownership === $ctrl.RCO.RESTRICTED && ($ctrl.isAdmin || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1))"
>
<div class="col-sm-12">
<label for="group-access" class="control-label text-left">
Authorized teams
<portainer-tooltip ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length > 0" position="bottom" message="You can select which teams(s) will be able to manage this resource."></portainer-tooltip>
<portainer-tooltip ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 1" position="bottom" message="As you are a member of multiple teams, you can select which teams(s) will be able to manage this resource."></portainer-tooltip>
<portainer-tooltip
ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length > 0"
position="bottom"
message="You can select which teams(s) will be able to manage this resource."
></portainer-tooltip>
<portainer-tooltip
ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 1"
position="bottom"
message="As you are a member of multiple teams, you can select which teams(s) will be able to manage this resource."
></portainer-tooltip>
</label>
<span ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length === 0" class="small text-muted" style="margin-left: 20px;">
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
</span>
<span isteven-multi-select
<span
isteven-multi-select
ng-if="($ctrl.isAdmin && $ctrl.availableTeams.length > 0) || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1)"
input-model="$ctrl.availableTeams"
output-model="$ctrl.formData.AuthorizedTeams"
@ -91,7 +102,8 @@
helper-elements="filter"
search-property="Name"
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
style="margin-left: 20px;">
style="margin-left: 20px;"
>
</span>
</div>
</div>
@ -101,12 +113,17 @@
<div class="col-sm-12">
<label for="group-access" class="control-label text-left">
Authorized users
<portainer-tooltip ng-if="$ctrl.isAdmin && $ctrl.availableUsers.length > 0" position="bottom" message="You can select which user(s) will be able to manage this resource."></portainer-tooltip>
<portainer-tooltip
ng-if="$ctrl.isAdmin && $ctrl.availableUsers.length > 0"
position="bottom"
message="You can select which user(s) will be able to manage this resource."
></portainer-tooltip>
</label>
<span ng-if="$ctrl.availableUsers.length === 0" class="small text-muted" style="margin-left: 20px;">
You have not yet created any users. Head over to the <a ui-sref="portainer.users">Users view</a> to manage users.
</span>
<span isteven-multi-select
<span
isteven-multi-select
ng-if="$ctrl.availableUsers.length > 0"
input-model="$ctrl.availableUsers"
output-model="$ctrl.formData.AuthorizedUsers"
@ -116,7 +133,8 @@
helper-elements="filter"
search-property="Username"
translation="{nothingSelected: 'Select one or more users', search: 'Search...'}"
style="margin-left: 20px;">
style="margin-left: 20px;"
>
</span>
</div>
</div>

View file

@ -1,75 +1,81 @@
import _ from 'lodash-es';
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
angular.module('portainer.app')
.controller('porAccessControlFormController', ['$q', 'UserService', 'TeamService', 'Notifications', 'Authentication', 'ResourceControlService',
function ($q, UserService, TeamService, Notifications, Authentication, ResourceControlService) {
var ctrl = this;
angular.module('portainer.app').controller('porAccessControlFormController', [
'$q',
'UserService',
'TeamService',
'Notifications',
'Authentication',
'ResourceControlService',
function ($q, UserService, TeamService, Notifications, Authentication, ResourceControlService) {
var ctrl = this;
ctrl.RCO = RCO;
ctrl.RCO = RCO;
ctrl.availableTeams = [];
ctrl.availableUsers = [];
ctrl.availableTeams = [];
ctrl.availableUsers = [];
function setOwnership(resourceControl, isAdmin) {
if (isAdmin && resourceControl.Ownership === RCO.PRIVATE) {
ctrl.formData.Ownership = RCO.RESTRICTED;
} else {
ctrl.formData.Ownership = resourceControl.Ownership;
}
}
function setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams) {
angular.forEach(ctrl.availableUsers, function(user) {
var found = _.find(authorizedUsers, { Id: user.Id });
if (found) {
user.selected = true;
function setOwnership(resourceControl, isAdmin) {
if (isAdmin && resourceControl.Ownership === RCO.PRIVATE) {
ctrl.formData.Ownership = RCO.RESTRICTED;
} else {
ctrl.formData.Ownership = resourceControl.Ownership;
}
});
angular.forEach(ctrl.availableTeams, function(team) {
var found = _.find(authorizedTeams, { Id: team.Id });
if (found) {
team.selected = true;
}
});
}
function initComponent() {
var isAdmin = Authentication.isAdmin();
ctrl.isAdmin = isAdmin;
if (isAdmin) {
ctrl.formData.Ownership = ctrl.RCO.ADMINISTRATORS;
}
$q.all({
availableTeams: TeamService.teams(),
availableUsers: isAdmin ? UserService.users(false) : []
})
.then(function success(data) {
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
function setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams) {
angular.forEach(ctrl.availableUsers, function (user) {
var found = _.find(authorizedUsers, { Id: user.Id });
if (found) {
user.selected = true;
}
});
var availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
ctrl.availableTeams = availableTeams;
if (!isAdmin && availableTeams.length === 1) {
ctrl.formData.AuthorizedTeams = availableTeams;
angular.forEach(ctrl.availableTeams, function (team) {
var found = _.find(authorizedTeams, { Id: team.Id });
if (found) {
team.selected = true;
}
});
}
function initComponent() {
var isAdmin = Authentication.isAdmin();
ctrl.isAdmin = isAdmin;
if (isAdmin) {
ctrl.formData.Ownership = ctrl.RCO.ADMINISTRATORS;
}
return $q.when(ctrl.resourceControl && ResourceControlService.retrieveOwnershipDetails(ctrl.resourceControl));
})
.then(function success(data) {
if (data) {
var authorizedUsers = data.authorizedUsers;
var authorizedTeams = data.authorizedTeams;
setOwnership(ctrl.resourceControl, isAdmin);
setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams);
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve access control information');
});
}
$q.all({
availableTeams: TeamService.teams(),
availableUsers: isAdmin ? UserService.users(false) : [],
})
.then(function success(data) {
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
initComponent();
}]);
var availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
ctrl.availableTeams = availableTeams;
if (!isAdmin && availableTeams.length === 1) {
ctrl.formData.AuthorizedTeams = availableTeams;
}
return $q.when(ctrl.resourceControl && ResourceControlService.retrieveOwnershipDetails(ctrl.resourceControl));
})
.then(function success(data) {
if (data) {
var authorizedUsers = data.authorizedUsers;
var authorizedTeams = data.authorizedTeams;
setOwnership(ctrl.resourceControl, isAdmin);
setAuthorizedUsersAndTeams(authorizedUsers, authorizedTeams);
}
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve access control information');
});
}
initComponent();
},
]);

View file

@ -11,6 +11,6 @@ angular.module('portainer.app').component('porAccessControlPanel', {
// Accepted values: 'container', 'service' or 'volume'.
resourceType: '<',
// Allow to disable the Ownership edition based on non resource control data
disableOwnershipChange: '<'
}
disableOwnershipChange: '<',
},
});

View file

@ -16,9 +16,24 @@
</span>
<span ng-if="$ctrl.resourceControl">
{{ $ctrl.resourceControl.Ownership }}
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.PUBLIC" message="This resource can be managed by any user with access to this endpoint." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.PRIVATE" message="Management of this resource is restricted to a single user." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
<portainer-tooltip ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.RESTRICTED" message="This resource can be managed by a restricted set of users and/or teams." position="bottom" style="margin-left: -3px;"></portainer-tooltip>
<portainer-tooltip
ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.PUBLIC"
message="This resource can be managed by any user with access to this endpoint."
position="bottom"
style="margin-left: -3px;"
></portainer-tooltip>
<portainer-tooltip
ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.PRIVATE"
message="Management of this resource is restricted to a single user."
position="bottom"
style="margin-left: -3px;"
></portainer-tooltip>
<portainer-tooltip
ng-if="$ctrl.resourceControl.Ownership === $ctrl.RCO.RESTRICTED"
message="This resource can be managed by a restricted set of users and/or teams."
position="bottom"
style="margin-left: -3px;"
></portainer-tooltip>
</span>
</td>
</tr>
@ -26,29 +41,43 @@
<tr ng-if="$ctrl.resourceControl.Type === $ctrl.RCTI.SERVICE && $ctrl.resourceType === $ctrl.RCTS.CONTAINER">
<td colspan="2">
<i class="fa fa-info-circle" aria-hidden="true" style="margin-right: 2px;"></i>
Access control on this resource is inherited from the following service: <a ui-sref="docker.services.service({ id: $ctrl.resourceControl.ResourceId })">{{ $ctrl.resourceControl.ResourceId | truncate }}</a>
<portainer-tooltip message="Access control applied on a service is also applied on each container of that service." position="bottom" style="margin-left: 2px;"></portainer-tooltip>
Access control on this resource is inherited from the following service:
<a ui-sref="docker.services.service({ id: $ctrl.resourceControl.ResourceId })">{{ $ctrl.resourceControl.ResourceId | truncate }}</a>
<portainer-tooltip
message="Access control applied on a service is also applied on each container of that service."
position="bottom"
style="margin-left: 2px;"
></portainer-tooltip>
</td>
</tr>
<tr ng-if="$ctrl.resourceControl.Type === $ctrl.RCTI.CONTAINER && $ctrl.resourceType === $ctrl.RCTS.VOLUME">
<td colspan="2">
<i class="fa fa-info-circle" aria-hidden="true" style="margin-right: 2px;"></i>
Access control on this resource is inherited from the following container: <a ui-sref="docker.containers.container({ id: $ctrl.resourceControl.ResourceId })">{{ $ctrl.resourceControl.ResourceId | truncate }}</a>
<portainer-tooltip message="Access control applied on a container created using a template is also applied on each volume associated to the container." position="bottom" style="margin-left: 2px;"></portainer-tooltip>
Access control on this resource is inherited from the following container:
<a ui-sref="docker.containers.container({ id: $ctrl.resourceControl.ResourceId })">{{ $ctrl.resourceControl.ResourceId | truncate }}</a>
<portainer-tooltip
message="Access control applied on a container created using a template is also applied on each volume associated to the container."
position="bottom"
style="margin-left: 2px;"
></portainer-tooltip>
</td>
</tr>
<tr ng-if="$ctrl.resourceControl.Type === $ctrl.RCTI.STACK && $ctrl.resourceType !== $ctrl.RCTS.STACK">
<td colspan="2">
<i class="fa fa-info-circle" aria-hidden="true" style="margin-right: 2px;"></i>
Access control on this resource is inherited from the following stack: {{ $ctrl.resourceControl.ResourceId }}
<portainer-tooltip message="Access control applied on a stack is also applied on each resource in the stack." position="bottom" style="margin-left: 2px;"></portainer-tooltip>
<portainer-tooltip
message="Access control applied on a stack is also applied on each resource in the stack."
position="bottom"
style="margin-left: 2px;"
></portainer-tooltip>
</td>
</tr>
<!-- authorized-users -->
<tr ng-if="$ctrl.resourceControl.UserAccesses.length > 0">
<td>Authorized users</td>
<td>
<span ng-repeat="user in $ctrl.authorizedUsers">{{user.Username}}{{$last ? '' : ', '}} </span>
<span ng-repeat="user in $ctrl.authorizedUsers">{{ user.Username }}{{ $last ? '' : ', ' }} </span>
</td>
</tr>
<!-- !authorized-users -->
@ -56,7 +85,7 @@
<tr ng-if="$ctrl.resourceControl.TeamAccesses.length > 0">
<td>Authorized teams</td>
<td>
<span ng-repeat="team in $ctrl.authorizedTeams">{{team.Name}}{{$last ? '' : ', '}} </span>
<span ng-repeat="team in $ctrl.authorizedTeams">{{ team.Name }}{{ $last ? '' : ', ' }} </span>
</td>
</tr>
<!-- !authorized-teams -->
@ -72,7 +101,7 @@
<td colspan="2" style="white-space: inherit;">
<div class="boxselector_wrapper">
<div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_administrators" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.ADMINISTRATORS">
<input type="radio" id="access_administrators" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.ADMINISTRATORS" />
<label for="access_administrators">
<div class="boxselector_header">
<i ng-class="'administrators' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -82,7 +111,7 @@
</label>
</div>
<div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_restricted" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.RESTRICTED">
<input type="radio" id="access_restricted" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.RESTRICTED" />
<label for="access_restricted">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -94,14 +123,15 @@
</label>
</div>
<div ng-if="!$ctrl.isAdmin && $ctrl.state.canChangeOwnershipToTeam && $ctrl.availableTeams.length > 0">
<input type="radio" id="access_restricted" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.RESTRICTED">
<input type="radio" id="access_restricted" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.RESTRICTED" />
<label for="access_restricted">
<div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
Restricted
</div>
<p ng-if="$ctrl.availableTeams.length === 1">
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b>) to be able to manage this resource
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b
>) to be able to manage this resource
</p>
<p ng-if="$ctrl.availableTeams.length > 1">
I want to restrict the management of this resource to one or more of my teams
@ -109,7 +139,7 @@
</label>
</div>
<div>
<input type="radio" id="access_public" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.PUBLIC">
<input type="radio" id="access_public" ng-model="$ctrl.formValues.Ownership" ng-value="$ctrl.RCO.PUBLIC" />
<label for="access_public">
<div class="boxselector_header">
<i ng-class="'public' | ownershipicon" aria-hidden="true" style="margin-right: 2px;"></i>
@ -123,13 +153,14 @@
</tr>
<!-- edit-ownership-choices -->
<!-- select-teams -->
<tr ng-if="$ctrl.state.editOwnership && $ctrl.formValues.Ownership === $ctrl.RCO.RESTRICTED && ($ctrl.isAdmin || !$ctrl.isAdmin && $ctrl.availableTeams.length > 1)">
<tr ng-if="$ctrl.state.editOwnership && $ctrl.formValues.Ownership === $ctrl.RCO.RESTRICTED && ($ctrl.isAdmin || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1))">
<td colspan="2">
<span>Teams</span>
<span ng-if="$ctrl.isAdmin && $ctrl.availableTeams.length === 0" class="small text-muted" style="margin-left: 10px;">
You have not yet created any teams. Head over to the <a ui-sref="portainer.teams">Teams view</a> to manage teams.
</span>
<span isteven-multi-select
<span
isteven-multi-select
ng-if="($ctrl.isAdmin && $ctrl.availableTeams.length > 0) || (!$ctrl.isAdmin && $ctrl.availableTeams.length > 1)"
input-model="$ctrl.availableTeams"
output-model="$ctrl.formValues.Ownership_Teams"
@ -139,7 +170,8 @@
helper-elements="filter"
search-property="Name"
max-labels="3"
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}">
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
>
</span>
</td>
</tr>
@ -151,7 +183,8 @@
<span ng-if="$ctrl.availableUsers.length === 0" class="small text-muted" style="margin-left: 10px;">
You have not yet created any users. Head over to the <a ui-sref="portainer.users">Users view</a> to manage users.
</span>
<span isteven-multi-select
<span
isteven-multi-select
ng-if="$ctrl.availableUsers.length > 0"
input-model="$ctrl.availableUsers"
output-model="$ctrl.formValues.Ownership_Users"
@ -161,7 +194,8 @@
helper-elements="filter"
search-property="Username"
max-labels="3"
translation="{nothingSelected: 'Select one or more users', search: 'Search...'}">
translation="{nothingSelected: 'Select one or more users', search: 'Search...'}"
>
</span>
</td>
</tr>

View file

@ -1,134 +1,144 @@
import _ from 'lodash-es';
import { AccessControlPanelData } from './porAccessControlPanelModel';
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
import { ResourceControlTypeString as RCTS, ResourceControlTypeInt as RCTI} from 'Portainer/models/resourceControl/resourceControlTypes';
import { ResourceControlTypeString as RCTS, ResourceControlTypeInt as RCTI } from 'Portainer/models/resourceControl/resourceControlTypes';
angular.module('portainer.app')
.controller('porAccessControlPanelController', ['$q', '$state', 'UserService', 'TeamService', 'ResourceControlHelper', 'ResourceControlService', 'Notifications', 'Authentication', 'ModalService', 'FormValidator',
function ($q, $state, UserService, TeamService, ResourceControlHelper, ResourceControlService, Notifications, Authentication, ModalService, FormValidator) {
angular.module('portainer.app').controller('porAccessControlPanelController', [
'$q',
'$state',
'UserService',
'TeamService',
'ResourceControlHelper',
'ResourceControlService',
'Notifications',
'Authentication',
'ModalService',
'FormValidator',
function ($q, $state, UserService, TeamService, ResourceControlHelper, ResourceControlService, Notifications, Authentication, ModalService, FormValidator) {
var ctrl = this;
var ctrl = this;
ctrl.RCO = RCO;
ctrl.RCTS = RCTS;
ctrl.RCTI = RCTI;
ctrl.state = {
displayAccessControlPanel: false,
canEditOwnership: false,
editOwnership: false,
formValidationError: ''
};
ctrl.formValues = new AccessControlPanelData();
ctrl.authorizedUsers = [];
ctrl.availableUsers = [];
ctrl.authorizedTeams = [];
ctrl.availableTeams = [];
ctrl.canEditOwnership = function() {
const hasRC = ctrl.resourceControl;
const inheritedVolume = hasRC && ctrl.resourceControl.Type === RCTI.CONTAINER && ctrl.resourceType === RCTS.VOLUME;
const inheritedContainer = hasRC && ctrl.resourceControl.Type === RCTI.SERVICE && ctrl.resourceType === RCTS.CONTAINER;
const inheritedFromStack = hasRC && ctrl.resourceControl.Type === RCTI.STACK && ctrl.resourceType !== RCTS.STACK;
const hasSpecialDisable = ctrl.disableOwnershipChange;
return !inheritedVolume && !inheritedContainer && !inheritedFromStack && !hasSpecialDisable
&& !ctrl.state.editOwnership && (ctrl.isAdmin || ctrl.state.canEditOwnership);
}
ctrl.confirmUpdateOwnership = function () {
if (!validateForm()) {
return;
}
ModalService.confirmAccessControlUpdate(function (confirmed) {
if(!confirmed) { return; }
updateOwnership();
});
};
function validateForm() {
ctrl.state.formValidationError = '';
var error = '';
var accessControlData = {
AccessControlEnabled: ctrl.formValues.Ownership === RCO.PUBLIC ? false : true,
Ownership: ctrl.formValues.Ownership,
AuthorizedUsers: ctrl.formValues.Ownership_Users,
AuthorizedTeams: ctrl.formValues.Ownership_Teams
ctrl.RCO = RCO;
ctrl.RCTS = RCTS;
ctrl.RCTI = RCTI;
ctrl.state = {
displayAccessControlPanel: false,
canEditOwnership: false,
editOwnership: false,
formValidationError: '',
};
var isAdmin = ctrl.isAdmin;
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
if (error) {
ctrl.state.formValidationError = error;
return false;
}
return true;
}
function updateOwnership() {
ResourceControlService.applyResourceControlChange(ctrl.resourceType, ctrl.resourceId, ctrl.resourceControl, ctrl.formValues)
.then(function success() {
Notifications.success('Access control successfully updated');
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update access control');
});
}
ctrl.formValues = new AccessControlPanelData();
function initComponent() {
var userDetails = Authentication.getUserDetails();
var isAdmin = Authentication.isAdmin();
var userId = userDetails.ID;
ctrl.isAdmin = isAdmin;
var resourceControl = ctrl.resourceControl;
ctrl.authorizedUsers = [];
ctrl.availableUsers = [];
ctrl.authorizedTeams = [];
ctrl.availableTeams = [];
if (isAdmin && resourceControl) {
ctrl.formValues.Ownership = resourceControl.Ownership === RCO.PRIVATE ? RCO.RESTRICTED : resourceControl.Ownership;
} else {
ctrl.formValues.Ownership = RCO.ADMINISTRATORS;
}
ctrl.canEditOwnership = function () {
const hasRC = ctrl.resourceControl;
const inheritedVolume = hasRC && ctrl.resourceControl.Type === RCTI.CONTAINER && ctrl.resourceType === RCTS.VOLUME;
const inheritedContainer = hasRC && ctrl.resourceControl.Type === RCTI.SERVICE && ctrl.resourceType === RCTS.CONTAINER;
const inheritedFromStack = hasRC && ctrl.resourceControl.Type === RCTI.STACK && ctrl.resourceType !== RCTS.STACK;
const hasSpecialDisable = ctrl.disableOwnershipChange;
ResourceControlService.retrieveOwnershipDetails(resourceControl)
.then(function success(data) {
ctrl.authorizedUsers = data.authorizedUsers;
ctrl.authorizedTeams = data.authorizedTeams;
return ResourceControlService.retrieveUserPermissionsOnResource(userId, isAdmin, resourceControl);
})
.then(function success(data) {
ctrl.state.canEditOwnership = data.isPartOfRestrictedUsers || data.isLeaderOfAnyRestrictedTeams;
ctrl.state.canChangeOwnershipToTeam = data.isPartOfRestrictedUsers;
return !inheritedVolume && !inheritedContainer && !inheritedFromStack && !hasSpecialDisable && !ctrl.state.editOwnership && (ctrl.isAdmin || ctrl.state.canEditOwnership);
};
return $q.all({
availableUsers: isAdmin ? UserService.users(false) : [],
availableTeams: isAdmin || data.isPartOfRestrictedUsers ? TeamService.teams() : []
});
})
.then(function success(data) {
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
angular.forEach(ctrl.availableUsers, function(user) {
var found = _.find(ctrl.authorizedUsers, { Id: user.Id });
if (found) {
user.selected = true;
}
});
ctrl.availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
angular.forEach(data.availableTeams, function(team) {
var found = _.find(ctrl.authorizedTeams, { Id: team.Id });
if (found) {
team.selected = true;
}
});
if (data.availableTeams.length === 1) {
ctrl.formValues.Ownership_Teams.push(data.availableTeams[0]);
ctrl.confirmUpdateOwnership = function () {
if (!validateForm()) {
return;
}
ctrl.state.displayAccessControlPanel = true;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve access control information');
});
}
ModalService.confirmAccessControlUpdate(function (confirmed) {
if (!confirmed) {
return;
}
updateOwnership();
});
};
initComponent();
}]);
function validateForm() {
ctrl.state.formValidationError = '';
var error = '';
var accessControlData = {
AccessControlEnabled: ctrl.formValues.Ownership === RCO.PUBLIC ? false : true,
Ownership: ctrl.formValues.Ownership,
AuthorizedUsers: ctrl.formValues.Ownership_Users,
AuthorizedTeams: ctrl.formValues.Ownership_Teams,
};
var isAdmin = ctrl.isAdmin;
error = FormValidator.validateAccessControl(accessControlData, isAdmin);
if (error) {
ctrl.state.formValidationError = error;
return false;
}
return true;
}
function updateOwnership() {
ResourceControlService.applyResourceControlChange(ctrl.resourceType, ctrl.resourceId, ctrl.resourceControl, ctrl.formValues)
.then(function success() {
Notifications.success('Access control successfully updated');
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update access control');
});
}
function initComponent() {
var userDetails = Authentication.getUserDetails();
var isAdmin = Authentication.isAdmin();
var userId = userDetails.ID;
ctrl.isAdmin = isAdmin;
var resourceControl = ctrl.resourceControl;
if (isAdmin && resourceControl) {
ctrl.formValues.Ownership = resourceControl.Ownership === RCO.PRIVATE ? RCO.RESTRICTED : resourceControl.Ownership;
} else {
ctrl.formValues.Ownership = RCO.ADMINISTRATORS;
}
ResourceControlService.retrieveOwnershipDetails(resourceControl)
.then(function success(data) {
ctrl.authorizedUsers = data.authorizedUsers;
ctrl.authorizedTeams = data.authorizedTeams;
return ResourceControlService.retrieveUserPermissionsOnResource(userId, isAdmin, resourceControl);
})
.then(function success(data) {
ctrl.state.canEditOwnership = data.isPartOfRestrictedUsers || data.isLeaderOfAnyRestrictedTeams;
ctrl.state.canChangeOwnershipToTeam = data.isPartOfRestrictedUsers;
return $q.all({
availableUsers: isAdmin ? UserService.users(false) : [],
availableTeams: isAdmin || data.isPartOfRestrictedUsers ? TeamService.teams() : [],
});
})
.then(function success(data) {
ctrl.availableUsers = _.orderBy(data.availableUsers, 'Username', 'asc');
angular.forEach(ctrl.availableUsers, function (user) {
var found = _.find(ctrl.authorizedUsers, { Id: user.Id });
if (found) {
user.selected = true;
}
});
ctrl.availableTeams = _.orderBy(data.availableTeams, 'Name', 'asc');
angular.forEach(data.availableTeams, function (team) {
var found = _.find(ctrl.authorizedTeams, { Id: team.Id });
if (found) {
team.selected = true;
}
});
if (data.availableTeams.length === 1) {
ctrl.formValues.Ownership_Teams.push(data.availableTeams[0]);
}
ctrl.state.displayAccessControlPanel = true;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve access control information');
});
}
initComponent();
},
]);

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('porAccessManagement', {
inheritFrom: '<',
entityType: '@',
updateAccess: '<',
actionInProgress: '<'
}
actionInProgress: '<',
},
});

View file

@ -12,10 +12,18 @@
<span class="small text-muted" ng-if="ctrl.availableUsersAndTeams.length === 0">
No users or teams available.
</span>
<span isteven-multi-select ng-if="ctrl.availableUsersAndTeams.length > 0" input-model="ctrl.availableUsersAndTeams"
output-model="ctrl.formValues.multiselectOutput" button-label="icon '-' Name" item-label="icon '-' Name"
tick-property="ticked" helper-elements="filter" search-property="Name"
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}">
<span
isteven-multi-select
ng-if="ctrl.availableUsersAndTeams.length > 0"
input-model="ctrl.availableUsersAndTeams"
output-model="ctrl.formValues.multiselectOutput"
button-label="icon '-' Name"
item-label="icon '-' Name"
tick-property="ticked"
helper-elements="filter"
search-property="Name"
translation="{nothingSelected: 'Select one or more users and/or teams', search: 'Search...'}"
>
</span>
</div>
</div>
@ -25,11 +33,9 @@
Role
</label>
<div class="col-sm-9 col-lg-4">
<select ng-if="ctrl.rbacEnabled" class="form-control" ng-model="ctrl.formValues.selectedRole"
ng-options="role.Name for role in ctrl.roles">
</select>
<select ng-if="ctrl.rbacEnabled" class="form-control" ng-model="ctrl.formValues.selectedRole" ng-options="role.Name for role in ctrl.roles"> </select>
<span class="small text-muted" ng-if="!ctrl.rbacEnabled">
The <a ui-sref="portainer.extensions.extension({id: 3})">Role-Based Access Control extension</a> is required to select a specific role.
The <a ui-sref="portainer.extensions.extension({id: 3})">Role-Based Access Control extension</a> is required to select a specific role.
</span>
</div>
</div>
@ -37,10 +43,14 @@
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="(ctrl.availableUsersAndTeams | filter:{ticked:true}).length === 0 || ctrl.actionInProgress"
ng-click="ctrl.authorizeAccess()" button-spinner="ctrl.actionInProgress">
<span ng-hide="ctrl.state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Create
access</span>
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="(ctrl.availableUsersAndTeams | filter:{ticked:true}).length === 0 || ctrl.actionInProgress"
ng-click="ctrl.authorizeAccess()"
button-spinner="ctrl.actionInProgress"
>
<span ng-hide="ctrl.state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Create access</span>
<span ng-show="ctrl.state.actionInProgress">Creating access...</span>
</button>
</div>
@ -53,13 +63,19 @@
</div>
<div class="row">
<div class="col-sm-12">
<access-datatable ng-if="ctrl.authorizedUsersAndTeams"
title-text="Access" title-icon="fa-user-lock"
table-key="{{ 'access_' + ctrl.entityType}}" order-by="Name"
rbac-enabled="ctrl.rbacEnabled && ctrl.entityType !== 'registry'" inherit-from="ctrl.inheritFrom"
dataset="ctrl.authorizedUsersAndTeams" roles="ctrl.roles"
update-action="ctrl.updateAction" remove-action="ctrl.unauthorizeAccess"
>
<access-datatable
ng-if="ctrl.authorizedUsersAndTeams"
title-text="Access"
title-icon="fa-user-lock"
table-key="{{ 'access_' + ctrl.entityType }}"
order-by="Name"
rbac-enabled="ctrl.rbacEnabled && ctrl.entityType !== 'registry'"
inherit-from="ctrl.inheritFrom"
dataset="ctrl.authorizedUsersAndTeams"
roles="ctrl.roles"
update-action="ctrl.updateAction"
remove-action="ctrl.unauthorizeAccess"
>
</access-datatable>
</div>
</div>

View file

@ -1,6 +1,6 @@
import _ from "lodash-es";
import _ from 'lodash-es';
import angular from "angular";
import angular from 'angular';
class PorAccessManagementController {
/* @ngInject */
@ -9,7 +9,7 @@ class PorAccessManagementController {
this.ExtensionService = ExtensionService;
this.AccessService = AccessService;
this.RoleService = RoleService;
this.unauthorizeAccess = this.unauthorizeAccess.bind(this);
this.updateAction = this.updateAction.bind(this);
}
@ -18,8 +18,8 @@ class PorAccessManagementController {
const entity = this.accessControlledEntity;
const oldUserAccessPolicies = entity.UserAccessPolicies;
const oldTeamAccessPolicies = entity.TeamAccessPolicies;
const updatedUserAccesses = _.filter(this.authorizedUsersAndTeams, {Updated: true, Type: 'user', Inherited: false});
const updatedTeamAccesses = _.filter(this.authorizedUsersAndTeams, {Updated: true, Type: 'team', Inherited: false});
const updatedUserAccesses = _.filter(this.authorizedUsersAndTeams, { Updated: true, Type: 'user', Inherited: false });
const updatedTeamAccesses = _.filter(this.authorizedUsersAndTeams, { Updated: true, Type: 'team', Inherited: false });
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, updatedUserAccesses, updatedTeamAccesses);
this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies;
@ -32,8 +32,8 @@ class PorAccessManagementController {
const oldUserAccessPolicies = entity.UserAccessPolicies;
const oldTeamAccessPolicies = entity.TeamAccessPolicies;
const selectedRoleId = this.rbacEnabled ? this.formValues.selectedRole.Id : 0;
const selectedUserAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === "user");
const selectedTeamAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === "team");
const selectedUserAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'user');
const selectedTeamAccesses = _.filter(this.formValues.multiselectOutput, (access) => access.Type === 'team');
const accessPolicies = this.AccessService.generateAccessPolicies(oldUserAccessPolicies, oldTeamAccessPolicies, selectedUserAccesses, selectedTeamAccesses, selectedRoleId);
this.accessControlledEntity.UserAccessPolicies = accessPolicies.userAccessPolicies;
@ -45,8 +45,8 @@ class PorAccessManagementController {
const entity = this.accessControlledEntity;
const userAccessPolicies = entity.UserAccessPolicies;
const teamAccessPolicies = entity.TeamAccessPolicies;
const selectedUserAccesses = _.filter(selectedAccesses, (access) => access.Type === "user");
const selectedTeamAccesses = _.filter(selectedAccesses, (access) => access.Type === "team");
const selectedUserAccesses = _.filter(selectedAccesses, (access) => access.Type === 'user');
const selectedTeamAccesses = _.filter(selectedAccesses, (access) => access.Type === 'team');
_.forEach(selectedUserAccesses, (access) => delete userAccessPolicies[access.Id]);
_.forEach(selectedTeamAccesses, (access) => delete teamAccessPolicies[access.Id]);
this.updateAccess();
@ -55,18 +55,18 @@ class PorAccessManagementController {
async $onInit() {
const entity = this.accessControlledEntity;
if (!entity) {
this.Notifications.error("Failure", "Unable to retrieve accesses");
this.Notifications.error('Failure', 'Unable to retrieve accesses');
return;
}
if (!entity.UserAccessPolicies) {
entity.UserAccessPolicies = {}
entity.UserAccessPolicies = {};
}
if (!entity.TeamAccessPolicies) {
entity.TeamAccessPolicies = {};
}
const parent = this.inheritFrom;
if (parent && !parent.UserAccessPolicies) {
parent.UserAccessPolicies = {}
parent.UserAccessPolicies = {};
}
if (parent && !parent.TeamAccessPolicies) {
parent.TeamAccessPolicies = {};
@ -78,7 +78,7 @@ class PorAccessManagementController {
if (this.rbacEnabled) {
this.roles = await this.RoleService.roles();
this.formValues = {
selectedRole: this.roles[0]
selectedRole: this.roles[0],
};
}
const data = await this.AccessService.accesses(
@ -93,12 +93,10 @@ class PorAccessManagementController {
} catch (err) {
this.availableUsersAndTeams = [];
this.authorizedUsersAndTeams = [];
this.Notifications.error("Failure", err, "Unable to retrieve accesses");
this.Notifications.error('Failure', err, 'Unable to retrieve accesses');
}
}
}
export default PorAccessManagementController;
angular
.module("portainer.app")
.controller("porAccessManagementController", PorAccessManagementController);
angular.module('portainer.app').controller('porAccessManagementController', PorAccessManagementController);

View file

@ -1,13 +1,15 @@
angular.module('portainer.app')
.directive('autoFocus', ['$timeout', function porAutoFocus($timeout) {
var directive = {
restrict: 'A',
link: function(scope, element) {
$timeout(function() {
element[0].focus();
});
}
};
angular.module('portainer.app').directive('autoFocus', [
'$timeout',
function porAutoFocus($timeout) {
var directive = {
restrict: 'A',
link: function (scope, element) {
$timeout(function () {
element[0].focus();
});
},
};
return directive;
}]);
return directive;
},
]);

View file

@ -1,12 +1,11 @@
angular.module('portainer.app')
.directive('buttonSpinner', function buttonSpinner() {
angular.module('portainer.app').directive('buttonSpinner', function buttonSpinner() {
var directive = {
restrict: 'A',
scope: {
spinning: '=buttonSpinner'
spinning: '=buttonSpinner',
},
transclude: true,
template: '<ng-transclude></ng-transclude><span ng-show="spinning"><i class="fa fa-circle-notch fa-spin" style="margin-left: 2px;"></i>&nbsp;</span>'
template: '<ng-transclude></ng-transclude><span ng-show="spinning"><i class="fa fa-circle-notch fa-spin" style="margin-left: 2px;"></i>&nbsp;</span>',
};
return directive;

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('codeEditor', {
yml: '<',
readOnly: '<',
onChange: '<',
value: '<'
}
value: '<',
},
});

View file

@ -1,18 +1,20 @@
angular.module('portainer.app')
.controller('CodeEditorController', ['$document', 'CodeMirrorService',
function ($document, CodeMirrorService) {
var ctrl = this;
angular.module('portainer.app').controller('CodeEditorController', [
'$document',
'CodeMirrorService',
function ($document, CodeMirrorService) {
var ctrl = this;
this.$onInit = function() {
$document.ready(function() {
var editorElement = $document[0].getElementById(ctrl.identifier);
ctrl.editor = CodeMirrorService.applyCodeMirrorOnElement(editorElement, ctrl.yml, ctrl.readOnly);
if (ctrl.onChange) {
ctrl.editor.on('change', ctrl.onChange);
}
if (ctrl.value) {
ctrl.editor.setValue(ctrl.value);
}
});
};
}]);
this.$onInit = function () {
$document.ready(function () {
var editorElement = $document[0].getElementById(ctrl.identifier);
ctrl.editor = CodeMirrorService.applyCodeMirrorOnElement(editorElement, ctrl.yml, ctrl.readOnly);
if (ctrl.onChange) {
ctrl.editor.on('change', ctrl.onChange);
}
if (ctrl.value) {
ctrl.editor.setValue(ctrl.value);
}
});
};
},
]);

View file

@ -46,7 +46,8 @@
margin-right: 5px;
}
.datatable .searchInput:active, .datatable .searchInput:focus {
.datatable .searchInput:active,
.datatable .searchInput:focus {
outline: none;
}
@ -84,7 +85,8 @@
margin: 0;
}
.datatable .pagination > li > a, .pagination > li > span {
.datatable .pagination > li > a,
.pagination > li > span {
float: none;
}
@ -140,8 +142,9 @@
cursor: pointer;
}
.md-checkbox label:before, .md-checkbox label:after {
content: "";
.md-checkbox label:before,
.md-checkbox label:after {
content: '';
left: 0;
position: absolute;
top: 0;
@ -155,28 +158,28 @@
border-radius: 2px;
cursor: pointer;
height: 16px;
transition: background .3s;
transition: background 0.3s;
width: 16px;
}
.md-checkbox input[type="checkbox"] {
.md-checkbox input[type='checkbox'] {
margin-right: 5px;
opacity: 0;
outline: 0;
}
.md-checkbox input[type="checkbox"]:checked + label:before {
.md-checkbox input[type='checkbox']:checked + label:before {
background: #337ab7;
border: none;
}
.md-checkbox input[type="checkbox"]:disabled + label:before {
.md-checkbox input[type='checkbox']:disabled + label:before {
background: #ececec;
border: 2px solid #ddd;
cursor: auto;
}
.md-checkbox input[type="checkbox"]:checked + label:after {
.md-checkbox input[type='checkbox']:checked + label:after {
border: 2px solid #fff;
border-right-style: none;
border-top-style: none;
@ -185,7 +188,6 @@
top: 5px;
transform: rotate(-45deg);
width: 9px;
}
.md-radio {
@ -196,16 +198,16 @@
display: inline-block;
}
.md-radio input[type="radio"] {
.md-radio input[type='radio'] {
display: none;
}
.md-radio input[type="radio"]:checked + label:before {
.md-radio input[type='radio']:checked + label:before {
animation: ripple 0.2s linear forwards;
border-color: #337ab7;
}
.md-radio input[type="radio"]:checked + label:after {
.md-radio input[type='radio']:checked + label:after {
transform: scale(1);
}
@ -219,11 +221,12 @@
vertical-align: bottom;
}
.md-radio label:before, .md-radio label:after {
.md-radio label:before,
.md-radio label:after {
border-radius: 50%;
content: '';
position: absolute;
transition: all .3s ease;
transition: all 0.3s ease;
transition-property: transform, border-color;
}

View file

@ -2,27 +2,25 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar" ng-if="$ctrl.endpointManagement">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.endpoints.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add endpoint
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.endpoints.new"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add endpoint </button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" auto-focus
<input
type="text"
class="searchInput"
auto-focus
placeholder="Search..."
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
ng-model-options="{ debounce: 300 }"
>
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -64,12 +62,14 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in $ctrl.state.filteredDataSet | itemsPerPage: $ctrl.state.paginatedItemLimit"
total-items="$ctrl.state.totalFilteredDataSet"
ng-class="{active: item.Checked}">
<tr
dir-paginate="item in $ctrl.state.filteredDataSet | itemsPerPage: $ctrl.state.paginatedItemLimit"
total-items="$ctrl.state.totalFilteredDataSet"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox" ng-if="$ctrl.endpointManagement">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.endpoints.endpoint({id: item.Id})" ng-if="$ctrl.endpointManagement">{{ item.Name }}</a>
@ -84,9 +84,7 @@
<td>{{ item.URL | stripprotocol }}</td>
<td>{{ item.GroupName }}</td>
<td>
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement">
<i class="fa fa-users" aria-hidden="true"></i> Manage access
</a>
<a ui-sref="portainer.endpoints.endpoint.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
</td>
</tr>
<tr ng-if="$ctrl.state.loading">
@ -99,9 +97,7 @@
</table>
</div>
<div class="footer" ng-if="!$ctrl.state.loading">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -10,6 +10,6 @@ angular.module('portainer.app').component('endpointsDatatable', {
endpointManagement: '<',
accessManagement: '<',
removeAction: '<',
retrievePage: '<'
}
retrievePage: '<',
},
});

View file

@ -1,80 +1,83 @@
angular.module('portainer.app')
.controller('EndpointsDatatableController', ['$scope', '$controller', 'DatatableService', 'PaginationService',
function ($scope, $controller, DatatableService, PaginationService) {
angular.module('portainer.app').controller('EndpointsDatatableController', [
'$scope',
'$controller',
'DatatableService',
'PaginationService',
function ($scope, $controller, DatatableService, PaginationService) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
this.state = Object.assign(this.state, {
orderBy: this.orderBy,
loading: true,
filteredDataSet: [],
totalFilteredDataset: 0,
pageNumber: 1,
});
this.state = Object.assign(this.state, {
orderBy: this.orderBy,
loading: true,
filteredDataSet: [],
totalFilteredDataset: 0,
pageNumber: 1
});
this.paginationChanged = function() {
this.state.loading = true;
this.state.filteredDataSet = [];
const start = (this.state.pageNumber - 1) * this.state.paginatedItemLimit + 1;
this.retrievePage(start, this.state.paginatedItemLimit, this.state.textFilter)
this.paginationChanged = function () {
this.state.loading = true;
this.state.filteredDataSet = [];
const start = (this.state.pageNumber - 1) * this.state.paginatedItemLimit + 1;
this.retrievePage(start, this.state.paginatedItemLimit, this.state.textFilter)
.then((data) => {
this.state.filteredDataSet = data.endpoints;
this.state.totalFilteredDataSet = data.totalCount;
}).finally(() => {
})
.finally(() => {
this.state.loading = false;
});
};
this.onPageChange = function (newPageNumber) {
this.state.pageNumber = newPageNumber;
this.paginationChanged();
};
/**
* Overridden
*/
this.onTextFilterChange = function () {
var filterValue = this.state.textFilter;
DatatableService.setDataTableTextFilters(this.tableKey, filterValue);
this.paginationChanged();
};
/**
* Overridden
*/
this.changePaginationLimit = function () {
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
this.paginationChanged();
};
/**
* Overridden
*/
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
this.onPageChange = function(newPageNumber) {
this.state.pageNumber = newPageNumber;
this.paginationChanged();
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
/**
* Overridden
*/
this.onTextFilterChange = function() {
var filterValue = this.state.textFilter;
DatatableService.setDataTableTextFilters(this.tableKey, filterValue);
this.paginationChanged();
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
/**
* Overridden
*/
this.changePaginationLimit = function() {
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
this.paginationChanged();
};
/**
* Overridden
*/
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
this.paginationChanged();
};
}
this.paginationChanged();
};
},
]);

View file

@ -3,191 +3,194 @@ import './datatable.css';
import { ResourceControlOwnership as RCO } from 'Portainer/models/resourceControl/resourceControlOwnership';
function isBetween(value, a, b) {
return (value >= a && value <= b) || (value >= b && value <= a) ;
return (value >= a && value <= b) || (value >= b && value <= a);
}
angular.module('portainer.app')
.controller('GenericDatatableController', ['$interval', 'PaginationService', 'DatatableService', 'PAGINATION_MAX_ITEMS',
function ($interval, PaginationService, DatatableService, PAGINATION_MAX_ITEMS) {
angular.module('portainer.app').controller('GenericDatatableController', [
'$interval',
'PaginationService',
'DatatableService',
'PAGINATION_MAX_ITEMS',
function ($interval, PaginationService, DatatableService, PAGINATION_MAX_ITEMS) {
this.RCO = RCO;
this.RCO = RCO;
this.state = {
selectAll: false,
orderBy: this.orderBy,
paginatedItemLimit: PAGINATION_MAX_ITEMS,
displayTextFilter: false,
get selectedItemCount() {
return this.selectedItems.length || 0;
},
selectedItems: [],
};
this.state = {
selectAll: false,
orderBy: this.orderBy,
paginatedItemLimit: PAGINATION_MAX_ITEMS,
displayTextFilter: false,
get selectedItemCount() {
return this.selectedItems.length || 0;
},
selectedItems: []
};
this.settings = {
open: false,
repeater: {
autoRefresh: false,
refreshRate: '30'
}
}
this.resetSelectionState = function() {
this.state.selectAll = false;
this.state.selectedItems = [];
_.map(this.state.filteredDataSet, (item) => item.Checked = false);
};
this.onTextFilterChange = function() {
DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
};
this.changeOrderBy = function(orderField) {
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
this.state.orderBy = orderField;
DatatableService.setDataTableOrder(this.tableKey, orderField, this.state.reverseOrder);
};
this.selectItem = function(item, event) {
// Handle range select using shift
if (event && event.originalEvent.shiftKey && this.state.firstClickedItem) {
const firstItemIndex = this.state.filteredDataSet.indexOf(this.state.firstClickedItem);
const lastItemIndex = this.state.filteredDataSet.indexOf(item);
const itemsInRange = _.filter(this.state.filteredDataSet, (item, index) => {
return isBetween(index, firstItemIndex, lastItemIndex);
});
const value = item.Checked;
_.forEach(itemsInRange, (i) => {
if (!this.allowSelection(i)) {
return;
}
i.Checked = value;
});
this.state.firstClickedItem = item;
} else if (event) {
this.state.firstClickedItem = item;
}
this.state.selectedItems = this.state.filteredDataSet.filter(i => i.Checked);
if (event && this.state.selectAll && this.state.selectedItems.length !== this.state.filteredDataSet.length) {
this.settings = {
open: false,
repeater: {
autoRefresh: false,
refreshRate: '30',
},
};
this.resetSelectionState = function () {
this.state.selectAll = false;
}
this.onSelectionChanged();
};
this.state.selectedItems = [];
_.map(this.state.filteredDataSet, (item) => (item.Checked = false));
};
this.selectAll = function() {
this.state.firstClickedItem = null;
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
var item = this.state.filteredDataSet[i];
if (this.allowSelection(item) && item.Checked !== this.state.selectAll) {
item.Checked = this.state.selectAll;
this.selectItem(item);
this.onTextFilterChange = function () {
DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
};
this.changeOrderBy = function (orderField) {
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
this.state.orderBy = orderField;
DatatableService.setDataTableOrder(this.tableKey, orderField, this.state.reverseOrder);
};
this.selectItem = function (item, event) {
// Handle range select using shift
if (event && event.originalEvent.shiftKey && this.state.firstClickedItem) {
const firstItemIndex = this.state.filteredDataSet.indexOf(this.state.firstClickedItem);
const lastItemIndex = this.state.filteredDataSet.indexOf(item);
const itemsInRange = _.filter(this.state.filteredDataSet, (item, index) => {
return isBetween(index, firstItemIndex, lastItemIndex);
});
const value = item.Checked;
_.forEach(itemsInRange, (i) => {
if (!this.allowSelection(i)) {
return;
}
i.Checked = value;
});
this.state.firstClickedItem = item;
} else if (event) {
this.state.firstClickedItem = item;
}
}
this.onSelectionChanged();
};
this.state.selectedItems = this.state.filteredDataSet.filter((i) => i.Checked);
if (event && this.state.selectAll && this.state.selectedItems.length !== this.state.filteredDataSet.length) {
this.state.selectAll = false;
}
this.onSelectionChanged();
};
/**
* Override this method to allow/deny selection
*/
this.allowSelection = function(/*item*/) {
return true;
}
this.selectAll = function () {
this.state.firstClickedItem = null;
for (var i = 0; i < this.state.filteredDataSet.length; i++) {
var item = this.state.filteredDataSet[i];
if (this.allowSelection(item) && item.Checked !== this.state.selectAll) {
item.Checked = this.state.selectAll;
this.selectItem(item);
}
}
this.onSelectionChanged();
};
/**
* Override this method to prepare data table
*/
this.prepareTableFromDataset = function() {
return;
}
/**
* Override this method to allow/deny selection
*/
this.allowSelection = function (/*item*/) {
return true;
};
/**
* Override this method to execute code after selection changed on datatable
*/
this.onSelectionChanged = function() {
return;
}
/**
* Override this method to prepare data table
*/
this.prepareTableFromDataset = function () {
return;
};
this.changePaginationLimit = function() {
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
};
/**
* Override this method to execute code after selection changed on datatable
*/
this.onSelectionChanged = function () {
return;
};
this.setDefaults = function() {
this.showTextFilter = this.showTextFilter ? this.showTextFilter : false;
this.state.reverseOrder = this.reverseOrder ? this.reverseOrder : false;
this.state.paginatedItemLimit = PaginationService.getPaginationLimit(this.tableKey);
}
this.changePaginationLimit = function () {
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
};
/**
* Duplicate this function when extending GenericDatatableController
* Extending-controller's bindings are not accessible there
* For more details see the following comments
* https://github.com/portainer/portainer/pull/2877#issuecomment-503333425
* https://github.com/portainer/portainer/pull/2877#issuecomment-503537249
*/
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
this.setDefaults = function () {
this.showTextFilter = this.showTextFilter ? this.showTextFilter : false;
this.state.reverseOrder = this.reverseOrder ? this.reverseOrder : false;
this.state.paginatedItemLimit = PaginationService.getPaginationLimit(this.tableKey);
};
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
/**
* Duplicate this function when extending GenericDatatableController
* Extending-controller's bindings are not accessible there
* For more details see the following comments
* https://github.com/portainer/portainer/pull/2877#issuecomment-503333425
* https://github.com/portainer/portainer/pull/2877#issuecomment-503537249
*/
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
/**
* REPEATER SECTION
*/
this.repeater = undefined;
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
this.$onDestroy = function() {
this.stopRepeater();
};
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
this.stopRepeater = function() {
if (angular.isDefined(this.repeater)) {
$interval.cancel(this.repeater);
this.repeater = undefined;
}
}
/**
* REPEATER SECTION
*/
this.repeater = undefined;
this.startRepeater = function() {
this.repeater = $interval(() => {
this.refreshCallback();
}, this.settings.repeater.refreshRate * 1000);
}
this.$onDestroy = function () {
this.stopRepeater();
};
this.onSettingsRepeaterChange = function() {
this.stopRepeater();
if (angular.isDefined(this.refreshCallback) && this.settings.repeater.autoRefresh) {
this.startRepeater();
$('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500);
}
DatatableService.setDataTableSettings(this.tableKey, this.settings);
}
/**
* !REPEATER SECTION
*/
}]);
this.stopRepeater = function () {
if (angular.isDefined(this.repeater)) {
$interval.cancel(this.repeater);
this.repeater = undefined;
}
};
this.startRepeater = function () {
this.repeater = $interval(() => {
this.refreshCallback();
}, this.settings.repeater.refreshRate * 1000);
};
this.onSettingsRepeaterChange = function () {
this.stopRepeater();
if (angular.isDefined(this.refreshCallback) && this.settings.repeater.autoRefresh) {
this.startRepeater();
$('#refreshRateChange').show();
$('#refreshRateChange').fadeOut(1500);
}
DatatableService.setDataTableSettings(this.tableKey, this.settings);
};
/**
* !REPEATER SECTION
*/
},
]);

View file

@ -2,22 +2,25 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.groups.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add group
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.groups.new"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add group </button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -38,18 +41,19 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.groups.group({id: item.Id})">{{ item.Name }}</a>
</td>
<td>
<a ui-sref="portainer.groups.group.access({id: item.Id})" ng-if="$ctrl.accessManagement">
<i class="fa fa-users" aria-hidden="true"></i> Manage access
</a>
<a ui-sref="portainer.groups.group.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
@ -62,9 +66,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -9,6 +9,6 @@ angular.module('portainer.app').component('groupsDatatable', {
orderBy: '@',
reverseOrder: '<',
accessManagement: '<',
removeAction: '<'
}
removeAction: '<',
},
});

View file

@ -2,22 +2,25 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar" ng-if="$ctrl.accessManagement">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.registries.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.registries.new"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add registry </button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -45,10 +48,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox" ng-if="$ctrl.accessManagement">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.registries.registry({id: item.Id})" ng-if="$ctrl.accessManagement">{{ item.Name }}</a>
@ -59,13 +65,20 @@
{{ item.URL }}
</td>
<td>
<a ui-sref="portainer.registries.registry.access({id: item.Id})" ng-if="$ctrl.accessManagement">
<i class="fa fa-users" aria-hidden="true"></i> Manage access
</a>
<a ui-sref="portainer.registries.registry.access({id: item.Id})" ng-if="$ctrl.accessManagement"> <i class="fa fa-users" aria-hidden="true"></i> Manage access </a>
<a ui-sref="portainer.registries.registry.repositories({id: item.Id})" ng-if="$ctrl.registryManagement && $ctrl.canBrowse(item)" class="space-left">
<i class="fa fa-search" aria-hidden="true"></i> Browse
</a>
<a ui-sref="portainer.extensions.extension({id: 1})" ng-if="!$ctrl.registryManagement && $ctrl.canBrowse(item)" class="space-left" style="color: #767676" tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="Feature available via an extension">
<a
ui-sref="portainer.extensions.extension({id: 1})"
ng-if="!$ctrl.registryManagement && $ctrl.canBrowse(item)"
class="space-left"
style="color: #767676;"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Feature available via an extension"
>
<i class="fa fa-search" aria-hidden="true"></i> Browse (extension)
</a>
</td>
@ -80,9 +93,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -11,6 +11,6 @@ angular.module('portainer.app').component('registriesDatatable', {
accessManagement: '<',
removeAction: '<',
registryManagement: '<',
canBrowse: '<'
}
canBrowse: '<',
},
});

View file

@ -11,7 +11,7 @@
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }" />
</div>
<div class="table-responsive">
<table class="table table-hover table-filters nowrap-cells">
@ -48,21 +48,25 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter: $ctrl.applyFilters | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
>
<td>
{{ item.Endpoint.Name }}
<a ng-if="item.Edge" ng-click="$ctrl.getEdgeTaskLogs(item.EndpointId, item.Id)"><i class="fa fa-download" aria-hidden="true" style="margin-left: 5px; margin-right: 2px;"></i> Download logs</a>
<a ng-if="item.Edge" ng-click="$ctrl.getEdgeTaskLogs(item.EndpointId, item.Id)"
><i class="fa fa-download" aria-hidden="true" style="margin-left: 5px; margin-right: 2px;"></i> Download logs</a
>
</td>
<td>
<a ng-if="!item.Edge" ng-click="$ctrl.goToContainerLogs(item.EndpointId, item.Id)">{{ item.Id | truncate: 32 }}</a>
<span ng-if="item.Edge">-</span>
</td>
<td>
<span ng-if="!item.Edge" class="label label-{{ item.Status|containerstatusbadge }}">{{ item.Status }}</span>
<span ng-if="!item.Edge" class="label label-{{ item.Status | containerstatusbadge }}">{{ item.Status }}</span>
<span ng-if="item.Edge">-</span>
</td>
<td>
<span ng-if="!item.Edge">{{ item.Created | getisodatefromtimestamp}}</span>
<span ng-if="!item.Edge">{{ item.Created | getisodatefromtimestamp }}</span>
<span ng-if="item.Edge">-</span>
</td>
</tr>

View file

@ -9,6 +9,6 @@ angular.module('portainer.docker').component('scheduleTasksDatatable', {
orderBy: '@',
reverseOrder: '<',
goToContainerLogs: '<',
getEdgeTaskLogs: '<'
}
getEdgeTaskLogs: '<',
},
});

View file

@ -2,22 +2,17 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.schedules.new">
<i class="fa fa-plus space-right" aria-hidden="true"></i>Add schedule
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.schedules.new"> <i class="fa fa-plus space-right" aria-hidden="true"></i>Add schedule </button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }" />
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -51,10 +46,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)" />
<label for="select_{{ $index }}"></label>
</span>
<span ng-if="item.JobType !== 1">{{ item.Name }}</span>
@ -75,9 +73,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -8,6 +8,6 @@ angular.module('portainer.app').component('schedulesDatatable', {
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
removeAction: '<'
}
removeAction: '<',
},
});

View file

@ -1,47 +1,48 @@
angular.module('portainer.app')
.controller('SchedulesDatatableController', ['$scope', '$controller', 'DatatableService',
function ($scope, $controller, DatatableService) {
angular.module('portainer.app').controller('SchedulesDatatableController', [
'$scope',
'$controller',
'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
/**
* Do not allow items
*/
this.allowSelection = function (item) {
return item.JobType === 1;
};
/**
* Do not allow items
*/
this.allowSelection = function(item) {
return item.JobType === 1
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
},
]);

View file

@ -2,9 +2,7 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
<div class="settings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.settings.open }" uib-dropdown dropdown-append-to-body auto-close="disabled" is-open="$ctrl.settings.open">
<span uib-dropdown-toggle><i class="fa fa-cog" aria-hidden="true"></i> Settings</span>
@ -16,7 +14,7 @@
<div class="menuContent">
<div>
<div class="md-checkbox">
<input id="setting_auto_refresh" type="checkbox" ng-model="$ctrl.settings.repeater.autoRefresh" ng-change="$ctrl.onSettingsRepeaterChange()"/>
<input id="setting_auto_refresh" type="checkbox" ng-model="$ctrl.settings.repeater.autoRefresh" ng-change="$ctrl.onSettingsRepeaterChange()" />
<label for="setting_auto_refresh">Auto refresh</label>
</div>
<div ng-if="$ctrl.settings.repeater.autoRefresh">
@ -45,8 +43,13 @@
</div>
</div>
<div class="actionBar" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
<button type="button" class="btn btn-sm btn-danger" authorization="PortainerStackDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button
type="button"
class="btn btn-sm btn-danger"
authorization="PortainerStackDelete"
ng-disabled="$ctrl.state.selectedItemCount === 0"
ng-click="$ctrl.removeAction($ctrl.state.selectedItems)"
>
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
<button type="button" class="btn btn-sm btn-primary" ui-sref="portainer.stacks.newstack" authorization="PortainerStackCreate">
@ -55,7 +58,15 @@
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -90,10 +101,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox" ng-if="!$ctrl.offlineMode" authorization="PortainerStackCreate, PortainerStackDelete">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="!$ctrl.allowSelection(item)" />
<label for="select_{{ $index }}"></label>
</span>
<a ng-if="!$ctrl.offlineMode" ui-sref="portainer.stacks.stack({ name: item.Name, id: item.Id, type: item.Type, external: item.External })">{{ item.Name }}</a>
@ -101,7 +115,14 @@
</td>
<td>{{ item.Type === 1 ? 'Swarm' : 'Compose' }}</td>
<td>
<span ng-if="item.External" class="interactive" tooltip-append-to-body="true" tooltip-placement="bottom" tooltip-class="portainer-tooltip" uib-tooltip="This stack was created outside of Portainer. Control over this stack is limited.">
<span
ng-if="item.External"
class="interactive"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="This stack was created outside of Portainer. Control over this stack is limited."
>
Limited <i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-left: 2px;"></i>
</span>
<span ng-if="!item.External">Total</span>
@ -123,9 +144,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -11,6 +11,6 @@ angular.module('portainer.app').component('stacksDatatable', {
showOwnershipColumn: '<',
removeAction: '<',
offlineMode: '<',
refreshCallback: '<'
}
refreshCallback: '<',
},
});

View file

@ -1,47 +1,48 @@
angular.module('portainer.app')
.controller('StacksDatatableController', ['$scope', '$controller', 'DatatableService',
function ($scope, $controller, DatatableService) {
angular.module('portainer.app').controller('StacksDatatableController', [
'$scope',
'$controller',
'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
/**
* Do not allow external items
*/
this.allowSelection = function (item) {
return !(item.External && item.Type === 2);
};
/**
* Do not allow external items
*/
this.allowSelection = function(item) {
return !(item.External && item.Type === 2);
}
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
}]);
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
},
]);

View file

@ -2,19 +2,23 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -34,10 +38,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
{{ item.Name }}
@ -53,9 +60,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -8,6 +8,6 @@ angular.module('portainer.app').component('tagsDatatable', {
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
removeAction: '<'
}
removeAction: '<',
},
});

View file

@ -2,19 +2,23 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -34,10 +38,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.teams.team({id: item.Id})">{{ item.Name }}</a>
@ -53,9 +60,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -8,6 +8,6 @@ angular.module('portainer.app').component('teamsDatatable', {
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
removeAction: '<'
}
removeAction: '<',
},
});

View file

@ -2,19 +2,23 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar">
<button type="button" class="btn btn-sm btn-danger"
ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<button type="button" class="btn btn-sm btn-danger" ng-disabled="$ctrl.state.selectedItemCount === 0" ng-click="$ctrl.removeAction($ctrl.state.selectedItems)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." ng-model-options="{ debounce: 300 }">
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -48,10 +52,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" />
<label for="select_{{ $index }}"></label>
</span>
<a ui-sref="portainer.users.user({id: item.Id})">{{ item.Username }}</a>
@ -65,7 +72,7 @@
</span>
</td>
<td>
<span ng-if="item.Id === 1 || $ctrl.authenticationMethod !== 2 && $ctrl.authenticationMethod !== 3">Internal</span>
<span ng-if="item.Id === 1 || ($ctrl.authenticationMethod !== 2 && $ctrl.authenticationMethod !== 3)">Internal</span>
<span ng-if="item.Id !== 1 && $ctrl.authenticationMethod === 2">LDAP</span>
<span ng-if="item.Id !== 1 && $ctrl.authenticationMethod === 3">OAuth</span>
</td>
@ -80,9 +87,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -9,6 +9,6 @@ angular.module('portainer.app').component('usersDatatable', {
orderBy: '@',
reverseOrder: '<',
removeAction: '<',
authenticationMethod: '<'
}
authenticationMethod: '<',
},
});

View file

@ -23,7 +23,7 @@ class EndpointItemController {
}
const tagNames = PortainerEndpointTagHelper.idsToTagNames(this.tags, this.model.TagIds);
return _.join(tagNames, ',')
return _.join(tagNames, ',');
}
$onInit() {

View file

@ -6,7 +6,6 @@
</span>
<span class="col-sm-12">
<div class="blocklist-item-line endpoint-item">
<span>
<span class="blocklist-item-title endpoint-item">
@ -17,7 +16,7 @@
<span ng-if="$ctrl.model.EdgeID"><i class="fas fa-link"></i> associated</span>
<span ng-if="!$ctrl.model.EdgeID"><i class="fas fa-unlink"></i> <s>associated</s></span>
</span>
<span class="label label-{{ $ctrl.model.Status|endpointstatusbadge }}" ng-if="$ctrl.model.Type !== 4">
<span class="label label-{{ $ctrl.model.Status | endpointstatusbadge }}" ng-if="$ctrl.model.Type !== 4">
{{ $ctrl.model.Status === 1 ? 'up' : 'down' }}
</span>
<span class="space-left small text-muted" ng-if="$ctrl.model.Snapshots[0]">
@ -26,15 +25,8 @@
</span>
</span>
<span>
<span class="small" ng-if="$ctrl.model.GroupName">
Group: {{ $ctrl.model.GroupName }}
</span>
<button
ng-if="$ctrl.isAdmin"
class="btn btn-link btn-xs"
ng-click="$ctrl.editEndpoint($event)"><i class="fa fa-pencil-alt"></i>
</button>
<span class="small" ng-if="$ctrl.model.GroupName"> Group: {{ $ctrl.model.GroupName }} </span>
<button ng-if="$ctrl.isAdmin" class="btn btn-link btn-xs" ng-click="$ctrl.editEndpoint($event)"><i class="fa fa-pencil-alt"></i> </button>
</span>
</div>
@ -42,13 +34,16 @@
<span class="blocklist-item-desc">
<span>
<span style="padding: 0 7px 0 0;">
<i class="fa fa-th-list space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].StackCount }} {{ $ctrl.model.Snapshots[0].StackCount === 1 ? 'stack' : 'stacks' }}
<i class="fa fa-th-list space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].StackCount }}
{{ $ctrl.model.Snapshots[0].StackCount === 1 ? 'stack' : 'stacks' }}
</span>
<span style="padding: 0 7px 0 7px;" ng-if="$ctrl.model.Snapshots[0].Swarm">
<i class="fa fa-list-alt space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ServiceCount }} {{ $ctrl.model.Snapshots[0].ServiceCount === 1 ? 'service' : 'services' }}
<i class="fa fa-list-alt space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ServiceCount }}
{{ $ctrl.model.Snapshots[0].ServiceCount === 1 ? 'service' : 'services' }}
</span>
<span style="padding: 0 7px 0 7px;">
<i class="fa fa-server space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount }} {{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount === 1 ? 'container' : 'containers' }}
<i class="fa fa-server space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount }}
{{ $ctrl.model.Snapshots[0].RunningContainerCount + $ctrl.model.Snapshots[0].StoppedContainerCount === 1 ? 'container' : 'containers' }}
<span ng-if="$ctrl.model.Snapshots[0].RunningContainerCount > 0 || $ctrl.model.Snapshots[0].StoppedContainerCount > 0">
-
<i class="fa fa-power-off green-icon" aria-hidden="true"></i> {{ $ctrl.model.Snapshots[0].RunningContainerCount }}
@ -59,15 +54,18 @@
</span>
</span>
<span style="padding: 0 7px 0 7px;">
<i class="fa fa-cubes space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].VolumeCount }} {{ $ctrl.model.Snapshots[0].VolumeCount === 1 ? 'volume' : 'volumes' }}
<i class="fa fa-cubes space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].VolumeCount }}
{{ $ctrl.model.Snapshots[0].VolumeCount === 1 ? 'volume' : 'volumes' }}
</span>
<span style="padding: 0 7px 0 7px;">
<i class="fa fa-clone space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ImageCount }} {{ $ctrl.model.Snapshots[0].ImageCount === 1 ? 'image' : 'images' }}
<i class="fa fa-clone space-right" aria-hidden="true"></i>{{ $ctrl.model.Snapshots[0].ImageCount }}
{{ $ctrl.model.Snapshots[0].ImageCount === 1 ? 'image' : 'images' }}
</span>
</span>
</span>
<span class="small text-muted">
{{ $ctrl.model.Snapshots[0].Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.model.Snapshots[0].DockerVersion }} <span ng-if="$ctrl.model.Type === 2">+ <i class="fa fa-bolt" aria-hidden="true"></i> Agent</span>
{{ $ctrl.model.Snapshots[0].Swarm ? 'Swarm' : 'Standalone' }} {{ $ctrl.model.Snapshots[0].DockerVersion }}
<span ng-if="$ctrl.model.Type === 2">+ <i class="fa fa-bolt" aria-hidden="true"></i> Agent</span>
</span>
</div>
@ -85,9 +83,7 @@
</span>
<span class="space-left space-right">-</span>
</span>
<span ng-if="$ctrl.endpointTags.length === 0">
<i class="fa fa-tags" aria-hidden="true"></i> No tags
</span>
<span ng-if="$ctrl.endpointTags.length === 0"> <i class="fa fa-tags" aria-hidden="true"></i> No tags </span>
<span ng-if="$ctrl.endpointTags.length > 0">
<i class="fa fa-tags" aria-hidden="true"></i>
{{ $ctrl.endpointTags }}
@ -97,7 +93,6 @@
{{ $ctrl.model.URL | stripprotocol }}
</span>
</div>
</span>
</div>
</div>

View file

@ -1,6 +1,8 @@
import _ from 'lodash-es';
angular.module('portainer.app').controller('EndpointListController', ['DatatableService', 'PaginationService',
angular.module('portainer.app').controller('EndpointListController', [
'DatatableService',
'PaginationService',
function EndpointListController(DatatableService, PaginationService) {
this.state = {
totalFilteredEndpoints: this.totalCount,
@ -8,21 +10,21 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
filteredEndpoints: [],
paginatedItemLimit: '10',
pageNumber: 1,
loading: true
loading: true,
};
this.$onChanges = function(changesObj) {
this.$onChanges = function (changesObj) {
this.handleEndpointsChange(changesObj.endpoints);
}
};
this.handleEndpointsChange = function(endpoints) {
this.handleEndpointsChange = function (endpoints) {
if (!endpoints || !endpoints.currentValue) {
return;
}
this.onTextFilterChange();
}
};
this.onTextFilterChange = function() {
this.onTextFilterChange = function () {
this.state.loading = true;
var filterValue = this.state.textFilter;
DatatableService.setDataTableTextFilters(this.tableKey, filterValue);
@ -32,23 +34,23 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
this.state.filteredEndpoints = frontEndpointFilter(this.endpoints, this.tags, filterValue);
this.state.loading = false;
}
}
};
function frontEndpointFilter(endpoints, tags, filterValue) {
if (!endpoints || !endpoints.length || !filterValue) {
return endpoints;
}
var keywords = filterValue.split(' ');
return _.filter(endpoints, function(endpoint) {
return _.filter(endpoints, function (endpoint) {
var statusString = convertStatusToString(endpoint.Status);
return _.every(keywords, function(keyword) {
return _.every(keywords, function (keyword) {
var lowerCaseKeyword = keyword.toLowerCase();
return (
_.includes(endpoint.Name.toLowerCase(), lowerCaseKeyword) ||
_.includes(endpoint.GroupName.toLowerCase(), lowerCaseKeyword) ||
_.includes(endpoint.URL.toLowerCase(), lowerCaseKeyword) ||
_.some(endpoint.TagIds, tagId => {
const tag = tags.find(t => t.Id === tagId);
_.some(endpoint.TagIds, (tagId) => {
const tag = tags.find((t) => t.Id === tagId);
if (!tag) {
return false;
}
@ -60,30 +62,29 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
});
}
this.hasBackendPagination = function() {
this.hasBackendPagination = function () {
return this.totalCount && this.totalCount > 100;
}
};
this.paginationChangedAction = function() {
this.paginationChangedAction = function () {
if (this.hasBackendPagination()) {
this.state.loading = true;
this.state.filteredEndpoints = [];
const start = (this.state.pageNumber - 1) * this.state.paginatedItemLimit + 1;
this.retrievePage(start, this.state.paginatedItemLimit, this.state.textFilter)
.then((data) => {
this.retrievePage(start, this.state.paginatedItemLimit, this.state.textFilter).then((data) => {
this.state.filteredEndpoints = data.endpoints;
this.state.totalFilteredEndpoints = data.totalCount;
this.state.loading = false;
});
}
}
};
this.pageChangeHandler = function(newPageNumber) {
this.pageChangeHandler = function (newPageNumber) {
this.state.pageNumber = newPageNumber;
this.paginationChangedAction();
}
};
this.changePaginationLimit = function() {
this.changePaginationLimit = function () {
PaginationService.setPaginationLimit(this.tableKey, this.state.paginatedItemLimit);
this.paginationChangedAction();
};
@ -92,7 +93,7 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
return status === 1 ? 'up' : 'down';
}
this.$onInit = function() {
this.$onInit = function () {
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
this.state.paginatedItemLimit = PaginationService.getPaginationLimit(this.tableKey);
if (textFilter !== null) {
@ -100,6 +101,6 @@ angular.module('portainer.app').controller('EndpointListController', ['Datatable
} else {
this.paginationChangedAction();
}
}
}
};
},
]);

View file

@ -11,8 +11,8 @@ angular.module('portainer.app').component('endpointList', {
snapshotAction: '<',
showSnapshotAction: '<',
editAction: '<',
isAdmin:'<',
isAdmin: '<',
totalCount: '<',
retrievePage: '<'
}
retrievePage: '<',
},
});

View file

@ -1,17 +1,12 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="actionBar" ng-if="$ctrl.showSnapshotAction">
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.snapshotAction()">
<i class="fa fa-sync space-right" aria-hidden="true"></i>Refresh
</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.snapshotAction()"> <i class="fa fa-sync space-right" aria-hidden="true"></i>Refresh </button>
</div>
<div class="searchBar">
@ -22,24 +17,28 @@
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
ng-model-options="{ debounce: 300 }"
placeholder="Search by name, group, tag, status, URL..." auto-focus>
placeholder="Search by name, group, tag, status, URL..."
auto-focus
/>
</div>
<div class="blocklist">
<endpoint-item ng-if="$ctrl.hasBackendPagination()"
<endpoint-item
ng-if="$ctrl.hasBackendPagination()"
dir-paginate="endpoint in $ctrl.state.filteredEndpoints | itemsPerPage: $ctrl.state.paginatedItemLimit"
model="endpoint"
total-items="$ctrl.state.totalFilteredEndpoints"
on-select="$ctrl.dashboardAction"
on-edit="$ctrl.editAction"
on-select="($ctrl.dashboardAction)"
on-edit="($ctrl.editAction)"
is-admin="$ctrl.isAdmin"
tags="$ctrl.tags"
></endpoint-item>
<endpoint-item ng-if="!$ctrl.hasBackendPagination()"
<endpoint-item
ng-if="!$ctrl.hasBackendPagination()"
dir-paginate="endpoint in $ctrl.state.filteredEndpoints | itemsPerPage: $ctrl.state.paginatedItemLimit"
model="endpoint"
on-select="$ctrl.dashboardAction"
on-edit="$ctrl.editAction"
on-select="($ctrl.dashboardAction)"
on-edit="($ctrl.editAction)"
is-admin="$ctrl.isAdmin"
tags="$ctrl.tags"
></endpoint-item>

View file

@ -2,8 +2,8 @@ angular.module('portainer.app').component('endpointSelector', {
templateUrl: './endpointSelector.html',
controller: 'EndpointSelectorController',
bindings: {
'model': '=',
'endpoints': '<',
'groups': '<'
}
model: '=',
endpoints: '<',
groups: '<',
},
});

View file

@ -1,14 +1,13 @@
import _ from 'lodash-es';
angular.module('portainer.app')
.controller('EndpointSelectorController', function () {
angular.module('portainer.app').controller('EndpointSelectorController', function () {
var ctrl = this;
this.sortGroups = function(groups) {
this.sortGroups = function (groups) {
return _.sortBy(groups, ['name']);
};
this.groupEndpoints = function(endpoint) {
this.groupEndpoints = function (endpoint) {
for (var i = 0; i < ctrl.availableGroups.length; i++) {
var group = ctrl.availableGroups[i];
@ -18,14 +17,13 @@ angular.module('portainer.app')
}
};
this.$onInit = function() {
this.$onInit = function () {
this.availableGroups = filterEmptyGroups(this.groups, this.endpoints);
};
function filterEmptyGroups(groups, endpoints) {
return groups.filter(function f(group) {
for (var i = 0; i < endpoints.length; i++) {
var endpoint = endpoints[i];
if (endpoint.GroupId === group.Id) {
return true;

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('porEndpointSecurity', {
formData: '=',
// The component will use this object to initialize the default values
// if present.
endpoint: '<'
}
endpoint: '<',
},
});

View file

@ -6,9 +6,7 @@
TLS
<portainer-tooltip position="bottom" message="Enable this option if you need to connect to the Docker endpoint with TLS."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.formData.TLS"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.formData.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
@ -19,16 +17,17 @@
<div class="form-group" ng-if="$ctrl.formData.TLS">
<div class="col-sm-12">
<span class="small text-muted">
You can find out more information about how to protect a Docker environment with TLS in the <a href="https://docs.docker.com/engine/security/https/" target="_blank">Docker documentation</a>.
You can find out more information about how to protect a Docker environment with TLS in the
<a href="https://docs.docker.com/engine/security/https/" target="_blank">Docker documentation</a>.
</span>
</div>
</div>
<div class="form-group"></div>
<!-- endpoint-tls-mode -->
<div class="form-group" style="margin-bottom: 0" ng-if="$ctrl.formData.TLS">
<div class="form-group" style="margin-bottom: 0;" ng-if="$ctrl.formData.TLS">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca">
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
<label for="tls_client_ca">
<div class="boxselector_header">
<i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
@ -38,7 +37,7 @@
</label>
</div>
<div>
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca">
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca" />
<label for="tls_client_noca">
<div class="boxselector_header">
<i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
@ -48,7 +47,7 @@
</label>
</div>
<div>
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca">
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca" />
<label for="tls_ca">
<div class="boxselector_header">
<i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>
@ -58,7 +57,7 @@
</label>
</div>
<div>
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only">
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only" />
<label for="tls_only">
<div class="boxselector_header">
<i class="fa fa-shield-alt" aria-hidden="true" style="margin-right: 2px;"></i>

View file

@ -1,32 +1,33 @@
angular.module('portainer.app')
.controller('porEndpointSecurityController', [function () {
var ctrl = this;
angular.module('portainer.app').controller('porEndpointSecurityController', [
function () {
var ctrl = this;
function initComponent() {
if (ctrl.endpoint) {
var endpoint = ctrl.endpoint;
var TLS = endpoint.TLSConfig.TLS;
ctrl.formData.TLS = TLS;
var CACert = endpoint.TLSConfig.TLSCACert;
ctrl.formData.TLSCACert = CACert;
var cert = endpoint.TLSConfig.TLSCert;
ctrl.formData.TLSCert = cert;
var key = endpoint.TLSConfig.TLSKey;
ctrl.formData.TLSKey = key;
function initComponent() {
if (ctrl.endpoint) {
var endpoint = ctrl.endpoint;
var TLS = endpoint.TLSConfig.TLS;
ctrl.formData.TLS = TLS;
var CACert = endpoint.TLSConfig.TLSCACert;
ctrl.formData.TLSCACert = CACert;
var cert = endpoint.TLSConfig.TLSCert;
ctrl.formData.TLSCert = cert;
var key = endpoint.TLSConfig.TLSKey;
ctrl.formData.TLSKey = key;
if (TLS) {
if (CACert && cert && key) {
ctrl.formData.TLSMode = 'tls_client_ca';
} else if (cert && key) {
ctrl.formData.TLSMode = 'tls_client_noca';
} else if (CACert) {
ctrl.formData.TLSMode = 'tls_ca';
} else {
ctrl.formData.TLSMode = 'tls_only';
if (TLS) {
if (CACert && cert && key) {
ctrl.formData.TLSMode = 'tls_client_ca';
} else if (cert && key) {
ctrl.formData.TLSMode = 'tls_client_noca';
} else if (CACert) {
ctrl.formData.TLSMode = 'tls_ca';
} else {
ctrl.formData.TLSMode = 'tls_only';
}
}
}
}
}
initComponent();
}]);
initComponent();
},
]);

View file

@ -3,6 +3,6 @@ angular.module('portainer.app').component('extensionItem', {
controller: 'ExtensionItemController',
bindings: {
model: '<',
currentDate: '<'
}
currentDate: '<',
},
});

View file

@ -1,13 +1,13 @@
angular.module('portainer.app')
.controller('ExtensionItemController', ['$state',
function($state) {
angular.module('portainer.app').controller('ExtensionItemController', [
'$state',
function ($state) {
var ctrl = this;
ctrl.goToExtensionView = goToExtensionView;
var ctrl = this;
ctrl.goToExtensionView = goToExtensionView;
function goToExtensionView() {
if (ctrl.model.Available) {
$state.go('portainer.extensions.extension', { id: ctrl.model.Id });
}
function goToExtensionView() {
if (ctrl.model.Available) {
$state.go('portainer.extensions.extension', { id: ctrl.model.Id });
}
}]);
}
},
]);

View file

@ -2,6 +2,6 @@ angular.module('portainer.app').component('extensionList', {
templateUrl: './extensionList.html',
bindings: {
extensions: '<',
currentDate: '<'
}
currentDate: '<',
},
});

View file

@ -1,20 +1,13 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i> Available extensions
</div>
<div class="toolBarTitle"> <i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i> Available extensions </div>
</div>
<div class="blocklist">
<extension-item ng-repeat="extension in $ctrl.extensions"
model="extension"
current-date="$ctrl.currentDate"
></extension-item>
<extension-item ng-repeat="extension in $ctrl.extensions" model="extension" current-date="$ctrl.currentDate"></extension-item>
</div>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -1,69 +1,74 @@
angular.module('portainer.app')
.controller('JobFormController', ['$state', 'LocalStorage', 'EndpointService', 'EndpointProvider', 'Notifications',
function ($state, LocalStorage, EndpointService, EndpointProvider, Notifications) {
var ctrl = this;
angular.module('portainer.app').controller('JobFormController', [
'$state',
'LocalStorage',
'EndpointService',
'EndpointProvider',
'Notifications',
function ($state, LocalStorage, EndpointService, EndpointProvider, Notifications) {
var ctrl = this;
ctrl.$onInit = onInit;
ctrl.editorUpdate = editorUpdate;
ctrl.executeJob = executeJob;
ctrl.$onInit = onInit;
ctrl.editorUpdate = editorUpdate;
ctrl.executeJob = executeJob;
ctrl.state = {
Method: 'editor',
formValidationError: '',
actionInProgress: false
};
ctrl.state = {
Method: 'editor',
formValidationError: '',
actionInProgress: false,
};
ctrl.formValues = {
Image: 'ubuntu:latest',
JobFileContent: '',
JobFile: null
};
ctrl.formValues = {
Image: 'ubuntu:latest',
JobFileContent: '',
JobFile: null,
};
function onInit() {
var storedImage = LocalStorage.getJobImage();
if (storedImage) {
ctrl.formValues.Image = storedImage;
}
}
function editorUpdate(cm) {
ctrl.formValues.JobFileContent = cm.getValue();
}
function createJob(image, method) {
var endpointId = EndpointProvider.endpointID();
var nodeName = ctrl.nodeName;
if (method === 'editor') {
var jobFileContent = ctrl.formValues.JobFileContent;
return EndpointService.executeJobFromFileContent(image, jobFileContent, endpointId, nodeName);
function onInit() {
var storedImage = LocalStorage.getJobImage();
if (storedImage) {
ctrl.formValues.Image = storedImage;
}
}
var jobFile = ctrl.formValues.JobFile;
return EndpointService.executeJobFromFileUpload(image, jobFile, endpointId, nodeName);
}
function executeJob() {
var method = ctrl.state.Method;
if (method === 'editor' && ctrl.formValues.JobFileContent === '') {
ctrl.state.formValidationError = 'Script file content must not be empty';
return;
function editorUpdate(cm) {
ctrl.formValues.JobFileContent = cm.getValue();
}
var image = ctrl.formValues.Image;
LocalStorage.storeJobImage(image);
function createJob(image, method) {
var endpointId = EndpointProvider.endpointID();
var nodeName = ctrl.nodeName;
ctrl.state.actionInProgress = true;
createJob(image, method)
.then(function success() {
Notifications.success('Job successfully created');
$state.go('^');
})
.catch(function error(err) {
Notifications.error('Job execution failure', err);
})
.finally(function final() {
ctrl.state.actionInProgress = false;
});
}
}]);
if (method === 'editor') {
var jobFileContent = ctrl.formValues.JobFileContent;
return EndpointService.executeJobFromFileContent(image, jobFileContent, endpointId, nodeName);
}
var jobFile = ctrl.formValues.JobFile;
return EndpointService.executeJobFromFileUpload(image, jobFile, endpointId, nodeName);
}
function executeJob() {
var method = ctrl.state.Method;
if (method === 'editor' && ctrl.formValues.JobFileContent === '') {
ctrl.state.formValidationError = 'Script file content must not be empty';
return;
}
var image = ctrl.formValues.Image;
LocalStorage.storeJobImage(image);
ctrl.state.actionInProgress = true;
createJob(image, method)
.then(function success() {
Notifications.success('Job successfully created');
$state.go('^');
})
.catch(function error(err) {
Notifications.error('Job execution failure', err);
})
.finally(function final() {
ctrl.state.actionInProgress = false;
});
}
},
]);

View file

@ -3,7 +3,7 @@
<div class="form-group">
<label for="job_image" class="col-sm-1 control-label text-left">Image</label>
<div class="col-sm-11">
<input type="text" class="form-control" ng-model="$ctrl.formValues.Image" id="job_image" name="job_image" placeholder="e.g. ubuntu:latest" required auto-focus>
<input type="text" class="form-control" ng-model="$ctrl.formValues.Image" id="job_image" name="job_image" placeholder="e.g. ubuntu:latest" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="executeJobForm.job_image.$invalid">
@ -25,10 +25,10 @@
Job creation
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor">
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" />
<label for="method_editor">
<div class="boxselector_header">
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
@ -38,7 +38,7 @@
</label>
</div>
<div>
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload">
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" />
<label for="method_upload">
<div class="boxselector_header">
<i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
@ -57,11 +57,7 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor
identifier="execute-job-editor"
placeholder="# Define or paste the content of your script file here"
on-change="$ctrl.editorUpdate">
</code-editor>
<code-editor identifier="execute-job-editor" placeholder="# Define or paste the content of your script file here" on-change="($ctrl.editorUpdate)"> </code-editor>
</div>
</div>
</div>
@ -93,11 +89,14 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm"
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="$ctrl.state.actionInProgress || !executeJobForm.$valid
|| ($ctrl.state.Method === 'upload' && !$ctrl.formValues.JobFile)"
ng-click="$ctrl.executeJob()"
button-spinner="$ctrl.state.actionInProgress">
button-spinner="$ctrl.state.actionInProgress"
>
<span ng-hide="$ctrl.state.actionInProgress">Execute</span>
<span ng-show="$ctrl.state.actionInProgress">Starting job...</span>
</button>
@ -106,5 +105,5 @@
</span>
</div>
</div>
<!-- !actions -->
<!-- !actions -->
</form>

View file

@ -2,6 +2,6 @@ angular.module('portainer.app').component('executeJobForm', {
templateUrl: './execute-job-form.html',
controller: 'JobFormController',
bindings: {
nodeName: '<'
}
nodeName: '<',
},
});

View file

@ -16,6 +16,6 @@ angular.module('portainer.app').component('groupForm', {
formAction: '<',
formActionLabel: '@',
actionInProgress: '<',
onCreateTag: '<'
}
onCreateTag: '<',
},
});

View file

@ -3,7 +3,7 @@
<div class="form-group" ng-class="{ 'has-error': endpointGroupForm.group_name.$invalid }">
<label for="group_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="group_name" ng-model="$ctrl.model.Name" placeholder="e.g. my-group" required auto-focus>
<input type="text" class="form-control" name="group_name" ng-model="$ctrl.model.Name" placeholder="e.g. my-group" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="endpointGroupForm.group_name.$invalid">
@ -18,7 +18,7 @@
<div class="form-group">
<label for="group_description" class="col-sm-3 col-lg-2 control-label text-left">Description</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="group_description" ng-model="$ctrl.model.Description" placeholder="e.g. production environments...">
<input type="text" class="form-control" id="group_description" ng-model="$ctrl.model.Description" placeholder="e.g. production environments..." />
</div>
</div>
<!-- !description-input -->
@ -32,7 +32,7 @@
tags="$ctrl.availableTags"
model="$ctrl.model.TagIds"
allow-create="$ctrl.state.allowCreateTag"
on-create="$ctrl.onCreateTag"
on-create="($ctrl.onCreateTag)"
></tag-selector>
</div>
<!-- !tags -->
@ -43,8 +43,8 @@
</div>
<div class="form-group">
<div class="col-sm-12 small text-muted">
You can select which endpoint should be part of this group by moving them to the associated endpoints table. Simply click
on any endpoint entry to move it from one table to the other.
You can select which endpoint should be part of this group by moving them to the associated endpoints table. Simply click on any endpoint entry to move it from one table to
the other.
</div>
<div class="col-sm-12" style="margin-top: 20px;">
<!-- available-endpoints -->
@ -107,7 +107,13 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-click="$ctrl.formAction()" ng-disabled="$ctrl.actionInProgress || !endpointGroupForm.$valid" button-spinner="$ctrl.actionInProgress">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="$ctrl.formAction()"
ng-disabled="$ctrl.actionInProgress || !endpointGroupForm.$valid"
button-spinner="$ctrl.actionInProgress"
>
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
<span ng-show="$ctrl.actionInProgress">In progress...</span>
</button>

View file

@ -21,15 +21,15 @@ class GroupFormController {
limit: '10',
filter: '',
pageNumber: 1,
totalCount: 0
totalCount: 0,
},
associated: {
limit: '10',
filter: '',
pageNumber: 1,
totalCount: 0
totalCount: 0,
},
allowCreateTag: this.Authentication.isAdmin()
allowCreateTag: this.Authentication.isAdmin(),
};
}
associateEndpoint(endpoint) {
@ -37,11 +37,11 @@ class GroupFormController {
this.associatedEndpoints.push(endpoint);
} else if (this.pageType === 'edit') {
this.GroupService.addEndpoint(this.model.Id, endpoint)
.then(() => {
this.Notifications.success('Success', 'Endpoint successfully added to group');
this.reloadTablesContent();
})
.catch((err) => this.Notifications.error('Error', err, 'Unable to add endpoint to group'));
.then(() => {
this.Notifications.success('Success', 'Endpoint successfully added to group');
this.reloadTablesContent();
})
.catch((err) => this.Notifications.error('Error', err, 'Unable to add endpoint to group'));
}
}
@ -50,29 +50,27 @@ class GroupFormController {
_.remove(this.associatedEndpoints, (item) => item.Id === endpoint.Id);
} else if (this.pageType === 'edit') {
this.GroupService.removeEndpoint(this.model.Id, endpoint.Id)
.then(() => {
this.Notifications.success('Success', 'Endpoint successfully removed from group');
this.reloadTablesContent();
})
.catch((err) => this.Notifications.error('Error', err, 'Unable to remove endpoint from group'));
.then(() => {
this.Notifications.success('Success', 'Endpoint successfully removed from group');
this.reloadTablesContent();
})
.catch((err) => this.Notifications.error('Error', err, 'Unable to remove endpoint from group'));
}
}
reloadTablesContent() {
this.getPaginatedEndpointsByGroup(this.pageType, 'available');
this.getPaginatedEndpointsByGroup(this.pageType, 'associated');
this.GroupService.group(this.model.Id)
.then((data) => {
this.GroupService.group(this.model.Id).then((data) => {
this.model = data;
})
});
}
getPaginatedEndpointsByGroup(pageType, tableType) {
if (tableType === 'available') {
const context = this.state.available;
const start = (context.pageNumber - 1) * context.limit + 1;
this.EndpointService.endpointsByGroup(start, context.limit, context.filter, 1)
.then((data) => {
this.EndpointService.endpointsByGroup(start, context.limit, context.filter, 1).then((data) => {
this.availableEndpoints = data.value;
this.state.available.totalCount = data.totalCount;
});
@ -80,8 +78,7 @@ class GroupFormController {
const groupId = this.model.Id ? this.model.Id : 1;
const context = this.state.associated;
const start = (context.pageNumber - 1) * context.limit + 1;
this.EndpointService.endpointsByGroup(start, context.limit, context.filter, groupId)
.then((data) => {
this.EndpointService.endpointsByGroup(start, context.limit, context.filter, groupId).then((data) => {
this.associatedEndpoints = data.value;
this.state.associated.totalCount = data.totalCount;
});
@ -91,4 +88,4 @@ class GroupFormController {
}
angular.module('portainer.app').controller('GroupFormController', GroupFormController);
export default GroupFormController;
export default GroupFormController;

View file

@ -6,7 +6,7 @@
<div class="form-group">
<label for="registry_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-azure-registry" required auto-focus>
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-azure-registry" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="registryFormAzure.registry_name.$invalid">
@ -24,7 +24,7 @@
<portainer-tooltip position="bottom" message="URL of an Azure Container Registry. Any protocol will be stripped."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="myproject.azurecr.io" required>
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="myproject.azurecr.io" required />
</div>
</div>
<div class="form-group" ng-show="registryFormAzure.registry_url.$invalid">
@ -39,7 +39,7 @@
<div class="form-group">
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required>
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
</div>
</div>
<div class="form-group" ng-show="registryFormAzure.registry_username.$invalid">
@ -54,7 +54,7 @@
<div class="form-group">
<label for="registry_password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required>
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
</div>
</div>
<div class="form-group" ng-show="registryFormAzure.registry_password.$invalid">

View file

@ -4,6 +4,6 @@ angular.module('portainer.app').component('registryFormAzure', {
model: '=',
formAction: '<',
formActionLabel: '@',
actionInProgress: '<'
}
actionInProgress: '<',
},
});

View file

@ -4,8 +4,8 @@
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
Docker requires you to connect to a <a href="https://docs.docker.com/registry/deploying/#running-a-domain-registry" target="_blank">secure registry</a>.
You can find more information about how to connect to an insecure registry <a href="https://docs.docker.com/registry/insecure/" target="_blank">in the Docker documentation</a>.
Docker requires you to connect to a <a href="https://docs.docker.com/registry/deploying/#running-a-domain-registry" target="_blank">secure registry</a>. You can find more
information about how to connect to an insecure registry <a href="https://docs.docker.com/registry/insecure/" target="_blank">in the Docker documentation</a>.
</span>
</div>
<div class="col-sm-12 form-section-title">
@ -15,7 +15,7 @@
<div class="form-group">
<label for="registry_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-custom-registry" required auto-focus>
<input type="text" class="form-control" id="registry_name" name="registry_name" ng-model="$ctrl.model.Name" placeholder="my-custom-registry" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="registryFormCustom.registry_name.$invalid">
@ -33,7 +33,7 @@
<portainer-tooltip position="bottom" message="URL or IP address of a Docker registry. Any protocol will be stripped."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="10.0.0.10:5000 or myregistry.domain.tld" required>
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="10.0.0.10:5000 or myregistry.domain.tld" required />
</div>
</div>
<div class="form-group" ng-show="registryFormCustom.registry_url.$invalid">
@ -51,9 +51,7 @@
Authentication
<portainer-tooltip position="bottom" message="Enable this option if you need to specify credentials to connect to this registry."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.model.Authentication"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.Authentication" /><i></i> </label>
</div>
</div>
<!-- !authentication-checkbox -->
@ -62,7 +60,7 @@
<div class="form-group">
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required>
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
</div>
</div>
<div class="form-group" ng-show="registryFormCustom.registry_username.$invalid">
@ -77,7 +75,7 @@
<div class="form-group">
<label for="registry_password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required>
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
</div>
</div>
<div class="form-group" ng-show="registryFormCustom.registry_password.$invalid">

View file

@ -4,6 +4,6 @@ angular.module('portainer.app').component('registryFormCustom', {
model: '=',
formAction: '<',
formActionLabel: '@',
actionInProgress: '<'
}
actionInProgress: '<',
},
});

View file

@ -2,13 +2,11 @@
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" ng-change="$ctrl.onTextFilterChange()" placeholder="Search..." auto-focus />
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
@ -43,10 +41,13 @@
</tr>
</thead>
<tbody>
<tr dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))" ng-class="{active: item.Checked}">
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="$ctrl.disableSelection(item)"/>
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-click="$ctrl.selectItem(item, $event)" ng-disabled="$ctrl.disableSelection(item)" />
<label for="select_{{ $index }}"></label>
</span>
{{ item.Namespace }}
@ -68,9 +69,7 @@
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0">
{{ $ctrl.state.selectedItemCount }} item(s) selected
</div>
<div class="infoBar" ng-if="$ctrl.state.selectedItemCount !== 0"> {{ $ctrl.state.selectedItemCount }} item(s) selected </div>
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">

View file

@ -8,6 +8,6 @@ angular.module('portainer.app').component('gitlabProjectsDatatable', {
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
state: '='
}
state: '=',
},
});

View file

@ -1,48 +1,50 @@
angular.module('portainer.app')
.controller('GitlabProjectsDatatableController', ['$scope', '$controller', 'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', {$scope: $scope}));
angular.module('portainer.app').controller('GitlabProjectsDatatableController', [
'$scope',
'$controller',
'DatatableService',
function ($scope, $controller, DatatableService) {
angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
this.disableSelection = function(item) {
return !this.allowSelection(item);
this.disableSelection = function (item) {
return !this.allowSelection(item);
};
// based on RegistryGitlabProject model
this.allowSelection = function (item) {
return item.RegistryEnabled;
};
this.$onInit = function () {
this.setDefaults();
this.prepareTableFromDataset();
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
// based on RegistryGitlabProject model
this.allowSelection = function(item) {
return item.RegistryEnabled;
};
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
this.$onInit = function() {
this.setDefaults();
this.prepareTableFromDataset();
this.state.orderBy = this.orderBy;
var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
if (storedOrder !== null) {
this.state.reverseOrder = storedOrder.reverse;
this.state.orderBy = storedOrder.orderBy;
}
var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
if (textFilter !== null) {
this.state.textFilter = textFilter;
this.onTextFilterChange();
}
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
}
]);
var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
if (storedFilters !== null) {
this.filters = storedFilters;
}
if (this.filters && this.filters.state) {
this.filters.state.open = false;
}
var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
if (storedSettings !== null) {
this.settings = storedSettings;
this.settings.open = false;
}
this.onSettingsRepeaterChange();
};
},
]);

View file

@ -5,10 +5,12 @@
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>
For information on how to generate a Gitlab Personal Access Token, follow the <a href="https://gitlab.com/help/user/profile/personal_access_tokens.md" target="_blank">gitlab guide</a>.
For information on how to generate a Gitlab Personal Access Token, follow the
<a href="https://gitlab.com/help/user/profile/personal_access_tokens.md" target="_blank">gitlab guide</a>.
</p>
<p>
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> You must provide a token with <code>api</code> scope. Failure to do so will mean you can only push/pull from your registry but not manage it using the <a ui-sref="portainer.extensions.extension({id: 1})">registry management (extension)</a>.
<i class="fa fa-exclamation-circle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> You must provide a token with <code>api</code> scope. Failure to do so
will mean you can only push/pull from your registry but not manage it using the <a ui-sref="portainer.extensions.extension({id: 1})">registry management (extension)</a>.
</p>
</span>
</div>
@ -19,7 +21,7 @@
<div class="form-group">
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required>
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required />
</div>
</div>
<div class="form-group" ng-show="registryFormGitlab.registry_username.$invalid">
@ -32,10 +34,9 @@
<!-- !credentials-user -->
<!-- credentials-pat -->
<div class="form-group">
<label for="registry_perso_acc_token" class="col-sm-3 col-lg-2 control-label text-left">Personal Access Token
</label>
<label for="registry_perso_acc_token" class="col-sm-3 col-lg-2 control-label text-left">Personal Access Token </label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="registry_perso_acc_token" name="registry_perso_acc_token" ng-model="$ctrl.model.Token" required>
<input type="password" class="form-control" id="registry_perso_acc_token" name="registry_perso_acc_token" ng-model="$ctrl.model.Token" required />
</div>
</div>
<div class="form-group" ng-show="registryFormGitlab.registry_perso_acc_token.$invalid">
@ -65,7 +66,7 @@
<portainer-tooltip position="bottom" message="URL of Gitlab instance."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="instance_url" name="instance_url" ng-model="$ctrl.model.Gitlab.InstanceURL" placeholder="https://gitlab.com" required>
<input type="text" class="form-control" id="instance_url" name="instance_url" ng-model="$ctrl.model.Gitlab.InstanceURL" placeholder="https://gitlab.com" required />
</div>
</div>
<div class="form-group" ng-show="registryFormGitlab.instance_url.$invalid">
@ -83,7 +84,7 @@
<portainer-tooltip position="bottom" message="URL of Gitlab registry instance."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="https://registry.gitlab.com" required>
<input type="text" class="form-control" id="registry_url" name="registry_url" ng-model="$ctrl.model.URL" placeholder="https://registry.gitlab.com" required />
</div>
</div>
<div class="form-group" ng-show="registryFormGitlab.registry_url.$invalid">
@ -119,21 +120,28 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<gitlab-projects-datatable
title-text="Gitlab projects" title-icon="fa-project-diagram"
dataset="$ctrl.projects" table-key="gitlab_projects"
<gitlab-projects-datatable
title-text="Gitlab projects"
title-icon="fa-project-diagram"
dataset="$ctrl.projects"
table-key="gitlab_projects"
state="$ctrl.state.gitlab"
order-by="Name"
></gitlab-projects-datatable>
</div>
></gitlab-projects-datatable>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button ng-click="$ctrl.createRegistries()" class="btn btn-primary btn-sm" ng-disabled="$ctrl.actionInProgress || !$ctrl.state.gitlab.selectedItemCount" button-spinner="$ctrl.actionInProgress">
<button
ng-click="$ctrl.createRegistries()"
class="btn btn-primary btn-sm"
ng-disabled="$ctrl.actionInProgress || !$ctrl.state.gitlab.selectedItemCount"
button-spinner="$ctrl.actionInProgress"
>
<span ng-hide="$ctrl.actionInProgress">Create registries</span>
<span ng-show="$ctrl.actionInProgress">In progress...</span>
</button>
</div>
</div>
<!-- !actions -->
</div>
</div>

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('registryFormGitlab', {
actionInProgress: '<',
projects: '=',
state: '=',
resetDefaults: '<'
}
resetDefaults: '<',
},
});

View file

@ -6,7 +6,7 @@
<div class="form-group">
<label for="registry_username" class="col-sm-3 col-lg-2 control-label text-left">Username</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required auto-focus>
<input type="text" class="form-control" id="registry_username" name="registry_username" ng-model="$ctrl.model.Username" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="registryFormQuay.registry_username.$invalid">
@ -21,7 +21,7 @@
<div class="form-group">
<label for="registry_password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required>
<input type="password" class="form-control" id="registry_password" name="registry_password" ng-model="$ctrl.model.Password" required />
</div>
</div>
<div class="form-group" ng-show="registryFormQuay.registry_password.$invalid">

View file

@ -4,6 +4,6 @@ angular.module('portainer.app').component('registryFormQuay', {
model: '=',
formAction: '<',
formActionLabel: '@',
actionInProgress: '<'
}
actionInProgress: '<',
},
});

View file

@ -2,30 +2,32 @@ import moment from 'moment';
angular.module('portainer.app').component('scheduleForm', {
templateUrl: './scheduleForm.html',
controller: function() {
controller: function () {
var ctrl = this;
ctrl.state = {
formValidationError: ''
formValidationError: '',
};
ctrl.scheduleValues = [{
displayed: 'Every hour',
cron: '0 * * * *'
},
ctrl.scheduleValues = [
{
displayed: 'Every hour',
cron: '0 * * * *',
},
{
displayed: 'Every 2 hours',
cron: '0 */2 * * *'
}, {
cron: '0 */2 * * *',
},
{
displayed: 'Every day',
cron: '0 0 * * *'
}
cron: '0 0 * * *',
},
];
ctrl.formValues = {
datetime: ctrl.model.CronExpression ? cronToDatetime(ctrl.model.CronExpression) : moment(),
scheduleValue: ctrl.scheduleValues[0],
cronMethod: ctrl.model.Recurring ? 'advanced' : 'basic'
cronMethod: ctrl.model.Recurring ? 'advanced' : 'basic',
};
function cronToDatetime(cron) {
@ -38,10 +40,10 @@ angular.module('portainer.app').component('scheduleForm', {
function datetimeToCron(datetime) {
var date = moment(datetime);
return '0 '.concat(date.minutes(), ' ', date.hours(), ' ', date.date(), ' ', (date.month() + 1), ' *');
return '0 '.concat(date.minutes(), ' ', date.hours(), ' ', date.date(), ' ', date.month() + 1, ' *');
}
this.action = function() {
this.action = function () {
ctrl.state.formValidationError = '';
if (ctrl.model.Job.Method === 'editor' && ctrl.model.Job.FileContent === '') {
@ -61,7 +63,7 @@ angular.module('portainer.app').component('scheduleForm', {
ctrl.formAction();
};
this.editorUpdate = function(cm) {
this.editorUpdate = function (cm) {
ctrl.model.Job.FileContent = cm.getValue();
};
},
@ -74,6 +76,6 @@ angular.module('portainer.app').component('scheduleForm', {
removeLabelAction: '<',
formAction: '<',
formActionLabel: '@',
actionInProgress: '<'
}
actionInProgress: '<',
},
});

View file

@ -3,12 +3,12 @@
Information
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Due to how schedules behave differently on Edge endpoints and other endpoints it is recommended to create specific schedules that will only target one
type of endpoint.
</p>
</span>
<span class="col-sm-12 text-muted small">
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Due to how schedules behave differently on Edge endpoints and other
endpoints it is recommended to create specific schedules that will only target one type of endpoint.
</p>
</span>
</div>
<div class="col-sm-12 form-section-title">
Schedule configuration
@ -17,7 +17,7 @@
<div class="form-group">
<label for="schedule_name" class="col-sm-1 control-label text-left">Name</label>
<div class="col-sm-11">
<input type="text" class="form-control" ng-model="$ctrl.model.Name" id="schedule_name" name="schedule_name" placeholder="backup-app-prod" required auto-focus>
<input type="text" class="form-control" ng-model="$ctrl.model.Name" id="schedule_name" name="schedule_name" placeholder="backup-app-prod" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="scheduleForm.schedule_name.$invalid">
@ -34,10 +34,10 @@
Schedule configuration
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic">
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
<label for="config_basic">
<div class="boxselector_header">
<i class="fa fa-calendar-alt" aria-hidden="true" style="margin-right: 2px;"></i>
@ -47,7 +47,7 @@
</label>
</div>
<div>
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced">
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced" />
<label for="config_advanced">
<div class="boxselector_header">
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
@ -64,9 +64,7 @@
<div class="form-group">
<label for="recurring" class="col-sm-2 control-label text-left">Recurring schedule</label>
<div class="col-sm-10">
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" name="recurring" ng-model="$ctrl.model.Recurring"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" name="recurring" ng-model="$ctrl.model.Recurring" /><i></i> </label>
</div>
</div>
<!-- not-recurring -->
@ -74,7 +72,7 @@
<div class="form-group">
<label for="schedule_cron" class="col-sm-2 control-label text-left">Schedule date</label>
<div class="col-sm-10">
<input class="form-control" moment-picker ng-model="$ctrl.formValues.datetime" format="YYYY-MM-DD HH:mm">
<input class="form-control" moment-picker ng-model="$ctrl.formValues.datetime" format="YYYY-MM-DD HH:mm" />
</div>
<div ng-show="scheduleForm.datepicker.$invalid">
<div class="col-sm-12 small text-warning">
@ -91,9 +89,14 @@
<div class="form-group">
<label for="schedule_value" class="col-sm-2 control-label text-left">Schedule time</label>
<div class="col-sm-10">
<select id="schedule_value" name="schedule_value" class="form-control"
ng-model="$ctrl.formValues.scheduleValue" ng-options="value.displayed for value in $ctrl.scheduleValues" required
></select>
<select
id="schedule_value"
name="schedule_value"
class="form-control"
ng-model="$ctrl.formValues.scheduleValue"
ng-options="value.displayed for value in $ctrl.scheduleValues"
required
></select>
</div>
<div ng-show="scheduleForm.schedule_value.$invalid">
<div class="col-sm-12 small text-warning">
@ -112,8 +115,7 @@
<div class="form-group">
<label for="schedule_cron" class="col-sm-2 control-label text-left">Cron rule</label>
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="$ctrl.model.CronExpression" id="schedule_cron" name="schedule_cron"
placeholder="0 2 * * *" required>
<input type="text" class="form-control" ng-model="$ctrl.model.CronExpression" id="schedule_cron" name="schedule_cron" placeholder="0 2 * * *" required />
</div>
</div>
<div class="form-group" ng-show="scheduleForm.schedule_cron.$invalid">
@ -126,10 +128,12 @@
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information about the supported cron expression format.
You can refer to the <a href="https://godoc.org/github.com/robfig/cron#hdr-CRON_Expression_Format" target="_blank">following documentation</a> to get more information
about the supported cron expression format.
</p>
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the underlying host. You need to use a valid cron expression that is different from the documentation above.
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the
underlying host. You need to use a valid cron expression that is different from the documentation above.
</p>
</span>
</div>
@ -140,17 +144,15 @@
Job configuration
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This configuration will be ignored for Edge endpoint schedules.
</p>
</span>
<span class="col-sm-12 text-muted small">
<p> <i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> This configuration will be ignored for Edge endpoint schedules. </p>
</span>
</div>
<!-- image-input -->
<div class="form-group">
<label for="schedule_image" class="col-sm-2 control-label text-left">Image</label>
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="$ctrl.model.Job.Image" id="schedule_image" name="schedule_image" placeholder="e.g. ubuntu:latest" required>
<input type="text" class="form-control" ng-model="$ctrl.model.Job.Image" id="schedule_image" name="schedule_image" placeholder="e.g. ubuntu:latest" required />
</div>
</div>
<div class="form-group" ng-show="scheduleForm.schedule_image.$invalid">
@ -168,14 +170,14 @@
<portainer-tooltip position="bottom" message="Number of retries when it's not possible to reach the endpoint."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-4">
<input type="number" class="form-control" ng-model="$ctrl.model.Job.RetryCount" id="retrycount" name="retrycount" placeholder="3">
<input type="number" class="form-control" ng-model="$ctrl.model.Job.RetryCount" id="retrycount" name="retrycount" placeholder="3" />
</div>
<label for="retryinterval" class="col-sm-2 control-label text-left">
Retry interval
<portainer-tooltip position="bottom" message="Retry interval in seconds."></portainer-tooltip>
</label>
<div class="col-sm-10 col-md-4">
<input type="number" class="form-control" ng-model="$ctrl.model.Job.RetryInterval" id="retryinterval" name="retryinterval" placeholder="30">
<input type="number" class="form-control" ng-model="$ctrl.model.Job.RetryInterval" id="retryinterval" name="retryinterval" placeholder="30" />
</div>
</div>
<!-- !retry-policy -->
@ -185,10 +187,10 @@
Job content
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="method_editor" ng-model="$ctrl.model.Job.Method" value="editor">
<input type="radio" id="method_editor" ng-model="$ctrl.model.Job.Method" value="editor" />
<label for="method_editor">
<div class="boxselector_header">
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
@ -198,7 +200,7 @@
</label>
</div>
<div>
<input type="radio" id="method_upload" ng-model="$ctrl.model.Job.Method" value="upload">
<input type="radio" id="method_upload" ng-model="$ctrl.model.Job.Method" value="upload" />
<label for="method_upload">
<div class="boxselector_header">
<i class="fa fa-upload" aria-hidden="true" style="margin-right: 2px;"></i>
@ -223,17 +225,18 @@
<code>/host</code> folder.
</p>
<p>
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the underlying host. You have full access to the filesystem without having to use the <code>/host</code> folder.
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i> Edge endpoint schedules are managed by <code>cron</code> on the
underlying host. You have full access to the filesystem without having to use the <code>/host</code> folder.
</p>
</span>
</div>
<div class="form-group">
<div class="col-sm-12">
<code-editor
identifier="execute-schedule-editor"
placeholder="# Define or paste the content of your script file here"
on-change="$ctrl.editorUpdate"
value="$ctrl.model.Job.FileContent"
identifier="execute-schedule-editor"
placeholder="# Define or paste the content of your script file here"
on-change="($ctrl.editorUpdate)"
value="$ctrl.model.Job.FileContent"
></code-editor>
</div>
</div>
@ -267,7 +270,9 @@
<multi-endpoint-selector
ng-if="$ctrl.endpoints && $ctrl.groups && $ctrl.tags"
model="$ctrl.model.Job.Endpoints"
endpoints="$ctrl.endpoints" groups="$ctrl.groups" tags="$ctrl.tags"
endpoints="$ctrl.endpoints"
groups="$ctrl.groups"
tags="$ctrl.tags"
></multi-endpoint-selector>
<!-- !node-selection -->
<!-- actions -->
@ -276,11 +281,14 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm"
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="$ctrl.actionInProgress || !scheduleForm.$valid
|| $ctrl.model.Job.Endpoints.length === 0
|| ($ctrl.model.Job.Method === 'upload' && !$ctrl.model.Job.File)"
ng-click="$ctrl.action()" button-spinner="$ctrl.actionInProgress"
ng-click="$ctrl.action()"
button-spinner="$ctrl.actionInProgress"
>
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
<span ng-show="$ctrl.actionInProgress">In progress...</span>
@ -290,5 +298,5 @@
</span>
</div>
</div>
<!-- !actions -->
<!-- !actions -->
</form>

View file

@ -8,6 +8,6 @@ angular.module('portainer.app').component('templateForm', {
formAction: '<',
formActionLabel: '@',
actionInProgress: '<',
showTypeSelector: '<'
}
showTypeSelector: '<',
},
});

View file

@ -3,7 +3,7 @@
<div class="form-group" ng-class="{ 'has-error': templateForm.template_title.$invalid }">
<label for="template_title" class="col-sm-3 col-lg-2 control-label text-left">Title</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_title" ng-model="$ctrl.model.Title" placeholder="e.g. my-template" required auto-focus>
<input type="text" class="form-control" name="template_title" ng-model="$ctrl.model.Title" placeholder="e.g. my-template" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="templateForm.template_title.$invalid">
@ -18,7 +18,7 @@
<div class="form-group" ng-class="{ 'has-error': templateForm.template_description.$invalid }">
<label for="template_description" class="col-sm-3 col-lg-2 control-label text-left">Description</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_description" ng-model="$ctrl.model.Description" placeholder="e.g. template description..." required>
<input type="text" class="form-control" name="template_description" ng-model="$ctrl.model.Description" placeholder="e.g. template description..." required />
</div>
</div>
<div class="form-group" ng-show="templateForm.template_description.$invalid">
@ -38,13 +38,12 @@
</div>
<!-- template-details -->
<div uib-collapse="$ctrl.state.collapseTemplate">
<div ng-if="$ctrl.showTypeSelector">
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="template_container" ng-model="$ctrl.model.Type" ng-value="1">
<input type="radio" id="template_container" ng-model="$ctrl.model.Type" ng-value="1" />
<label for="template_container">
<div class="boxselector_header">
<i class="fa fa-server" aria-hidden="true" style="margin-right: 2px;"></i>
@ -54,7 +53,7 @@
</label>
</div>
<div>
<input type="radio" id="template_swarm_stack" ng-model="$ctrl.model.Type" ng-value="2">
<input type="radio" id="template_swarm_stack" ng-model="$ctrl.model.Type" ng-value="2" />
<label for="template_swarm_stack">
<div class="boxselector_header">
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
@ -64,7 +63,7 @@
</label>
</div>
<div>
<input type="radio" id="template_compose_stack" ng-model="$ctrl.model.Type" ng-value="3">
<input type="radio" id="template_compose_stack" ng-model="$ctrl.model.Type" ng-value="3" />
<label for="template_compose_stack">
<div class="boxselector_header">
<i class="fa fa-th-list" aria-hidden="true" style="margin-right: 2px;"></i>
@ -84,7 +83,7 @@
<portainer-tooltip position="bottom" message="Default name that will be associated to the template"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_name" ng-model="$ctrl.model.Name" placeholder="e.g. myApp">
<input type="text" class="form-control" name="template_name" ng-model="$ctrl.model.Name" placeholder="e.g. myApp" />
</div>
</div>
<!-- !name -->
@ -95,7 +94,7 @@
<portainer-tooltip position="bottom" message="Recommended size: 60x60"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_logo" ng-model="$ctrl.model.Logo" placeholder="e.g. https://portainer.io/images/logos/nginx.png">
<input type="text" class="form-control" name="template_logo" ng-model="$ctrl.model.Logo" placeholder="e.g. https://portainer.io/images/logos/nginx.png" />
</div>
</div>
<!-- !logo -->
@ -106,7 +105,12 @@
<portainer-tooltip position="bottom" message="Usage/extra information about the template. Supports HTML."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<textarea class="form-control" name="template_note" ng-model="$ctrl.model.Note" placeholder='You can use this field to specify extra information. <br/> It supports <b>HTML</b>.'></textarea>
<textarea
class="form-control"
name="template_note"
ng-model="$ctrl.model.Note"
placeholder="You can use this field to specify extra information. <br/> It supports <b>HTML</b>."
></textarea>
</div>
</div>
<!-- !note -->
@ -146,9 +150,7 @@
Administrator template
<portainer-tooltip position="bottom" message="This template will only be available to administrator users."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.model.AdministratorOnly"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.AdministratorOnly" /><i></i> </label>
</div>
</div>
<!-- administrator-only -->
@ -168,7 +170,14 @@
<div class="form-group" ng-class="{ 'has-error': templateForm.template_repository_url.$invalid }">
<label for="template_repository_url" class="col-sm-3 col-lg-2 control-label text-left">Repository URL</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_repository_url" ng-model="$ctrl.model.Repository.url" placeholder="https://github.com/portainer/portainer-compose" required>
<input
type="text"
class="form-control"
name="template_repository_url"
ng-model="$ctrl.model.Repository.url"
placeholder="https://github.com/portainer/portainer-compose"
required
/>
</div>
</div>
<div class="form-group" ng-show="templateForm.template_repository_url.$invalid">
@ -185,7 +194,7 @@
Compose file path
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_repository_path" ng-model="$ctrl.model.Repository.stackfile" placeholder='docker-compose.yml'>
<input type="text" class="form-control" name="template_repository_path" ng-model="$ctrl.model.Repository.stackfile" placeholder="docker-compose.yml" />
</div>
</div>
<!-- !composefile-path -->
@ -202,19 +211,18 @@
</div>
<!-- container-details -->
<div uib-collapse="$ctrl.state.collapseContainer">
<por-image-registry
model="$ctrl.model.RegistryModel"
auto-complete="true"
label-class="col-sm-1" input-class="col-sm-11"
></por-image-registry>
<por-image-registry model="$ctrl.model.RegistryModel" auto-complete="true" label-class="col-sm-1" input-class="col-sm-11"></por-image-registry>
<!-- command -->
<div class="form-group">
<label for="template_command" class="col-sm-3 col-lg-2 control-label text-left">
Command
<portainer-tooltip position="bottom" message="The command to run in the container. If not specified, the container will use the default command specified in its Dockerfile."></portainer-tooltip>
<portainer-tooltip
position="bottom"
message="The command to run in the container. If not specified, the container will use the default command specified in its Dockerfile."
></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_command" ng-model="$ctrl.model.Command" placeholder='/bin/bash -c \"echo hello\" && exit 777'>
<input type="text" class="form-control" name="template_command" ng-model="$ctrl.model.Command" placeholder='/bin/bash -c \"echo hello\" && exit 777' />
</div>
</div>
<!-- !command -->
@ -225,7 +233,7 @@
<portainer-tooltip position="bottom" message="Set the hostname of the container. Will use Docker default if not specified."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" name="template_hostname" ng-model="$ctrl.model.Hostname" placeholder='mycontainername'>
<input type="text" class="form-control" name="template_hostname" ng-model="$ctrl.model.Hostname" placeholder="mycontainername" />
</div>
</div>
<!-- !hostname -->
@ -242,14 +250,14 @@
</div>
<!-- !network -->
<!-- port-mapping -->
<div class="form-group" >
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Port mapping</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addPortBinding()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
</span>
</div>
<div class="col-sm-12" style="margin-top: 10px" ng-if="$ctrl.model.Ports.length > 0">
<div class="col-sm-12" style="margin-top: 10px;" ng-if="$ctrl.model.Ports.length > 0">
<span class="small text-muted">Portainer will automatically assign a port if you leave the host port empty.</span>
</div>
<!-- port-mapping-input-list -->
@ -259,7 +267,7 @@
<!-- host-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)">
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)" />
</div>
<!-- !host-port -->
<span style="margin: 0 10px 0 10px;">
@ -268,7 +276,7 @@
<!-- container-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">container</span>
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80">
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80" />
</div>
<!-- !container-port -->
<!-- protocol-actions -->
@ -289,14 +297,14 @@
</div>
<!-- !port-mapping -->
<!-- volumes -->
<div class="form-group" >
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Volume mapping</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addVolume()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional volume
</span>
</div>
<div class="col-sm-12" style="margin-top: 10px" ng-if="$ctrl.model.Volumes.length > 0">
<div class="col-sm-12" style="margin-top: 10px;" ng-if="$ctrl.model.Volumes.length > 0">
<span class="small text-muted">Portainer will automatically create and map a local volume when using the <b>auto</b> option.</span>
</div>
<div ng-repeat="volume in $ctrl.model.Volumes">
@ -306,7 +314,7 @@
<!-- container-path -->
<div class="input-group input-group-sm col-sm-6">
<span class="input-group-addon">container</span>
<input type="text" class="form-control" ng-model="volume.container" placeholder="e.g. /path/in/container">
<input type="text" class="form-control" ng-model="volume.container" placeholder="e.g. /path/in/container" />
</div>
<!-- !container-path -->
<!-- volume-type -->
@ -328,7 +336,7 @@
<!-- bind -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'bind'">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" ng-model="volume.bind" placeholder="e.g. /path/on/host">
<input type="text" class="form-control" ng-model="volume.bind" placeholder="e.g. /path/on/host" />
</div>
<!-- !bind -->
<!-- read-only -->
@ -346,7 +354,7 @@
</div>
<!-- !volumes -->
<!-- labels -->
<div class="form-group" >
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Labels</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addLabel()">
@ -359,11 +367,11 @@
<div ng-repeat="label in $ctrl.model.Labels" style="margin-top: 2px;">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">name</span>
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo">
<input type="text" class="form-control" ng-model="label.name" placeholder="e.g. com.example.foo" />
</div>
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">value</span>
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar">
<input type="text" class="form-control" ng-model="label.value" placeholder="e.g. bar" />
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLabel($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@ -396,9 +404,7 @@
Privileged mode
<portainer-tooltip position="bottom" message="Start the container in privileged mode."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.model.Privileged"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.Privileged" /><i></i> </label>
</div>
</div>
<!-- !privileged-mode -->
@ -409,9 +415,7 @@
Interactive mode
<portainer-tooltip position="bottom" message="Start the container in foreground (equivalent of -i -t flags)."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;">
<input type="checkbox" ng-model="$ctrl.model.Interactive"><i></i>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.model.Interactive" /><i></i> </label>
</div>
</div>
<!-- !interactive-mode -->
@ -428,7 +432,7 @@
<!-- environment-details -->
<div uib-collapse="$ctrl.state.collapseEnv">
<!-- env -->
<div class="form-group" >
<div class="form-group">
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Environment variables</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addEnvVar()">
@ -439,11 +443,11 @@
<div style="margin-top: 10px;">
<div class="col-sm-12 template-envvar" ng-repeat="var in $ctrl.model.Env" style="margin-top: 10px;">
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0">
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="preset_var_{{$index}}" ng-model="var.type" ng-value="1" ng-change="$ctrl.changeEnvVarType(var)">
<label for="preset_var_{{$index}}">
<input type="radio" id="preset_var_{{ $index }}" ng-model="var.type" ng-value="1" ng-change="$ctrl.changeEnvVarType(var)" />
<label for="preset_var_{{ $index }}">
<div class="boxselector_header">
<i class="fa fa-user-slash" aria-hidden="true" style="margin-right: 2px;"></i>
Preset
@ -452,8 +456,8 @@
</label>
</div>
<div>
<input type="radio" id="text_var_{{$index}}" ng-model="var.type" ng-value="2" ng-change="$ctrl.changeEnvVarType(var)">
<label for="text_var_{{$index}}">
<input type="radio" id="text_var_{{ $index }}" ng-model="var.type" ng-value="2" ng-change="$ctrl.changeEnvVarType(var)" />
<label for="text_var_{{ $index }}">
<div class="boxselector_header">
<i class="fa fa-edit" aria-hidden="true" style="margin-right: 2px;"></i>
Text
@ -462,8 +466,8 @@
</label>
</div>
<div>
<input type="radio" id="select_var_{{$index}}" ng-model="var.type" ng-value="3">
<label for="select_var_{{$index}}">
<input type="radio" id="select_var_{{ $index }}" ng-model="var.type" ng-value="3" />
<label for="select_var_{{ $index }}">
<div class="boxselector_header">
<i class="fa fa-list-ol" aria-hidden="true" style="margin-right: 2px;"></i>
Select
@ -478,7 +482,7 @@
Name
</label>
<div class="col-sm-8">
<input type="text" class="form-control" ng-model="var.name" placeholder="env_var">
<input type="text" class="form-control" ng-model="var.name" placeholder="env_var" />
</div>
<div class="col-sm-2">
<button class="btn btn-sm btn-danger space-left" type="button" ng-click="$ctrl.removeEnvVar($index)">
@ -487,20 +491,20 @@
</div>
</div>
<div ng-if="var.type == 2 || var.type == 3">
<div class="form-group">
<div class="form-group">
<label class="col-sm-2 control-label text-left">
Label
</label>
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="var.label" placeholder="Choose a label">
<input type="text" class="form-control" ng-model="var.label" placeholder="Choose a label" />
</div>
</div>
<div class="form-group">
<div class="form-group">
<label class="col-sm-2 control-label text-left" style="margin-top: 2px;">
Description
</label>
<div class="col-sm-10" style="margin-top: 2px;">
<input type="text" class="form-control" ng-model="var.description" placeholder="Tooltip">
<input type="text" class="form-control" ng-model="var.description" placeholder="Tooltip" />
</div>
</div>
</div>
@ -509,7 +513,7 @@
Default value
</label>
<div class="col-sm-10">
<input type="text" class="form-control" ng-model="var.default" placeholder="default_value">
<input type="text" class="form-control" ng-model="var.default" placeholder="default_value" />
</div>
</div>
<div ng-if="var.type === 3" style="margin-bottom: 5px;" class="form-group">
@ -525,17 +529,19 @@
<div ng-repeat="val in var.select" style="margin-top: 2px;">
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">name</span>
<input type="text" class="form-control" ng-model="val.text" placeholder="Yes, I agree">
<input type="text" class="form-control" ng-model="val.text" placeholder="Yes, I agree" />
</div>
<div class="input-group col-sm-5 input-group-sm">
<span class="input-group-addon">value</span>
<input type="text" class="form-control" ng-model="val.value" placeholder="Y">
<input type="text" class="form-control" ng-model="val.value" placeholder="Y" />
</div>
<div class="input-group col-sm-1 input-group-sm">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeEnvVarValue(var, $index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
<input style="margin-left: 5px;" type="checkbox" ng-model="val.default" id="val_default_{{$index}}"><label for="val_default_{{$index}}" class="space-left">Default</label>
<input style="margin-left: 5px;" type="checkbox" ng-model="val.default" id="val_default_{{ $index }}" /><label for="val_default_{{ $index }}" class="space-left"
>Default</label
>
</div>
</div>
</div>
@ -558,7 +564,13 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-click="$ctrl.formAction()" ng-disabled="$ctrl.actionInProgress || !templateForm.$valid" button-spinner="$ctrl.actionInProgress">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="$ctrl.formAction()"
ng-disabled="$ctrl.actionInProgress || !templateForm.$valid"
button-spinner="$ctrl.actionInProgress"
>
<span ng-hide="$ctrl.actionInProgress">{{ $ctrl.formActionLabel }}</span>
<span ng-show="$ctrl.actionInProgress">In progress...</span>
</button>

View file

@ -1,54 +1,55 @@
angular.module('portainer.app')
.controller('TemplateFormController', [function() {
this.state = {
collapseTemplate: false,
collapseContainer: false,
collapseStack: false,
collapseEnv: false
};
angular.module('portainer.app').controller('TemplateFormController', [
function () {
this.state = {
collapseTemplate: false,
collapseContainer: false,
collapseStack: false,
collapseEnv: false,
};
this.addPortBinding = function() {
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
};
this.addPortBinding = function () {
this.model.Ports.push({ containerPort: '', protocol: 'tcp' });
};
this.removePortBinding = function(index) {
this.model.Ports.splice(index, 1);
};
this.removePortBinding = function (index) {
this.model.Ports.splice(index, 1);
};
this.addVolume = function () {
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
};
this.addVolume = function () {
this.model.Volumes.push({ container: '', bind: '', readonly: false, type: 'auto' });
};
this.removeVolume = function(index) {
this.model.Volumes.splice(index, 1);
};
this.removeVolume = function (index) {
this.model.Volumes.splice(index, 1);
};
this.addLabel = function () {
this.model.Labels.push({ name: '', value: ''});
};
this.addLabel = function () {
this.model.Labels.push({ name: '', value: '' });
};
this.removeLabel = function(index) {
this.model.Labels.splice(index, 1);
};
this.removeLabel = function (index) {
this.model.Labels.splice(index, 1);
};
this.addEnvVar = function() {
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
};
this.addEnvVar = function () {
this.model.Env.push({ type: 1, name: '', label: '', description: '', default: '', preset: true, select: [] });
};
this.removeEnvVar = function(index) {
this.model.Env.splice(index, 1);
};
this.removeEnvVar = function (index) {
this.model.Env.splice(index, 1);
};
this.addEnvVarValue = function(env) {
env.select = env.select || [];
env.select.push({ name: '', value: '' });
};
this.addEnvVarValue = function (env) {
env.select = env.select || [];
env.select.push({ name: '', value: '' });
};
this.removeEnvVarValue = function(env, index) {
env.select.splice(index, 1);
};
this.removeEnvVarValue = function (env, index) {
env.select.splice(index, 1);
};
this.changeEnvVarType = function(env) {
env.preset = env.type === 1;
};
}]);
this.changeEnvVarType = function (env) {
env.preset = env.type === 1;
};
},
]);

View file

@ -1,41 +1,41 @@
angular.module('portainer.app').component('groupAssociationTable', {
templateUrl: './groupAssociationTable.html',
controller: function() {
controller: function () {
this.state = {
orderBy: 'Name',
reverseOrder: false,
paginatedItemLimit: '10',
textFilter: '',
loading:true,
pageNumber: 1
loading: true,
pageNumber: 1,
};
this.changeOrderBy = function(orderField) {
this.changeOrderBy = function (orderField) {
this.state.reverseOrder = this.state.orderBy === orderField ? !this.state.reverseOrder : false;
this.state.orderBy = orderField;
};
this.hasBackendPagination = function() {
this.hasBackendPagination = function () {
return !(this.pageType === 'create' && this.tableType === 'associated');
}
this.onTextFilterChange = function() {
this.paginationChangedAction();
}
this.onPageChanged = function(newPageNumber) {
this.paginationState.pageNumber = newPageNumber;
this.paginationChangedAction();
}
this.onPaginationLimitChanged = function() {
};
this.onTextFilterChange = function () {
this.paginationChangedAction();
};
this.paginationChangedAction = function() {
this.onPageChanged = function (newPageNumber) {
this.paginationState.pageNumber = newPageNumber;
this.paginationChangedAction();
};
this.onPaginationLimitChanged = function () {
this.paginationChangedAction();
};
this.paginationChangedAction = function () {
this.retrievePage(this.pageType, this.tableType);
};
this.$onChanges = function(changes) {
this.$onChanges = function (changes) {
if (changes.loaded && changes.loaded.currentValue) {
this.paginationChangedAction();
}
@ -49,6 +49,6 @@ angular.module('portainer.app').component('groupAssociationTable', {
retrievePage: '<',
dataset: '<',
entryClick: '<',
emptyDatasetMessage: '@'
}
emptyDatasetMessage: '@',
},
});

View file

@ -2,11 +2,14 @@
<table class="table table-hover">
<div class="col-sm-12">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput"
<input
type="text"
class="searchInput"
ng-model="$ctrl.paginationState.filter"
ng-change="$ctrl.onTextFilterChange()"
ng-model-options="{ debounce: 300 }"
placeholder="Search...">
placeholder="Search..."
/>
</div>
<thead>
<tr>
@ -20,19 +23,23 @@
</tr>
</thead>
<tbody>
<tr ng-if="!$ctrl.hasBackendPagination();"
<tr
ng-if="!$ctrl.hasBackendPagination();"
ng-click="$ctrl.entryClick(item)"
class="interactive"
dir-paginate="item in $ctrl.dataset | filter:$ctrl.paginationState.filter | itemsPerPage: $ctrl.paginationState.limit"
pagination-id="$ctrl.tableType">
pagination-id="$ctrl.tableType"
>
<td>{{ item.Name }}</td>
</tr>
<tr ng-if="$ctrl.hasBackendPagination();"
<tr
ng-if="$ctrl.hasBackendPagination();"
ng-click="$ctrl.entryClick(item)"
class="interactive"
dir-paginate="item in $ctrl.dataset | itemsPerPage: $ctrl.paginationState.limit"
pagination-id="$ctrl.tableType"
total-items="$ctrl.paginationState.totalCount">
total-items="$ctrl.paginationState.totalCount"
>
<td>{{ item.Name }}</td>
</tr>
<tr ng-if="!$ctrl.dataset">

View file

@ -1,13 +1,16 @@
angular.module('portainer.app')
.directive('rdHeaderContent', ['Authentication', function rdHeaderContent(Authentication) {
var directive = {
requires: '^rdHeader',
transclude: true,
link: function (scope) {
scope.username = Authentication.getUserDetails().username;
},
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="portainer.account" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> my account </u></a><a ui-sref="portainer.auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out-alt" aria-hidden="true"></i> log out</u></a></div></div>',
restrict: 'E'
};
return directive;
}]);
angular.module('portainer.app').directive('rdHeaderContent', [
'Authentication',
function rdHeaderContent(Authentication) {
var directive = {
requires: '^rdHeader',
transclude: true,
link: function (scope) {
scope.username = Authentication.getUserDetails().username;
},
template:
'<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="portainer.account" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> my account </u></a><a ui-sref="portainer.auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out-alt" aria-hidden="true"></i> log out</u></a></div></div>',
restrict: 'E',
};
return directive;
},
]);

View file

@ -1,16 +1,19 @@
angular.module('portainer.app')
.directive('rdHeaderTitle', ['Authentication', function rdHeaderTitle(Authentication) {
var directive = {
requires: '^rdHeader',
scope: {
titleText: '@'
},
link: function (scope) {
scope.username = Authentication.getUserDetails().username;
},
transclude: true,
template: '<div class="page white-space-normal">{{titleText}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle" aria-hidden="true"></i> {{username}}</span><a ui-sref="portainer.support" class="pull-right" style="font-size:14px;margin-right:15px;margin-top:2px;"><span class="fa fa-life-ring fa-fw red-icon"></span> Portainer support</a></div>',
restrict: 'E'
};
return directive;
}]);
angular.module('portainer.app').directive('rdHeaderTitle', [
'Authentication',
function rdHeaderTitle(Authentication) {
var directive = {
requires: '^rdHeader',
scope: {
titleText: '@',
},
link: function (scope) {
scope.username = Authentication.getUserDetails().username;
},
transclude: true,
template:
'<div class="page white-space-normal">{{titleText}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle" aria-hidden="true"></i> {{username}}</span><a ui-sref="portainer.support" class="pull-right" style="font-size:14px;margin-right:15px;margin-top:2px;"><span class="fa fa-life-ring fa-fw red-icon"></span> Portainer support</a></div>',
restrict: 'E',
};
return directive;
},
]);

View file

@ -1,12 +1,11 @@
angular.module('portainer.app')
.directive('rdHeader', function rdHeader() {
angular.module('portainer.app').directive('rdHeader', function rdHeader() {
var directive = {
scope: {
'ngModel': '='
ngModel: '=',
},
transclude: true,
template: '<div class="row header"><div id="loadingbar-placeholder"></div><div class="col-xs-12"><div class="meta" ng-transclude></div></div></div>',
restrict: 'EA'
restrict: 'EA',
};
return directive;
});

View file

@ -1,4 +1,4 @@
angular.module('portainer.app').component('informationPanelOffline', {
templateUrl: './informationPanelOffline.html',
controller: 'InformationPanelOfflineController'
controller: 'InformationPanelOfflineController',
});

View file

@ -1,34 +1,38 @@
angular.module('portainer.app').controller('InformationPanelOfflineController', ['$state', 'EndpointProvider', 'EndpointService', 'Authentication', 'Notifications',
function StackDuplicationFormController($state, EndpointProvider, EndpointService, Authentication, Notifications) {
var ctrl = this;
angular.module('portainer.app').controller('InformationPanelOfflineController', [
'$state',
'EndpointProvider',
'EndpointService',
'Authentication',
'Notifications',
function StackDuplicationFormController($state, EndpointProvider, EndpointService, Authentication, Notifications) {
var ctrl = this;
this.$onInit = onInit;
this.triggerSnapshot = triggerSnapshot;
this.$onInit = onInit;
this.triggerSnapshot = triggerSnapshot;
function triggerSnapshot() {
var endpointId = EndpointProvider.endpointID();
function triggerSnapshot() {
var endpointId = EndpointProvider.endpointID();
EndpointService.snapshotEndpoint(endpointId)
.then(function onSuccess() {
$state.reload();
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'An error occured during endpoint snapshot');
});
}
EndpointService.snapshotEndpoint(endpointId)
.then(function onSuccess() {
$state.reload();
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'An error occured during endpoint snapshot');
});
}
function onInit() {
var endpointId = EndpointProvider.endpointID();
ctrl.showRefreshButton = Authentication.isAdmin();
function onInit() {
var endpointId = EndpointProvider.endpointID();
ctrl.showRefreshButton = Authentication.isAdmin();
EndpointService.endpoint(endpointId)
.then(function onSuccess(data) {
ctrl.snapshotTime = data.Snapshots[0].Time;
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint information');
});
}
}]);
EndpointService.endpoint(endpointId)
.then(function onSuccess(data) {
ctrl.snapshotTime = data.Snapshots[0].Time;
})
.catch(function onError(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint information');
});
}
},
]);

View file

@ -2,7 +2,7 @@ angular.module('portainer.app').component('informationPanel', {
templateUrl: './informationPanel.html',
bindings: {
titleText: '@',
dismissAction: '&?'
dismissAction: '&?',
},
transclude: true
transclude: true,
});

View file

@ -1,8 +1,7 @@
angular.module('portainer.app')
.directive('rdLoading', function rdLoading() {
angular.module('portainer.app').directive('rdLoading', function rdLoading() {
var directive = {
restrict: 'AE',
template: '<div class="loading"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>'
template: '<div class="loading"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>',
};
return directive;
});

View file

@ -2,7 +2,7 @@ angular.module('portainer.app').component('motdPanel', {
templateUrl: './motdPanel.html',
bindings: {
motd: '<',
dismissAction: '&?'
dismissAction: '&?',
},
transclude: true
transclude: true,
});

View file

@ -23,4 +23,4 @@
</rd-widget-body>
</rd-widget>
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
<ui-select multiple ng-model="$ctrl.model" close-on-select="false" style="margin-top: 55px">
<ui-select multiple ng-model="$ctrl.model" close-on-select="false" style="margin-top: 55px;">
<ui-select-match placeholder="Select one or multiple endpoint(s)">
<span>
{{ $item.Name }}

View file

@ -30,9 +30,7 @@ class MultiEndpointSelectorController {
}
$onInit() {
this.availableGroups = _.filter(this.groups, group =>
_.some(this.endpoints, endpoint => endpoint.GroupId == group.Id)
);
this.availableGroups = _.filter(this.groups, (group) => _.some(this.endpoints, (endpoint) => endpoint.GroupId == group.Id));
}
}

View file

@ -1,18 +1,19 @@
angular.module('portainer.app')
.directive('onEnterKey', [function porOnEnterKey() {
var directive = {
restrict: 'A',
link: function(scope, element, attrs) {
element.bind('keydown keypress', function (e) {
if ( e.which === 13 ) {
e.preventDefault();
scope.$apply(function () {
scope.$eval(attrs.onEnterKey);
});
}
});
}
};
angular.module('portainer.app').directive('onEnterKey', [
function porOnEnterKey() {
var directive = {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('keydown keypress', function (e) {
if (e.which === 13) {
e.preventDefault();
scope.$apply(function () {
scope.$eval(attrs.onEnterKey);
});
}
});
},
};
return directive;
}]);
return directive;
},
]);

View file

@ -2,6 +2,6 @@ angular.module('portainer.app').component('productItem', {
templateUrl: './productItem.html',
bindings: {
model: '<',
goTo: '<'
}
goTo: '<',
},
});

View file

@ -3,10 +3,8 @@
<div class="blocklist-item-box">
<!-- extension-image -->
<span class="blocklist-item-logo">
<img class="blocklist-item-logo" ng-if="$ctrl.model.Id == 1 || $ctrl.model.Id == 2 || $ctrl.model.Id == 3"
src="../../../../../assets/images/support_1.png" />
<img class="blocklist-item-logo" ng-if="$ctrl.model.Id == 4 || $ctrl.model.Id == 5"
src="../../../../../assets/images/support_2.png" />
<img class="blocklist-item-logo" ng-if="$ctrl.model.Id == 1 || $ctrl.model.Id == 2 || $ctrl.model.Id == 3" src="../../../../../assets/images/support_1.png" />
<img class="blocklist-item-logo" ng-if="$ctrl.model.Id == 4 || $ctrl.model.Id == 5" src="../../../../../assets/images/support_2.png" />
</span>
<!-- !extension-image -->
<!-- extension-details -->

View file

@ -3,6 +3,6 @@ angular.module('portainer.app').component('productList', {
bindings: {
titleText: '@',
products: '<',
goTo: '<'
}
goTo: '<',
},
});

View file

@ -1,20 +1,13 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle">
<i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }}
</div>
<div class="toolBarTitle"> <i class="fa fa-bolt" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="blocklist">
<product-item ng-repeat="product in $ctrl.products"
model="product"
go-to="$ctrl.goTo"
></product-item>
<product-item ng-repeat="product in $ctrl.products" model="product" go-to="$ctrl.goTo"></product-item>
</div>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -7,6 +7,6 @@ angular.module('portainer.app').component('slider', {
floor: '<',
ceil: '<',
step: '<',
precision: '<'
}
precision: '<',
},
});

View file

@ -1,5 +1,4 @@
angular.module('portainer.app')
.controller('SliderController', function () {
angular.module('portainer.app').controller('SliderController', function () {
var ctrl = this;
ctrl.options = {
@ -9,14 +8,14 @@ angular.module('portainer.app')
precision: ctrl.precision,
showSelectionBar: true,
enforceStep: false,
translate: function(value, sliderId, label) {
translate: function (value, sliderId, label) {
if ((label === 'floor' && ctrl.floor === 0) || value === 0) {
return 'unlimited';
}
return value;
},
onChange: function() {
onChange: function () {
ctrl.onChange();
}
},
};
});

View file

@ -5,12 +5,12 @@ angular.module('portainer.app').controller('StackDuplicationFormController', [
ctrl.state = {
duplicationInProgress: false,
migrationInProgress: false
migrationInProgress: false,
};
ctrl.formValues = {
endpoint: null,
newName: ''
newName: '',
};
ctrl.isFormValidForDuplication = isFormValidForDuplication;
@ -29,48 +29,38 @@ angular.module('portainer.app').controller('StackDuplicationFormController', [
function duplicateStack() {
if (!ctrl.formValues.newName) {
Notifications.error(
'Failure',
null,
'Stack name is required for duplication'
);
Notifications.error('Failure', null, 'Stack name is required for duplication');
return;
}
ctrl.state.duplicationInProgress = true;
ctrl.onDuplicate({
ctrl
.onDuplicate({
endpointId: ctrl.formValues.endpoint.Id,
name: ctrl.formValues.newName ? ctrl.formValues.newName : undefined
name: ctrl.formValues.newName ? ctrl.formValues.newName : undefined,
})
.finally(function() {
.finally(function () {
ctrl.state.duplicationInProgress = false;
});
}
function migrateStack() {
ctrl.state.migrationInProgress = true;
ctrl.onMigrate({
ctrl
.onMigrate({
endpointId: ctrl.formValues.endpoint.Id,
name: ctrl.formValues.newName ? ctrl.formValues.newName : undefined
name: ctrl.formValues.newName ? ctrl.formValues.newName : undefined,
})
.finally(function() {
.finally(function () {
ctrl.state.migrationInProgress = false;
});
}
function isMigrationButtonDisabled() {
return (
!ctrl.isFormValidForMigration() ||
ctrl.state.duplicationInProgress ||
ctrl.state.migrationInProgress ||
isTargetEndpointAndCurrentEquals()
);
return !ctrl.isFormValidForMigration() || ctrl.state.duplicationInProgress || ctrl.state.migrationInProgress || isTargetEndpointAndCurrentEquals();
}
function isTargetEndpointAndCurrentEquals() {
return (
ctrl.formValues.endpoint &&
ctrl.formValues.endpoint.Id === ctrl.currentEndpointId
);
return ctrl.formValues.endpoint && ctrl.formValues.endpoint.Id === ctrl.currentEndpointId;
}
}
},
]);

View file

@ -10,33 +10,28 @@
</span>
<div>
<div class="form-group">
<input class="form-control" placeholder="Stack name (optional for migration)"
aria-placeholder="Stack name"
ng-model="$ctrl.formValues.newName" />
<input class="form-control" placeholder="Stack name (optional for migration)" aria-placeholder="Stack name" ng-model="$ctrl.formValues.newName" />
</div>
<endpoint-selector ng-if="$ctrl.endpoints && $ctrl.groups" model="$ctrl.formValues.endpoint"
endpoints="$ctrl.endpoints" groups="$ctrl.groups"></endpoint-selector>
<button class="btn btn-sm btn-primary" ng-click="$ctrl.migrateStack()"
<endpoint-selector ng-if="$ctrl.endpoints && $ctrl.groups" model="$ctrl.formValues.endpoint" endpoints="$ctrl.endpoints" groups="$ctrl.groups"></endpoint-selector>
<button
class="btn btn-sm btn-primary"
ng-click="$ctrl.migrateStack()"
ng-disabled="$ctrl.isMigrationButtonDisabled()"
style="margin-top: 7px; margin-left: 0;"
button-spinner="$ctrl.state.migrationInProgress">
<span ng-hide="$ctrl.state.migrationInProgress">
<i class="fa fa-long-arrow-alt-right space-right"
aria-hidden="true"></i> Migrate
</span>
<span ng-show="$ctrl.state.migrationInProgress">Migration in progress...</span>
button-spinner="$ctrl.state.migrationInProgress"
>
<span ng-hide="$ctrl.state.migrationInProgress"> <i class="fa fa-long-arrow-alt-right space-right" aria-hidden="true"></i> Migrate </span>
<span ng-show="$ctrl.state.migrationInProgress">Migration in progress...</span>
</button>
<button
class="btn btn-sm btn-primary"
ng-click="$ctrl.duplicateStack()"
ng-disabled="!$ctrl.isFormValidForDuplication() || $ctrl.state.duplicationInProgress || $ctrl.state.migrationInProgress"
style="margin-top: 7px; margin-left: 0;"
button-spinner="$ctrl.state.duplicationInProgress">
<span ng-hide="$ctrl.state.duplicationInProgress">
<i class="fa fa-clone space-right"
aria-hidden="true"></i> Duplicate
</span>
<span ng-show="$ctrl.state.duplicationInProgress">Duplication in progress...</span>
button-spinner="$ctrl.state.duplicationInProgress"
>
<span ng-hide="$ctrl.state.duplicationInProgress"> <i class="fa fa-clone space-right" aria-hidden="true"></i> Duplicate </span>
<span ng-show="$ctrl.state.duplicationInProgress">Duplication in progress...</span>
</button>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show more