diff --git a/app/portainer/components/forms/group-form/group-form.js b/app/portainer/components/forms/group-form/group-form.js index 10e12f62c..db298082a 100644 --- a/app/portainer/components/forms/group-form/group-form.js +++ b/app/portainer/components/forms/group-form/group-form.js @@ -1,92 +1,5 @@ -import _ from 'lodash-es'; import angular from 'angular'; - -class GroupFormController { - /* @ngInject */ - constructor($q, EndpointService, GroupService, Notifications) { - this.$q = $q; - this.EndpointService = EndpointService; - this.GroupService = GroupService; - this.Notifications = Notifications; - - this.associateEndpoint = this.associateEndpoint.bind(this); - this.dissociateEndpoint = this.dissociateEndpoint.bind(this); - this.getPaginatedEndpointsByGroup = this.getPaginatedEndpointsByGroup.bind(this); - } - - $onInit() { - this.state = { - available: { - limit: '10', - filter: '', - pageNumber: 1, - totalCount: 0 - }, - associated: { - limit: '10', - filter: '', - pageNumber: 1, - totalCount: 0 - } - }; - } - associateEndpoint(endpoint) { - if (this.pageType === 'create' && !_.includes(this.associatedEndpoints, endpoint)) { - 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')); - } - } - - dissociateEndpoint(endpoint) { - if (this.pageType === 'create') { - _.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')); - } - } - - reloadTablesContent() { - this.getPaginatedEndpointsByGroup(this.pageType, 'available'); - this.getPaginatedEndpointsByGroup(this.pageType, 'associated'); - 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.availableEndpoints = data.value; - this.state.available.totalCount = data.totalCount; - }); - } else if (tableType === 'associated' && pageType === 'edit') { - 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.associatedEndpoints = data.value; - this.state.associated.totalCount = data.totalCount; - }); - } - // ignore (associated + create) group as there is no backend pagination for this table - } -} +import GroupFormController from './groupFormController'; angular.module('portainer.app').component('groupForm', { templateUrl: './groupForm.html', @@ -102,6 +15,7 @@ angular.module('portainer.app').component('groupForm', { removeLabelAction: '<', formAction: '<', formActionLabel: '@', - actionInProgress: '<' + actionInProgress: '<', + onCreateTag: '<' } }); diff --git a/app/portainer/components/forms/group-form/groupForm.html b/app/portainer/components/forms/group-form/groupForm.html index addd43a01..5a0526b02 100644 --- a/app/portainer/components/forms/group-form/groupForm.html +++ b/app/portainer/components/forms/group-form/groupForm.html @@ -28,9 +28,11 @@
diff --git a/app/portainer/components/forms/group-form/groupFormController.js b/app/portainer/components/forms/group-form/groupFormController.js new file mode 100644 index 000000000..95db59e60 --- /dev/null +++ b/app/portainer/components/forms/group-form/groupFormController.js @@ -0,0 +1,94 @@ +import _ from 'lodash-es'; +import angular from 'angular'; + +class GroupFormController { + /* @ngInject */ + constructor($q, EndpointService, GroupService, Notifications, Authentication) { + this.$q = $q; + this.EndpointService = EndpointService; + this.GroupService = GroupService; + this.Notifications = Notifications; + this.Authentication = Authentication; + + this.associateEndpoint = this.associateEndpoint.bind(this); + this.dissociateEndpoint = this.dissociateEndpoint.bind(this); + this.getPaginatedEndpointsByGroup = this.getPaginatedEndpointsByGroup.bind(this); + } + + $onInit() { + this.state = { + available: { + limit: '10', + filter: '', + pageNumber: 1, + totalCount: 0 + }, + associated: { + limit: '10', + filter: '', + pageNumber: 1, + totalCount: 0 + }, + allowCreateTag: this.Authentication.isAdmin() + }; + } + associateEndpoint(endpoint) { + if (this.pageType === 'create' && !_.includes(this.associatedEndpoints, endpoint)) { + 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')); + } + } + + dissociateEndpoint(endpoint) { + if (this.pageType === 'create') { + _.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')); + } + } + + reloadTablesContent() { + this.getPaginatedEndpointsByGroup(this.pageType, 'available'); + this.getPaginatedEndpointsByGroup(this.pageType, 'associated'); + 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.availableEndpoints = data.value; + this.state.available.totalCount = data.totalCount; + }); + } else if (tableType === 'associated' && pageType === 'edit') { + 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.associatedEndpoints = data.value; + this.state.associated.totalCount = data.totalCount; + }); + } + // ignore (associated + create) group as there is no backend pagination for this table + } +} + +angular.module('portainer.app').controller('GroupFormController', GroupFormController); +export default GroupFormController; \ No newline at end of file diff --git a/app/portainer/components/tag-selector/tag-selector.js b/app/portainer/components/tag-selector/tag-selector.js index 540b16902..fc05755cf 100644 --- a/app/portainer/components/tag-selector/tag-selector.js +++ b/app/portainer/components/tag-selector/tag-selector.js @@ -4,5 +4,7 @@ angular.module('portainer.app').component('tagSelector', { bindings: { tags: '<', model: '=', + onCreate: '<', + allowCreate: '<' }, }); diff --git a/app/portainer/components/tag-selector/tagSelector.html b/app/portainer/components/tag-selector/tagSelector.html index d453f1acc..144f04624 100644 --- a/app/portainer/components/tag-selector/tagSelector.html +++ b/app/portainer/components/tag-selector/tagSelector.html @@ -15,24 +15,24 @@ -
+
-
+
No tags available.
-
+
No tags matching your filter. diff --git a/app/portainer/components/tag-selector/tagSelectorController.js b/app/portainer/components/tag-selector/tagSelectorController.js index 3d64485e3..40b99d3e5 100644 --- a/app/portainer/components/tag-selector/tagSelectorController.js +++ b/app/portainer/components/tag-selector/tagSelectorController.js @@ -1,35 +1,62 @@ +import angular from 'angular'; import _ from 'lodash-es'; -angular.module('portainer.app').controller('TagSelectorController', function() { - this.$onInit = function() { - this.state.selectedTags = _.map(this.model, (id) => _.find(this.tags, (t) => t.Id === id)); - }; - - this.state = { - selectedValue: '', - selectedTags: [], - noResult: false, - }; - - this.selectTag = function($item) { - this.state.selectedValue = ''; - this.model.push($item.Id); - this.state.selectedTags.push($item); - }; - - this.removeTag = function removeTag(tag) { - _.remove(this.state.selectedTags, { Id: tag.Id }); - _.remove(this.model, (id) => id === tag.Id); - }; - - this.filterSelected = filterSelected.bind(this); - - function filterSelected($item) { - if (!this.model) { - return true; - } - return !_.includes(this.model, $item.Id); +class TagSelectorController { + /* @ngInject */ + constructor() { + this.state = { + selectedValue: '', + selectedTags: [], + noResult: false, + }; } - window._remove = _.remove; -}); + removeTag(tag) { + _.remove(this.model, (id) => tag.Id === id); + _.remove(this.state.selectedTags, { Id: tag.Id }); + } + + selectTag($item) { + this.state.selectedValue = ''; + if ($item.create && this.allowCreate) { + this.onCreate($item.value); + return; + } + this.state.selectedTags.push($item); + this.model.push($item.Id); + } + + filterTags(searchValue) { + let filteredTags = _.filter(this.tags, (tag) => !_.includes(this.model, tag.Id)); + if (!searchValue) { + return filteredTags.slice(0, 7); + } + + const exactTag = _.find(this.tags, (tag) => tag.Name === searchValue); + filteredTags = _.filter(filteredTags, (tag) => _.includes(tag.Name.toLowerCase(), searchValue.toLowerCase())); + if (exactTag || !this.allowCreate) { + return filteredTags.slice(0, 7); + } + + return filteredTags.slice(0, 6).concat({ Name: `Create "${searchValue}"`, create: true, value: searchValue }); + } + + generateSelectedTags(model, tags) { + this.state.selectedTags = _.map(model, (id) => _.find(tags, (t) => t.Id === id)); + } + + $onInit() { + this.generateSelectedTags(this.model, this.tags); + } + + $onChanges({ tags, model }) { + const tagsValue = tags && tags.currentValue ? tags.currentValue : this.tags; + const modelValue = model && model.currentValue ? model.currentValue : this.model; + if (modelValue && tagsValue) { + this.generateSelectedTags(modelValue, tagsValue); + } + } +} + +export default TagSelectorController; +angular.module('portainer.app').controller('TagSelectorController', TagSelectorController); diff --git a/app/portainer/services/api/tagService.js b/app/portainer/services/api/tagService.js index b8c6e3918..d7c952dd2 100644 --- a/app/portainer/services/api/tagService.js +++ b/app/portainer/services/api/tagService.js @@ -35,12 +35,16 @@ angular.module('portainer.app') return deferred.promise; }; - service.createTag = function(name) { + service.createTag = async function(name) { var payload = { Name: name }; - - return Tags.create({}, payload).$promise; + try { + const tag = await Tags.create({}, payload).$promise; + return new TagViewModel(tag); + } catch(err) { + throw { msg: 'Unable to create tag', err }; + } }; service.deleteTag = function(id) { diff --git a/app/portainer/views/endpoints/create/createEndpointController.js b/app/portainer/views/endpoints/create/createEndpointController.js index 968aa041b..3bb7a989f 100644 --- a/app/portainer/views/endpoints/create/createEndpointController.js +++ b/app/portainer/views/endpoints/create/createEndpointController.js @@ -1,12 +1,12 @@ import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel'; -angular.module('portainer.app') -.controller('CreateEndpointController', ['$q', '$scope', '$state', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'Notifications', -function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('CreateEndpointController', +function CreateEndpointController($async, $q, $scope, $state, $filter, clipboard, EndpointService, GroupService, TagService, Notifications, Authentication) { $scope.state = { EnvironmentType: 'agent', - actionInProgress: false + actionInProgress: false, + allowCreateTag: Authentication.isAdmin() }; $scope.formValues = { @@ -84,6 +84,20 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.formValues.TagIds = $scope.formValues.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) { $scope.state.actionInProgress = true; EndpointService.createAzureEndpoint(name, applicationId, tenantId, authenticationKey, groupId, tagIds) @@ -133,4 +147,4 @@ function ($q, $scope, $state, $filter, clipboard, EndpointService, GroupService, } initView(); -}]); +}); diff --git a/app/portainer/views/endpoints/create/createendpoint.html b/app/portainer/views/endpoints/create/createendpoint.html index 89308e179..955453464 100644 --- a/app/portainer/views/endpoints/create/createendpoint.html +++ b/app/portainer/views/endpoints/create/createendpoint.html @@ -259,10 +259,12 @@
+ allow-create="state.allowCreateTag" + on-create="onCreateTag" + >
diff --git a/app/portainer/views/endpoints/edit/endpoint.html b/app/portainer/views/endpoints/edit/endpoint.html index d33e8bd7a..827a9aa97 100644 --- a/app/portainer/views/endpoints/edit/endpoint.html +++ b/app/portainer/views/endpoints/edit/endpoint.html @@ -165,9 +165,11 @@
diff --git a/app/portainer/views/endpoints/edit/endpointController.js b/app/portainer/views/endpoints/edit/endpointController.js index b01a5f1ad..451b6955f 100644 --- a/app/portainer/views/endpoints/edit/endpointController.js +++ b/app/portainer/views/endpoints/edit/endpointController.js @@ -3,8 +3,8 @@ import uuidv4 from 'uuid/v4'; import {EndpointSecurityFormData} from '../../../components/endpointSecurity/porEndpointSecurityModel'; angular.module('portainer.app') -.controller('EndpointController', ['$q', '$scope', '$state', '$transition$', '$filter', 'clipboard', 'EndpointService', 'GroupService', 'TagService', 'EndpointProvider', 'Notifications', -function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications) { +.controller('EndpointController', +function EndpointController($async, $q, $scope, $state, $transition$, $filter, clipboard, EndpointService, GroupService, TagService, EndpointProvider, Notifications, Authentication) { if (!$scope.applicationState.application.endpointManagement) { $state.go('portainer.endpoints'); @@ -13,13 +13,16 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, $scope.state = { uploadInProgress: false, actionInProgress: false, - deploymentTab: 0 + deploymentTab: 0, + allowCreate: Authentication.isAdmin() }; $scope.formValues = { SecurityFormData: new EndpointSecurityFormData() }; + + $scope.copyEdgeAgentDeploymentCommand = function() { if ($scope.state.deploymentTab === 0) { clipboard.copyText('docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/docker/volumes:/var/lib/docker/volumes -v /:/host --restart always -e EDGE=1 -e EDGE_ID=' + $scope.randomEdgeID + ' -e EDGE_KEY=' + $scope.endpoint.EdgeKey +' -e CAP_HOST_MANAGEMENT=1 -v portainer_agent_data:/data --name portainer_edge_agent portainer/agent'); @@ -34,6 +37,20 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, $('#copyNotificationEdgeKey').show().fadeOut(2500); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.endpoint.TagIds = $scope.endpoint.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + $scope.updateEndpoint = function() { var endpoint = $scope.endpoint; var securityData = $scope.formValues.SecurityFormData; @@ -120,4 +137,4 @@ function ($q, $scope, $state, $transition$, $filter, clipboard, EndpointService, } initView(); -}]); +}); diff --git a/app/portainer/views/groups/create/createGroupController.js b/app/portainer/views/groups/create/createGroupController.js index 109e70ca1..9308c93b0 100644 --- a/app/portainer/views/groups/create/createGroupController.js +++ b/app/portainer/views/groups/create/createGroupController.js @@ -1,8 +1,7 @@ import {EndpointGroupDefaultModel} from '../../../models/group'; angular.module('portainer.app') -.controller('CreateGroupController', ['$q', '$scope', '$state', 'GroupService', 'EndpointService', 'TagService', 'Notifications', -function ($q, $scope, $state, GroupService, EndpointService, TagService, Notifications) { +.controller('CreateGroupController', function CreateGroupController($async, $scope, $state, GroupService, TagService, Notifications) { $scope.state = { actionInProgress: false @@ -31,6 +30,20 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific }); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.model.TagIds = $scope.model.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function initView() { TagService.tags() .then((tags) => { @@ -45,4 +58,4 @@ function ($q, $scope, $state, GroupService, EndpointService, TagService, Notific } initView(); -}]); +}); diff --git a/app/portainer/views/groups/create/creategroup.html b/app/portainer/views/groups/create/creategroup.html index 480d04f81..e89b01e08 100644 --- a/app/portainer/views/groups/create/creategroup.html +++ b/app/portainer/views/groups/create/creategroup.html @@ -21,6 +21,7 @@ form-action="create" form-action-label="Create the group" action-in-progress="state.actionInProgress" + on-create-tag="onCreateTag" > diff --git a/app/portainer/views/groups/edit/group.html b/app/portainer/views/groups/edit/group.html index 7dca1c807..25b8f19a0 100644 --- a/app/portainer/views/groups/edit/group.html +++ b/app/portainer/views/groups/edit/group.html @@ -21,6 +21,7 @@ form-action="update" form-action-label="Update the group" action-in-progress="state.actionInProgress" + on-create-tag="onCreateTag" > diff --git a/app/portainer/views/groups/edit/groupController.js b/app/portainer/views/groups/edit/groupController.js index d173e40b1..a9bcf1812 100644 --- a/app/portainer/views/groups/edit/groupController.js +++ b/app/portainer/views/groups/edit/groupController.js @@ -1,6 +1,5 @@ -angular.module('portainer.app') -.controller('GroupController', ['$q', '$scope', '$state', '$transition$', 'GroupService', 'TagService', 'Notifications', -function ($q, $scope, $state, $transition$, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('GroupController', +function GroupController($q, $async, $scope, $state, $transition$, GroupService, TagService, Notifications) { $scope.state = { actionInProgress: false @@ -23,6 +22,20 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati }); }; + $scope.onCreateTag = function onCreateTag(tagName) { + return $async(onCreateTagAsync, tagName); + } + + async function onCreateTagAsync(tagName) { + try { + const tag = await TagService.createTag(tagName); + $scope.availableTags = $scope.availableTags.concat(tag); + $scope.group.TagIds = $scope.group.TagIds.concat(tag.Id); + } catch(err) { + Notifications.error('Failue', err, 'Unable to create tag'); + } + } + function initView() { var groupId = $transition$.params().id; @@ -41,4 +54,4 @@ function ($q, $scope, $state, $transition$, GroupService, TagService, Notificati } initView(); -}]); +});