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:
parent
b349f16090
commit
5e73a49473
50 changed files with 942 additions and 118 deletions
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
|
@ -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: '<'
|
||||
}
|
||||
});
|
|
@ -21,6 +21,7 @@ angular.module('portainer.app').component('groupForm', {
|
|||
bindings: {
|
||||
model: '=',
|
||||
availableEndpoints: '=',
|
||||
availableTags: '<',
|
||||
associatedEndpoints: '=',
|
||||
addLabelAction: '<',
|
||||
removeLabelAction: '<',
|
||||
|
|
|
@ -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">
|
||||
|
|
8
app/portainer/components/tag-selector/tag-selector.js
Normal file
8
app/portainer/components/tag-selector/tag-selector.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
angular.module('portainer.app').component('tagSelector', {
|
||||
templateUrl: 'app/portainer/components/tag-selector/tagSelector.html',
|
||||
controller: 'TagSelectorController',
|
||||
bindings: {
|
||||
tags: '<',
|
||||
model: '='
|
||||
}
|
||||
});
|
38
app/portainer/components/tag-selector/tagSelector.html
Normal file
38
app/portainer/components/tag-selector/tagSelector.html
Normal 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>
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
4
app/portainer/models/tag.js
Normal file
4
app/portainer/models/tag.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
function TagViewModel(data) {
|
||||
this.Id = data.ID;
|
||||
this.Name = data.Name;
|
||||
}
|
9
app/portainer/rest/tag.js
Normal file
9
app/portainer/rest/tag.js
Normal 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'} }
|
||||
});
|
||||
}]);
|
|
@ -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);
|
||||
})
|
||||
|
|
49
app/portainer/services/api/tagService.js
Normal file
49
app/portainer/services/api/tagService.js
Normal 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;
|
||||
}]);
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
58
app/portainer/views/tags/tags.html
Normal file
58
app/portainer/views/tags/tags.html
Normal 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>
|
58
app/portainer/views/tags/tagsController.js
Normal file
58
app/portainer/views/tags/tagsController.js
Normal 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();
|
||||
}]);
|
Loading…
Add table
Add a link
Reference in a new issue