diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts index c0627cf82..56558bd4f 100644 --- a/app/docker/react/components/index.ts +++ b/app/docker/react/components/index.ts @@ -3,6 +3,8 @@ import angular from 'angular'; import { r2a } from '@/react-tools/react2angular'; import { ContainersDatatableContainer } from '@/react/docker/containers/ListView/ContainersDatatable/ContainersDatatableContainer'; import { ContainerQuickActions } from '@/react/docker/containers/components/ContainerQuickActions'; +import { TemplateListDropdownAngular } from '@/react/docker/app-templates/TemplateListDropdown'; +import { TemplateListSortAngular } from '@/react/docker/app-templates/TemplateListSort'; export const componentsModule = angular .module('portainer.docker.react.components', []) @@ -26,4 +28,6 @@ export const componentsModule = angular 'status', 'taskId', ]) - ).name; + ) + .component('templateListDropdown', TemplateListDropdownAngular) + .component('templateListSort', TemplateListSortAngular).name; diff --git a/app/portainer/components/template-list/template-list-controller.js b/app/portainer/components/template-list/template-list-controller.js index 23554347f..7185250b3 100644 --- a/app/portainer/components/template-list/template-list-controller.js +++ b/app/portainer/components/template-list/template-list-controller.js @@ -2,48 +2,53 @@ import _ from 'lodash-es'; angular.module('portainer.app').controller('TemplateListController', TemplateListController); -function TemplateListController($async, $state, DatatableService, Notifications, TemplateService) { +function TemplateListController($scope, $async, $state, DatatableService, Notifications, TemplateService) { var ctrl = this; this.state = { textFilter: '', - selectedCategory: '', + selectedCategory: null, categories: [], + typeFilters: [], + filterByType: null, showContainerTemplates: true, + selectedOrderBy: null, + orderByFields: [], + orderDesc: false, }; this.onTextFilterChange = function () { DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter); }; - this.filterByTemplateType = function (item) { + ctrl.filterByTemplateType = function (item) { switch (item.Type) { case 1: // container return ctrl.state.showContainerTemplates; case 2: // swarm stack - return ctrl.showSwarmStacks; + return ctrl.showSwarmStacks && !ctrl.state.showContainerTemplates; case 3: // docker compose - return !ctrl.showSwarmStacks || (ctrl.showSwarmStacks && ctrl.state.showContainerTemplates); + return !ctrl.state.showContainerTemplates || null === ctrl.state.filterByType; case 4: // Edge stack templates return false; } return false; }; - this.updateCategories = function () { + ctrl.updateCategories = function () { var availableCategories = []; for (var i = 0; i < ctrl.templates.length; i++) { var template = ctrl.templates[i]; - if (this.filterByTemplateType(template)) { + if (ctrl.filterByTemplateType(template)) { availableCategories = availableCategories.concat(template.Categories); } } - this.state.categories = _.sortBy(_.uniq(availableCategories)); + ctrl.state.categories = _.sortBy(_.uniq(availableCategories)); }; - this.filterByCategory = function (item) { + ctrl.filterByCategory = function (item) { if (!ctrl.state.selectedCategory) { return true; } @@ -73,6 +78,40 @@ function TemplateListController($async, $state, DatatableService, Notifications, } } + ctrl.changeOrderBy = function (orderField) { + $scope.$evalAsync(() => { + if (null === orderField) { + ctrl.state.selectedOrderBy = null; + ctrl.templates = ctrl.initalTemplates; + } + + ctrl.state.selectedOrderBy = orderField; + ctrl.templates = _.orderBy(ctrl.templates, [getSorter(ctrl.state.selectedOrderBy)], [ctrl.state.orderDesc ? 'desc' : 'asc']); + }); + }; + + ctrl.applyTypeFilter = function (type) { + $scope.$evalAsync(() => { + ctrl.state.filterByType = type; + ctrl.state.showContainerTemplates = 'Container' === type || null === type; + ctrl.updateCategories(); + }); + }; + + ctrl.invertOrder = function () { + $scope.$evalAsync(() => { + ctrl.state.orderDesc = !ctrl.state.orderDesc; + ctrl.templates = _.orderBy(ctrl.templates, [getSorter(ctrl.state.selectedOrderBy)], [ctrl.state.orderDesc ? 'desc' : 'asc']); + }); + }; + + ctrl.applyCategoriesFilter = function (category) { + $scope.$evalAsync(() => { + ctrl.state.selectedCategory = category; + ctrl.updateCategories(); + }); + }; + this.$onInit = function () { if (this.showSwarmStacks) { this.state.showContainerTemplates = false; @@ -83,5 +122,28 @@ function TemplateListController($async, $state, DatatableService, Notifications, if (textFilter !== null) { this.state.textFilter = textFilter; } + + this.initalTemplates = this.templates; + this.state.orderByFields = ['Title', 'Categories', 'Description']; + this.state.typeFilters = ['Container', 'Stack']; }; + + function categorySorter(template) { + if (template.Categories && template.Categories.length > 0 && template.Categories[0] && template.Categories[0].length > 0) { + return template.Categories[0].toLowerCase(); + } + } + + function getSorter(orderBy) { + let sorter; + switch (orderBy) { + case 'Categories': + sorter = categorySorter; + break; + default: + sorter = orderBy; + } + + return sorter; + } } diff --git a/app/portainer/components/template-list/templateList.html b/app/portainer/components/template-list/templateList.html index 222b24f72..1ad09e43c 100644 --- a/app/portainer/components/template-list/templateList.html +++ b/app/portainer/components/template-list/templateList.html @@ -2,45 +2,45 @@ -
-
-
+
+
+
- - - - {{ $select.selected }} - - - {{ category }} - - - -
- -
- -
- - +
void; + placeholder?: string; + value: string; +} + +export function TemplateListDropdown({ + options, + onChange, + placeholder, + value, +}: Props) { + const filterOptions: Filter[] = options.map((value) => ({ label: value })); + const filterValue: Filter | null = value ? { label: value } : null; + + return ( +