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

feat(tags): add the ability to manage tags (#1971)

* feat(tags): add the ability to manage tags

* feat(tags): update tag selector UX

* refactor(app): remove unused ui-select library
This commit is contained in:
Anthony Lapenna 2018-06-15 09:18:25 +02:00 committed by GitHub
parent b349f16090
commit 5e73a49473
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 942 additions and 118 deletions

View file

@ -9,6 +9,7 @@ angular.module('portainer')
.constant('API_ENDPOINT_STACKS', 'api/stacks')
.constant('API_ENDPOINT_STATUS', 'api/status')
.constant('API_ENDPOINT_USERS', 'api/users')
.constant('API_ENDPOINT_TAGS', 'api/tags')
.constant('API_ENDPOINT_TEAMS', 'api/teams')
.constant('API_ENDPOINT_TEAM_MEMBERSHIPS', 'api/team_memberships')
.constant('API_ENDPOINT_TEMPLATES', 'api/templates')

View file

@ -296,6 +296,17 @@ angular.module('portainer.app', [])
}
};
var tags = {
name: 'portainer.tags',
url: '/tags',
views: {
'content@': {
templateUrl: 'app/portainer/views/tags/tags.html',
controller: 'TagsController'
}
}
};
var users = {
name: 'portainer.users',
url: '/users',
@ -366,6 +377,7 @@ angular.module('portainer.app', [])
$stateRegistryProvider.register(stack);
$stateRegistryProvider.register(stackCreation);
$stateRegistryProvider.register(support);
$stateRegistryProvider.register(tags);
$stateRegistryProvider.register(users);
$stateRegistryProvider.register(user);
$stateRegistryProvider.register(teams);

View file

@ -0,0 +1,84 @@
<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="settings">
<span class="setting" ng-class="{ 'setting-active': $ctrl.state.displayTextFilter }" ng-click="$ctrl.updateDisplayTextFilter()" ng-if="$ctrl.showTextFilter">
<i class="fa fa-search" aria-hidden="true"></i> Search
</span>
</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)">
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
</button>
</div>
<div class="searchBar" ng-if="$ctrl.state.displayTextFilter">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input type="text" class="searchInput" ng-model="$ctrl.state.textFilter" placeholder="Search..." auto-focus>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<span class="md-checkbox">
<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>
</a>
</th>
</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}">
<td>
<span class="md-checkbox">
<input id="select_{{ $index }}" type="checkbox" ng-model="item.Checked" ng-change="$ctrl.selectItem(item)"/>
<label for="select_{{ $index }}"></label>
</span>
{{ item.Name }}
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="1" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="1" class="text-center text-muted">No tag available.</td>
</tr>
</tbody>
</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="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()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>

View file

@ -0,0 +1,14 @@
angular.module('portainer.app').component('tagsDatatable', {
templateUrl: 'app/portainer/components/datatables/tags-datatable/tagsDatatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
showTextFilter: '<',
removeAction: '<'
}
});

View file

