diff --git a/.storybook/preview.js b/.storybook/preview.js index 0d13e4731..0e3e673af 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -3,6 +3,7 @@ import '../app/assets/css'; import { pushStateLocationPlugin, UIRouter } from '@uirouter/react'; import { initialize as initMSW, mswDecorator } from 'msw-storybook-addon'; import { handlers } from '@/setup-tests/server-handlers'; +import { QueryClient, QueryClientProvider } from 'react-query'; // Initialize MSW initMSW({ @@ -31,11 +32,17 @@ export const parameters = { }, }; +const testQueryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}); + export const decorators = [ (Story) => ( - - - + + + + + ), mswDecorator, ]; diff --git a/app/assets/css/app.css b/app/assets/css/app.css index 7866fac0f..13536a402 100644 --- a/app/assets/css/app.css +++ b/app/assets/css/app.css @@ -807,13 +807,6 @@ json-tree .branch-preview { } /* !spinkit override */ -/* uib-typeahead override */ -#scrollable-dropdown-menu .dropdown-menu { - max-height: 300px; - overflow-y: auto; -} -/* !uib-typeahead override */ - .kubectl-shell { display: block; text-align: center; diff --git a/app/edge/components/group-form/groupForm.html b/app/edge/components/group-form/groupForm.html index efba72b60..0c4c0cf46 100644 --- a/app/edge/components/group-form/groupForm.html +++ b/app/edge/components/group-form/groupForm.html @@ -93,12 +93,9 @@ -
- -
- No tags available. Head over to the Tags view to add tags -
-
+ + +
Associated environments by tags
this.model, @@ -34,6 +36,12 @@ export class EdgeGroupFormController { ); } + onChangeTags(value) { + return this.$scope.$evalAsync(() => { + this.model.TagIds = value; + }); + } + associateEndpoint(endpoint) { if (!_.includes(this.model.Endpoints, endpoint.Id)) { this.model.Endpoints = [...this.model.Endpoints, endpoint.Id]; diff --git a/app/edge/components/group-form/index.js b/app/edge/components/group-form/index.js index 98316f88e..2da9bfd7d 100644 --- a/app/edge/components/group-form/index.js +++ b/app/edge/components/group-form/index.js @@ -8,7 +8,6 @@ angular.module('portainer.edge').component('edgeGroupForm', { bindings: { model: '<', groups: '<', - tags: '<', formActionLabel: '@', formAction: '<', actionInProgress: '<', diff --git a/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupView.html b/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupView.html index 2732b58e0..8a3cada8e 100644 --- a/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupView.html +++ b/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupView.html @@ -13,9 +13,7 @@ form-action-label="Add edge group" form-action="$ctrl.createGroup" groups="$ctrl.endpointGroups" - tags="$ctrl.tags" model="$ctrl.model" - on-change-tags="($ctrl.onChangeTags)" > diff --git a/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupViewController.js b/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupViewController.js index ffa6a8a2c..89d2951e0 100644 --- a/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupViewController.js +++ b/app/edge/views/edge-groups/createEdgeGroupView/createEdgeGroupViewController.js @@ -1,9 +1,8 @@ export class CreateEdgeGroupController { /* @ngInject */ - constructor(EdgeGroupService, GroupService, TagService, Notifications, $state, $async) { + constructor(EdgeGroupService, GroupService, Notifications, $state, $async) { this.EdgeGroupService = EdgeGroupService; this.GroupService = GroupService; - this.TagService = TagService; this.Notifications = Notifications; this.$state = $state; this.$async = $async; @@ -26,8 +25,8 @@ export class CreateEdgeGroupController { } async $onInit() { - const [tags, endpointGroups] = await Promise.all([this.TagService.tags(), this.GroupService.groups()]); - this.tags = tags; + const endpointGroups = await this.GroupService.groups(); + this.endpointGroups = endpointGroups; this.state.loaded = true; } diff --git a/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupView.html b/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupView.html index ba26f203f..fe4c0e97a 100644 --- a/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupView.html +++ b/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupView.html @@ -14,7 +14,6 @@ form-action="$ctrl.updateGroup" endpoints="$ctrl.endpoints" groups="$ctrl.endpointGroups" - tags="$ctrl.tags" model="$ctrl.model" > diff --git a/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupViewController.js b/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupViewController.js index d2c910c31..0a0868a9b 100644 --- a/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupViewController.js +++ b/app/edge/views/edge-groups/editEdgeGroupView/editEdgeGroupViewController.js @@ -1,9 +1,8 @@ export class EditEdgeGroupController { /* @ngInject */ - constructor(EdgeGroupService, GroupService, TagService, Notifications, $state, $async) { + constructor(EdgeGroupService, GroupService, Notifications, $state, $async) { this.EdgeGroupService = EdgeGroupService; this.GroupService = GroupService; - this.TagService = TagService; this.Notifications = Notifications; this.$state = $state; this.$async = $async; @@ -18,13 +17,12 @@ export class EditEdgeGroupController { } async $onInit() { - const [tags, endpointGroups, group] = await Promise.all([this.TagService.tags(), this.GroupService.groups(), this.EdgeGroupService.group(this.$state.params.groupId)]); + const [endpointGroups, group] = await Promise.all([this.GroupService.groups(), this.EdgeGroupService.group(this.$state.params.groupId)]); if (!group) { this.Notifications.error('Failed to find edge group', {}); this.$state.go('edge.groups'); } - this.tags = tags; this.endpointGroups = endpointGroups; this.model = group; this.state.loaded = true; diff --git a/app/portainer/components/forms/group-form/group-form.js b/app/portainer/components/forms/group-form/group-form.js index 4e07f2b19..e5f815655 100644 --- a/app/portainer/components/forms/group-form/group-form.js +++ b/app/portainer/components/forms/group-form/group-form.js @@ -9,13 +9,11 @@ angular.module('portainer.app').component('groupForm', { pageType: '@', model: '=', availableEndpoints: '=', - availableTags: '<', associatedEndpoints: '=', addLabelAction: '<', removeLabelAction: '<', formAction: '<', formActionLabel: '@', actionInProgress: '<', - onCreateTag: '<', }, }); diff --git a/app/portainer/components/forms/group-form/groupForm.html b/app/portainer/components/forms/group-form/groupForm.html index 987650c56..72024899a 100644 --- a/app/portainer/components/forms/group-form/groupForm.html +++ b/app/portainer/components/forms/group-form/groupForm.html @@ -23,17 +23,9 @@
Metadata
- -
- -
- + + +
Associated environments
diff --git a/app/portainer/components/forms/group-form/groupFormController.js b/app/portainer/components/forms/group-form/groupFormController.js index 9f9eb7acf..70c390301 100644 --- a/app/portainer/components/forms/group-form/groupFormController.js +++ b/app/portainer/components/forms/group-form/groupFormController.js @@ -3,8 +3,9 @@ import angular from 'angular'; class GroupFormController { /* @ngInject */ - constructor($q, EndpointService, GroupService, Notifications, Authentication) { + constructor($q, $scope, EndpointService, GroupService, Notifications, Authentication) { this.$q = $q; + this.$scope = $scope; this.EndpointService = EndpointService; this.GroupService = GroupService; this.Notifications = Notifications; @@ -13,6 +14,13 @@ class GroupFormController { this.associateEndpoint = this.associateEndpoint.bind(this); this.dissociateEndpoint = this.dissociateEndpoint.bind(this); this.getPaginatedEndpointsByGroup = this.getPaginatedEndpointsByGroup.bind(this); + this.onChangeTags = this.onChangeTags.bind(this); + } + + onChangeTags(value) { + return this.$scope.$evalAsync(() => { + this.model.TagIds = value; + }); } $onInit() { diff --git a/app/portainer/components/tag-selector/tag-selector.js b/app/portainer/components/tag-selector/tag-selector.js deleted file mode 100644 index 8cc489bba..000000000 --- a/app/portainer/components/tag-selector/tag-selector.js +++ /dev/null @@ -1,10 +0,0 @@ -angular.module('portainer.app').component('tagSelector', { - templateUrl: './tagSelector.html', - controller: 'TagSelectorController', - bindings: { - tags: '<', - model: '=', - onCreate: '<', - allowCreate: '<', - }, -}); diff --git a/app/portainer/components/tag-selector/tagSelector.html b/app/portainer/components/tag-selector/tagSelector.html deleted file mode 100644 index 03c495c4a..000000000 --- a/app/portainer/components/tag-selector/tagSelector.html +++ /dev/null @@ -1,35 +0,0 @@ -
- -
- - {{ tag.Name }} - - - - -
-
-
- -
- -
-
- 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 deleted file mode 100644 index ff681825c..000000000 --- a/app/portainer/components/tag-selector/tagSelectorController.js +++ /dev/null @@ -1,62 +0,0 @@ -import angular from 'angular'; -import _ from 'lodash-es'; - -class TagSelectorController { - /* @ngInject */ - constructor() { - this.state = { - selectedValue: '', - selectedTags: [], - noResult: false, - }; - } - - 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; - } - - 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; - } - - return filteredTags.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/react/components/index.ts b/app/portainer/react/components/index.ts index 212e4f87e..181912b77 100644 --- a/app/portainer/react/components/index.ts +++ b/app/portainer/react/components/index.ts @@ -1,6 +1,11 @@ import angular from 'angular'; -export const componentsModule = angular.module( - 'portainer.app.react.components', - [] -).name; +import { r2a } from '@/react-tools/react2angular'; +import { TagSelector } from '@/react/components/TagSelector'; + +export const componentsModule = angular + .module('portainer.app.react.components', []) + .component( + 'tagSelector', + r2a(TagSelector, ['allowCreate', 'onChange', 'value']) + ).name; diff --git a/app/portainer/tags/tags.service.ts b/app/portainer/tags/tags.service.ts index 7fe24ca26..74ba6b1ca 100644 --- a/app/portainer/tags/tags.service.ts +++ b/app/portainer/tags/tags.service.ts @@ -13,7 +13,7 @@ export async function getTags() { export async function createTag(name: string) { try { - const { data: tag } = await axios.post(buildUrl(), { name }); + const { data: tag } = await axios.post(buildUrl(), { name }); return tag; } catch (err) { throw parseAxiosError(err as Error, 'Unable to create tag'); diff --git a/app/portainer/views/devices/import/importDevice.html b/app/portainer/views/devices/import/importDevice.html index 79d01d9b5..3f5ed7a2b 100644 --- a/app/portainer/views/devices/import/importDevice.html +++ b/app/portainer/views/devices/import/importDevice.html @@ -188,12 +188,9 @@
- -
- - -
- + + +
diff --git a/app/portainer/views/devices/import/importDeviceController.js b/app/portainer/views/devices/import/importDeviceController.js index ff9d7b8f1..7b597e005 100644 --- a/app/portainer/views/devices/import/importDeviceController.js +++ b/app/portainer/views/devices/import/importDeviceController.js @@ -28,6 +28,12 @@ angular $scope.profiles = []; + $scope.onChangeTags = function onChangeTags(value) { + return $scope.$evalAsync(() => { + $scope.formValues.TagIds = value; + }); + }; + $scope.onVoucherFilesChange = function () { if ($scope.formValues.VoucherFiles.length < 1) { return; @@ -53,20 +59,6 @@ angular }); }; - $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('Failure', err, 'Unable to create tag'); - } - } - $scope.createEndpointAndConfigureDevice = function () { return $async(async () => { $scope.state.actionInProgress = true; @@ -133,11 +125,9 @@ angular $q.all({ groups: GroupService.groups(), - tags: TagService.tags(), }) .then(function success(data) { $scope.groups = data.groups; - $scope.availableTags = data.tags; }) .catch(function error(err) { Notifications.error('Failure', err, 'Unable to load groups'); diff --git a/app/portainer/views/endpoints/create/createEndpointController.js b/app/portainer/views/endpoints/create/createEndpointController.js index 128b27ab9..2adb30fea 100644 --- a/app/portainer/views/endpoints/create/createEndpointController.js +++ b/app/portainer/views/endpoints/create/createEndpointController.js @@ -17,7 +17,6 @@ angular clipboard, EndpointService, GroupService, - TagService, SettingsService, Notifications, Authentication, @@ -91,20 +90,12 @@ angular $scope.formValues.URL = ''; }; - $scope.onCreateTag = function onCreateTag(tagName) { - return $async(onCreateTagAsync, tagName); + $scope.onChangeTags = function onChangeTags(value) { + return $scope.$evalAsync(() => { + $scope.formValues.TagIds = value; + }); }; - 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('Failure', err, 'Unable to create tag'); - } - } - function onChangeCheckInInterval(value) { setFieldValue('EdgeCheckinInterval', value); } @@ -310,12 +301,10 @@ angular function initView() { $q.all({ groups: GroupService.groups(), - tags: TagService.tags(), settings: SettingsService.settings(), }) .then(function success(data) { $scope.groups = data.groups; - $scope.availableTags = data.tags; const settings = data.settings; diff --git a/app/portainer/views/endpoints/create/createendpoint.html b/app/portainer/views/endpoints/create/createendpoint.html index b6e146290..ca57c0503 100644 --- a/app/portainer/views/endpoints/create/createendpoint.html +++ b/app/portainer/views/endpoints/create/createendpoint.html @@ -436,12 +436,9 @@
- -
- - -
- + + +
Actions
diff --git a/app/portainer/views/endpoints/edit/endpoint.html b/app/portainer/views/endpoints/edit/endpoint.html index baaa1c80b..d2bed2129 100644 --- a/app/portainer/views/endpoints/edit/endpoint.html +++ b/app/portainer/views/endpoints/edit/endpoint.html @@ -60,7 +60,7 @@
Join token

- For those prestaging the edge agent, use the following join token to associate the Edge agent with this environment. + For those pre-staging the edge agent, use the following join token to associate the Edge agent with this environment.

You can read more about pre-staging in the userguide available here.

@@ -159,11 +159,9 @@
- -
- -
- + + +
Security
diff --git a/app/portainer/views/endpoints/edit/endpointController.js b/app/portainer/views/endpoints/edit/endpointController.js index 328f031a8..39380fd8f 100644 --- a/app/portainer/views/endpoints/edit/endpointController.js +++ b/app/portainer/views/endpoints/edit/endpointController.js @@ -20,7 +20,7 @@ function EndpointController( clipboard, EndpointService, GroupService, - TagService, + Notifications, Authentication, SettingsService, @@ -28,6 +28,7 @@ function EndpointController( ) { $scope.onChangeCheckInInterval = onChangeCheckInInterval; $scope.setFieldValue = setFieldValue; + $scope.onChangeTags = onChangeTags; $scope.state = { uploadInProgress: false, @@ -51,26 +52,12 @@ function EndpointController( $('#copyNotificationEdgeKey').show().fadeOut(2500); }; - $scope.onCreateTag = function onCreateTag(tagName) { - return $async(onCreateTagAsync, tagName); - }; - $scope.onToggleAllowSelfSignedCerts = function onToggleAllowSelfSignedCerts(checked) { return $scope.$evalAsync(() => { $scope.state.allowSelfSignedCerts = checked; }); }; - 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.onDisassociateEndpoint = async function () { ModalService.confirmDisassociate((confirmed) => { if (confirmed) { @@ -98,6 +85,10 @@ function EndpointController( setFieldValue('EdgeCheckinInterval', value); } + function onChangeTags(value) { + setFieldValue('TagIds', value); + } + function setFieldValue(name, value) { return $scope.$evalAsync(() => { $scope.endpoint = { @@ -229,12 +220,7 @@ function EndpointController( async function initView() { return $async(async () => { try { - const [endpoint, groups, tags, settings] = await Promise.all([ - EndpointService.endpoint($transition$.params().id), - GroupService.groups(), - TagService.tags(), - SettingsService.settings(), - ]); + const [endpoint, groups, settings] = await Promise.all([EndpointService.endpoint($transition$.params().id), GroupService.groups(), SettingsService.settings()]); if (endpoint.URL.indexOf('unix://') === 0 || endpoint.URL.indexOf('npipe://') === 0) { $scope.endpointType = 'local'; @@ -254,7 +240,6 @@ function EndpointController( $scope.endpoint = endpoint; $scope.initialTagIds = endpoint.TagIds.slice(); $scope.groups = groups; - $scope.availableTags = tags; configureState(); diff --git a/app/portainer/views/groups/create/createGroupController.js b/app/portainer/views/groups/create/createGroupController.js index c09201fa9..881e24a8f 100644 --- a/app/portainer/views/groups/create/createGroupController.js +++ b/app/portainer/views/groups/create/createGroupController.js @@ -1,6 +1,6 @@ import { EndpointGroupDefaultModel } from '../../../models/group'; -angular.module('portainer.app').controller('CreateGroupController', function CreateGroupController($async, $scope, $state, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('CreateGroupController', function CreateGroupController($async, $scope, $state, GroupService, Notifications) { $scope.state = { actionInProgress: false, }; @@ -28,31 +28,10 @@ angular.module('portainer.app').controller('CreateGroupController', function Cre }); }; - $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) => { - $scope.availableTags = tags; - $scope.associatedEndpoints = []; - $scope.model = new EndpointGroupDefaultModel(); - $scope.loaded = true; - }) - .catch((err) => { - Notifications.error('Failure', err, 'Unable to retrieve tags'); - }); + $scope.associatedEndpoints = []; + $scope.model = new EndpointGroupDefaultModel(); + $scope.loaded = true; } initView(); diff --git a/app/portainer/views/groups/create/creategroup.html b/app/portainer/views/groups/create/creategroup.html index f19069355..9aa982758 100644 --- a/app/portainer/views/groups/create/creategroup.html +++ b/app/portainer/views/groups/create/creategroup.html @@ -12,14 +12,12 @@ page-type="create" model="model" available-endpoints="availableEndpoints" - available-tags="availableTags" associated-endpoints="associatedEndpoints" add-label-action="addLabel" remove-label-action="removeLabel" 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 ddbb927d1..40f5c246a 100644 --- a/app/portainer/views/groups/edit/group.html +++ b/app/portainer/views/groups/edit/group.html @@ -12,14 +12,12 @@ page-type="edit" model="group" available-endpoints="availableEndpoints" - available-tags="availableTags" associated-endpoints="associatedEndpoints" add-label-action="addLabel" remove-label-action="removeLabel" 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 bfb7ea418..571f78e9f 100644 --- a/app/portainer/views/groups/edit/groupController.js +++ b/app/portainer/views/groups/edit/groupController.js @@ -1,4 +1,4 @@ -angular.module('portainer.app').controller('GroupController', function GroupController($q, $async, $scope, $state, $transition$, GroupService, TagService, Notifications) { +angular.module('portainer.app').controller('GroupController', function GroupController($q, $scope, $state, $transition$, GroupService, Notifications) { $scope.state = { actionInProgress: false, }; @@ -20,30 +20,14 @@ angular.module('portainer.app').controller('GroupController', function GroupCont }); }; - $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; $q.all({ group: GroupService.group(groupId), - tags: TagService.tags(), }) .then(function success(data) { $scope.group = data.group; - $scope.availableTags = data.tags; $scope.loaded = true; }) .catch(function error(err) { diff --git a/app/react/components/TagSelector/TagSelector.tsx b/app/react/components/TagSelector/TagSelector.tsx index 559bfeff6..d268f2a0c 100644 --- a/app/react/components/TagSelector/TagSelector.tsx +++ b/app/react/components/TagSelector/TagSelector.tsx @@ -62,18 +62,19 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) { {value.length > 0 && ( {selectedTags.map((tag) => ( - +