@ -21,6 +21,7 @@ angular.module('portainer.app').component('groupForm', {
bindings: {
model: '=',
availableEndpoints: '=',
availableTags: '<',
associatedEndpoints: '=',
addLabelAction: '<',
removeLabelAction: '<',

View file

@ -22,33 +22,17 @@
</div>
</div>
<!-- !description-input -->
<!-- labels -->
<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.addLabelAction()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add label
</span>
</div>
<!-- labels-input-list -->
<div class="col-sm-12 form-inline" style="margin-top: 10px;">
<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. organization">
</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. acme">
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLabelAction($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
<!-- !labels-input-list -->
<div class="col-sm-12 form-section-title">
Metadata
</div>
<!-- !labels -->
<!-- tags -->
<div class="form-group">
<tag-selector
tags="$ctrl.availableTags"
model="$ctrl.model.Tags"
></tag-selector>
</div>
<!-- !tags -->
<!-- endpoints -->
<div ng-if="$ctrl.model.Id !== 1">
<div class="col-sm-12 form-section-title">

View file

@ -0,0 +1,8 @@
angular.module('portainer.app').component('tagSelector', {
templateUrl: 'app/portainer/components/tag-selector/tagSelector.html',
controller: 'TagSelectorController',
bindings: {
tags: '<',
model: '='
}
});

View file

@ -0,0 +1,38 @@
<div ng-show="$ctrl.model.length > 0" class="col-sm-12" style="padding: 0; margin-bottom: 15px;">
<label class="col-sm-3 col-lg-2 control-label text-left">
Selected tags
</label>
<div class="col-sm-9 col-lg-10" style="padding-top: 4px;">
<span class="tag space-right interactive" ng-repeat="tag in $ctrl.model" ng-click="$ctrl.removeTag(tag)">
{{ tag }}
<a title="Remove tag" ng-click="$ctrl.removeTag(tag)" style="margin-left: 2px;">
<span class="fa fa-trash-alt white-icon" aria-hidden="true"></span>
</a>
</span>
</div>
</div>
<div class="col-sm-12" style="padding: 0">
<label for="tags" class="col-sm-3 col-lg-2 control-label text-left">
Tags
</label>
<div class="col-sm-9 col-lg-10" ng-if="$ctrl.tags.length > 0">
<input
type="text" ng-model="$ctrl.state.selectedValue"
id="tags" class="form-control"
placeholder="Select tags..."
uib-typeahead="tag for tag in $ctrl.tags | filter:$viewValue | limitTo:7"
typeahead-on-select="$ctrl.selectTag($item, $model, $label)"
typeahead-no-results="$ctrl.state.noResult"
typeahead-show-hint="true" typeahead-min-length="0" />
</div>
<div class="col-sm-9 col-lg-10" ng-if="$ctrl.tags.length === 0">
<span class="small text-muted">
No tags available.
</span>
</div>
</div>
<div class="col-sm-offset-3 col-lg-offset-2 col-sm-12" ng-if="$ctrl.state.noResult" style="margin-top: 2px;">
<span class="small text-muted">
No tags matching your filter.
</span>
</div>

View file

@ -0,0 +1,32 @@
angular.module('portainer.app')
.controller('TagSelectorController', function () {
var ctrl = this;
this.$onChanges = function(changes) {
if(angular.isDefined(changes.tags.currentValue)) {
this.tags = _.difference(changes.tags.currentValue, this.model);
}
};
this.state = {
selectedValue: '',
noResult: false
};
this.selectTag = function($item, $model, $label) {
this.state.selectedValue = '';
this.model.push($item);
this.tags = _.remove(this.tags, function(item) {
return item !== $item;
});
};
this.removeTag = function(tag) {
var idx = this.model.indexOf(tag);
if (idx > -1) {
this.model.splice(idx, 1);
this.tags.push(tag);
}
};
});

View file

@ -1,14 +1,14 @@
function EndpointGroupDefaultModel() {
this.Name = '';
this.Description = '';
this.Labels = [];
this.Tags = [];
}
function EndpointGroupModel(data) {
this.Id = data.Id;
this.Name = data.Name;
this.Description = data.Description;
this.Labels = data.Labels;
this.Tags = data.Tags;
this.AuthorizedUsers = data.AuthorizedUsers;
this.AuthorizedTeams = data.AuthorizedTeams;
}
@ -16,7 +16,7 @@ function EndpointGroupModel(data) {
function EndpointGroupCreateRequest(model, endpoints) {
this.Name = model.Name;
this.Description = model.Description;
this.Labels = model.Labels;
this.Tags = model.Tags;
this.AssociatedEndpoints = endpoints;
}
@ -24,6 +24,6 @@ function EndpointGroupUpdateRequest(model, endpoints) {
this.id = model.Id;
this.Name = model.Name;
this.Description = model.Description;
this.Labels = model.Labels;
this.Tags = model.Tags;
this.AssociatedEndpoints = endpoints;
}

View file

@ -0,0 +1,4 @@
function TagViewModel(data) {
this.Id = data.ID;
this.Name = data.Name;
}

View file

@ -0,0 +1,9 @@
angular.module('portainer.app')
.factory('Tags', ['$resource', 'API_ENDPOINT_TAGS', function TagsFactory($resource, API_ENDPOINT_TAGS) {
'use strict';
return $resource(API_ENDPOINT_TAGS + '/:id', {}, {
create: { method: 'POST' },
query: { method: 'GET', isArray: true },
remove: { method: 'DELETE', params: { id: '@id'} }
});
}]);

View file

@ -57,7 +57,7 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
service.createLocalEndpoint = function() {
var deferred = $q.defer();
FileUploadService.createEndpoint('local', 1, 'unix:///var/run/docker.sock', '', 1, false)
FileUploadService.createEndpoint('local', 1, 'unix:///var/run/docker.sock', '', 1, [], false)
.then(function success(response) {
deferred.resolve(response.data);
})
@ -68,10 +68,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
return deferred.promise;
};
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
service.createRemoteEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
var deferred = $q.defer();
FileUploadService.createEndpoint(name, type, 'tcp://' + URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
FileUploadService.createEndpoint(name, type, 'tcp://' + URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success(response) {
deferred.resolve(response.data);
})
@ -82,10 +82,10 @@ function EndpointServiceFactory($q, Endpoints, FileUploadService) {
return deferred.promise;
};
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey) {
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) {
var deferred = $q.defer();
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
FileUploadService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
.then(function success(response) {
deferred.resolve(response.data);
})

View file

@ -0,0 +1,49 @@
angular.module('portainer.app')
.factory('TagService', ['$q', 'Tags', function TagServiceFactory($q, Tags) {
'use strict';
var service = {};
service.tags = function() {
var deferred = $q.defer();
Tags.query().$promise
.then(function success(data) {
var tags = data.map(function (item) {
return new TagViewModel(item);
});
deferred.resolve(tags);
})
.catch(function error(err) {
deferred.reject({msg: 'Unable to retrieve tags', err: err});
});
return deferred.promise;
};
service.tagNames = function() {
var deferred = $q.defer();
Tags.query().$promise
.then(function success(data) {
var tags = data.map(function (item) {
return item.Name;
});
deferred.resolve(tags);
})
.catch(function error(err) {
deferred.reject({msg: 'Unable to retrieve tags', err: err});
});
return deferred.promise;
};
service.createTag = function(name) {
var payload = {
Name: name
};
return Tags.create({}, payload).$promise;
};
service.deleteTag = function(id) {
return Tags.remove({id: id}).$promise;
};
return service;
}]);

View file

@ -52,7 +52,7 @@ angular.module('portainer.app')
});
};
service.createEndpoint = function(name, type, URL, PublicURL, groupID, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
service.createEndpoint = function(name, type, URL, PublicURL, groupID, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
return Upload.upload({
url: 'api/endpoints',
data: {
@ -61,6 +61,7 @@ angular.module('portainer.app')
URL: URL,
PublicURL: PublicURL,
GroupID: groupID,
Tags: Upload.json(tags),
TLS: TLS,
TLSSkipVerify: TLSSkipVerify,
TLSSkipClientVerify: TLSSkipClientVerify,
@ -72,12 +73,14 @@ angular.module('portainer.app')
});
};
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey) {
service.createAzureEndpoint = function(name, applicationId, tenantId, authenticationKey, groupId, tags) {
return Upload.upload({
url: 'api/endpoints',
data: {
Name: name,
EndpointType: 3,
GroupID: groupID,
Tags: Upload.json(tags),
AzureApplicationID: applicationId,
AzureTenantID: tenantId,
AzureAuthenticationKey: authenticationKey

View file

@ -1,6 +1,6 @@
angular.module('portainer.app')
.controller('CreateEndpointController', ['$scope', '$state', '$filter', 'EndpointService', 'GroupService', 'Notifications',
function ($scope, $state, $filter, EndpointService, GroupService, Notifications) {
.controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'EndpointService', 'GroupService', 'TagService', 'Notifications',
function ($q, $scope, $state, $filter, EndpointService, GroupService, TagService, Notifications) {
$scope.state = {
EnvironmentType: 'docker',
@ -15,7 +15,8 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
SecurityFormData: new EndpointSecurityFormData(),
AzureApplicationId: '',
AzureTenantId: '',
AzureAuthenticationKey: ''
AzureAuthenticationKey: '',
Tags: []
};
$scope.addDockerEndpoint = function() {
@ -23,6 +24,7 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
var URL = $filter('stripprotocol')($scope.formValues.URL);
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
var groupId = $scope.formValues.GroupId;
var tags = $scope.formValues.Tags;
var securityData = $scope.formValues.SecurityFormData;
var TLS = securityData.TLS;
@ -33,7 +35,7 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
addEndpoint(name, 1, URL, publicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
addEndpoint(name, 1, URL, publicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile);
};
$scope.addAgentEndpoint = function() {
@ -41,8 +43,9 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
var URL = $filter('stripprotocol')($scope.formValues.URL);
var publicURL = $scope.formValues.PublicURL === '' ? URL.split(':')[0] : $scope.formValues.PublicURL;
var groupId = $scope.formValues.GroupId;
var tags = $scope.formValues.Tags;
addEndpoint(name, 2, URL, publicURL, groupId, true, true, true, null, null, null);
addEndpoint(name, 2, URL, publicURL, groupId, tags, true, true, true, null, null, null);
};
$scope.addAzureEndpoint = function() {
@ -50,15 +53,17 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
var applicationId = $scope.formValues.AzureApplicationId;
var tenantId = $scope.formValues.AzureTenantId;
var authenticationKey = $scope.formValues.AzureAuthenticationKey;
var groupId = $scope.formValues.GroupId;
var tags = $scope.formValues.Tags;
createAzureEndpoint(name, applicationId, tenantId, authenticationKey);
createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags);
};
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey) {
function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags) {
var endpoint;
$scope.state.actionInProgress = true;
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tags)
.then(function success() {
Notifications.success('Endpoint created', name);
$state.go('portainer.endpoints', {}, {reload: true});
@ -71,9 +76,9 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
});
}
function addEndpoint(name, type, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
function addEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
$scope.state.actionInProgress = true;
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tags, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success() {
Notifications.success('Endpoint created', name);
$state.go('portainer.endpoints', {}, {reload: true});
@ -87,9 +92,13 @@ function ($scope, $state, $filter, EndpointService, GroupService, Notifications)
}
function initView() {
GroupService.groups()
$q.all({
groups: GroupService.groups(),
tags: TagService.tagNames()
})
.then(function success(data) {
$scope.groups = data;
$scope.groups = data.groups;
$scope.availableTags = data.tags;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to load groups');

View file

@ -192,6 +192,12 @@
<!-- !authenticationkey-input -->
</div>
<!-- !azure-details -->
<!-- endpoint-security -->
<por-endpoint-security ng-if="state.EnvironmentType === 'docker'" form-data="formValues.SecurityFormData"></por-endpoint-security>
<!-- !endpoint-security -->
<div class="col-sm-12 form-section-title">
Metadata
</div>
<!-- group -->
<div class="form-group">
<label for="endpoint_group" class="col-sm-3 col-lg-2 control-label text-left">
@ -202,9 +208,17 @@
</div>
</div>
<!-- !group -->
<!-- endpoint-security -->
<por-endpoint-security ng-if="state.EnvironmentType === 'docker'" form-data="formValues.SecurityFormData"></por-endpoint-security>
<!-- !endpoint-security -->
<!-- tags -->
<div class="form-group">
<tag-selector
tags="availableTags"
model="formValues.Tags"
></tag-selector>
</div>
<!-- !tags -->
<div class="col-sm-12 form-section-title">
Actions
</div>
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">

View file

@ -49,7 +49,7 @@
></azure-endpoint-config>
<!-- !endpoint-public-url-input -->
<div class="col-sm-12 form-section-title">
Grouping
Metadata
</div>
<!-- group -->
<div class="form-group">
@ -61,6 +61,14 @@
</div>
</div>
<!-- !group -->
<!-- tags -->
<div class="form-group">
<tag-selector
tags="availableTags"
model="endpoint.Tags"
></tag-selector>
</div>
<!-- !tags -->
<!-- endpoint-security -->
<div ng-if="endpointType === 'remote' && endpoint.Type !== 3">
<div class="col-sm-12 form-section-title">

View file

@ -1,6 +1,6 @@
angular.module('portainer.app')
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'EndpointProvider', 'Notifications',
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, EndpointProvider, Notifications) {
.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications',
function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupService, TagService, EndpointProvider, Notifications) {
if (!$scope.applicationState.application.endpointManagement) {
$state.go('portainer.endpoints');
@ -27,6 +27,7 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
Name: endpoint.Name,
PublicURL: endpoint.PublicURL,
GroupID: endpoint.GroupId,
Tags: endpoint.Tags,
TLS: TLS,
TLSSkipVerify: TLSSkipVerify,
TLSSkipClientVerify: TLSSkipClientVerify,
@ -61,7 +62,8 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
function initView() {
$q.all({
endpoint: EndpointService.endpoint($transition$.params().id),
groups: GroupService.groups()
groups: GroupService.groups(),
tags: TagService.tagNames()
})
.then(function success(data) {
var endpoint = data.endpoint;
@ -73,6 +75,7 @@ function ($q, $scope, $state, $transition$, $filter, EndpointService, GroupServi
endpoint.URL = $filter('stripprotocol')(endpoint.URL);
$scope.endpoint = endpoint;
$scope.groups = data.groups;
$scope.availableTags = data.tags;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');

View file

@ -1,19 +1,11 @@
angular.module('portainer.app')
.controller('CreateGroupController', ['$scope', '$state', 'GroupService', 'EndpointService', 'Notifications',
function ($scope, $state, GroupService, EndpointService, Notifications) {
.controller('CreateGroupController', ['$q', '$scope', '$state', 'GroupService', 'EndpointService', 'TagService', 'Notifications',
function ($q, $scope, $state, GroupService, EndpointService, TagService, Notifications) {
$scope.state = {
actionInProgress: false
};
$scope.addLabel = function() {
$scope.model.Labels.push({ name: '', value: '' });
};
$scope.removeLabel = function(index) {
$scope.model.Labels.splice(index, 1);
};
$scope.create = function() {
var model = $scope.model;
@ -40,10 +32,14 @@ function ($scope, $state, GroupService, EndpointService, Notifications) {
function initView() {
$scope.model = new EndpointGroupDefaultModel();
EndpointService.endpointsByGroup(1)
$q.all({
endpoints: EndpointService.endpointsByGroup(1),
tags: TagService.tagNames()
})
.then(function success(data) {
$scope.availableEndpoints = data;
$scope.availableEndpoints = data.endpoints;
$scope.associatedEndpoints = [];
$scope.availableTags = data.tags;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve endpoints');

View file

@ -12,6 +12,7 @@
<group-form
model="model"
available-endpoints="availableEndpoints"
available-tags="availableTags"
associated-endpoints="associatedEndpoints"
add-label-action="addLabel"
remove-label-action="removeLabel"

View file

@ -12,6 +12,7 @@
<group-form
model="group"
available-endpoints="availableEndpoints"
available-tags="availableTags"
associated-endpoints="associatedEndpoints"
add-label-action="addLabel"
remove-label-action="removeLabel"

View file

@ -1,19 +1,11 @@
angular.module('portainer.app')
.controller('GroupController', ['$q', '$scope', '$state', '$transition$', 'GroupService', 'EndpointService', 'Notifications',
function ($q, $scope, $state, $transition$, GroupService, EndpointService, Notifications) {
.controller('GroupController', ['$q', '$scope', '$state', '$transition$', 'GroupService', 'EndpointService', 'TagService', 'Notifications',
function ($q, $scope, $state, $transition$, GroupService, EndpointService, TagService, Notifications) {
$scope.state = {
actionInProgress: false
};
$scope.addLabel = function() {
$scope.group.Labels.push({ name: '', value: '' });
};
$scope.removeLabel = function(index) {
$scope.group.Labels.splice(index, 1);
};
$scope.update = function() {
var model = $scope.group;
@ -42,7 +34,8 @@ function ($q, $scope, $state, $transition$, GroupService, EndpointService, Notif
$q.all({
group: GroupService.group(groupId),
endpoints: EndpointService.endpoints()
endpoints: EndpointService.endpoints(),
tags: TagService.tagNames()
})
.then(function success(data) {
$scope.group = data.group;
@ -60,6 +53,7 @@ function ($q, $scope, $state, $transition$, GroupService, EndpointService, Notif
$scope.availableEndpoints = availableEndpoints;
$scope.associatedEndpoints = associatedEndpoints;
$scope.availableTags = data.tags;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to load view');

View file

@ -90,7 +90,7 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
var endpoint;
$scope.state.actionInProgress = true;
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey)
EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, 1, [])
.then(function success(data) {
endpoint = data;
EndpointProvider.setEndpointID(endpoint.Id);
@ -110,7 +110,7 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
function createRemoteEndpoint(name, type, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
var endpoint;
$scope.state.actionInProgress = true;
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, 1, [], TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
.then(function success(data) {
endpoint = data;
EndpointProvider.setEndpointID(endpoint.Id);

View file

@ -49,9 +49,12 @@
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="portainer.endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug fa-fw"></span></a>
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'portainer.endpoints' || $state.current.name === 'portainer.endpoints.endpoint' || $state.current.name === 'portainer.endpoints.endpoint.access' || $state.current.name === 'portainer.groups' || $state.current.name === 'portainer.groups.group' || $state.current.name === 'portainer.groups.group.access' || $state.current.name === 'portainer.groups.new')">
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'portainer.endpoints' || $state.current.name === 'portainer.endpoints.endpoint' || $state.current.name === 'portainer.endpoints.new' || $state.current.name === 'portainer.endpoints.endpoint.access' || $state.current.name === 'portainer.groups' || $state.current.name === 'portainer.groups.group' || $state.current.name === 'portainer.groups.group.access' || $state.current.name === 'portainer.groups.new' || $state.current.name === 'portainer.tags')">
<a ui-sref="portainer.groups" ui-sref-active="active">Groups</a>
</div>
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'portainer.endpoints' || $state.current.name === 'portainer.endpoints.endpoint' || $state.current.name === 'portainer.endpoints.new' || $state.current.name === 'portainer.endpoints.endpoint.access' || $state.current.name === 'portainer.groups' || $state.current.name === 'portainer.groups.group' || $state.current.name === 'portainer.groups.group.access' || $state.current.name === 'portainer.groups.new' || $state.current.name === 'portainer.tags')">
<a ui-sref="portainer.tags" ui-sref-active="active">Tags</a>
</div>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="portainer.registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database fa-fw"></span></a>

View file

@ -0,0 +1,58 @@
<rd-header>
<rd-header-title title-text="Tags">
<a data-toggle="tooltip" title="Refresh" ui-sref="portainer.tags" ui-sref-opts="{reload: true}">
<i class="fa fa-sync" aria-hidden="true"></i>
</a>
</rd-header-title>
<rd-header-content>Tag management</rd-header-content>
</rd-header>
<div class="row">
<div class="col-sm-12">
<rd-widget>
<rd-widget-header icon="fa-plus" title-text="Add a new tag">
</rd-widget-header>
<rd-widget-body>
<form class="form-horizontal" name="tagCreationForm" ng-submit="createTag()">
<!-- name-input -->
<div class="form-group">
<label for="name" class="col-sm-2 control-label text-left">
Name
</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="name" ng-model="formValues.Name" placeholder="org/acme" required auto-focus />
</div>
</div>
<div class="form-group" ng-show="tagCreationForm.name.$invalid">
<div class="col-sm-12 small text-danger">
<div ng-messages="tagCreationForm.name.$error">
<p ng-message="required"><i class="fa fa-exclamation-triangle" aria-hidden="true"></i> This field is required.</p>
</div>
</div>
</div>
<!-- !name-input -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="state.actionInProgress || !tagCreationForm.$valid" ng-click="createTag()" button-spinner="state.actionInProgress">
<span ng-hide="state.actionInProgress"><i class="fa fa-plus" aria-hidden="true"></i> Create tag</span>
<span ng-show="state.actionInProgress">Creating tag...</span>
</button>
</div>
</div>
</form>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<tags-datatable
title-text="Tags" title-icon="fa-tags"
dataset="tags" table-key="tags"
order-by="Name" show-text-filter="true"
remove-action="removeAction"
></tags-datatable>
</div>
</div>

View file

@ -0,0 +1,58 @@
angular.module('portainer.app')
.controller('TagsController', ['$scope', '$state', 'TagService', 'Notifications',
function ($scope, $state, TagService, Notifications) {
$scope.state = {
actionInProgress: false
};
$scope.formValues = {
Name: ''
};
$scope.removeAction = function (selectedItems) {
var actionCount = selectedItems.length;
angular.forEach(selectedItems, function (tag) {
TagService.deleteTag(tag.Id)
.then(function success() {
Notifications.success('Tag successfully removed', tag.Name);
var index = $scope.tags.indexOf(tag);
$scope.tags.splice(index, 1);
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to tag');
})
.finally(function final() {
--actionCount;
if (actionCount === 0) {
$state.reload();
}
});
});
};
$scope.createTag = function() {
var tagName = $scope.formValues.Name;
TagService.createTag(tagName)
.then(function success(data) {
Notifications.success('Tag successfully created', tagName);
$state.reload();
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to create tag');
});
};
function initView() {
TagService.tags()
.then(function success(data) {
$scope.tags = data;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve tags');
$scope.tags = [];
});
}
initView();
}]);