diff --git a/app/docker/react/components/index.ts b/app/docker/react/components/index.ts
index 34e2a4d25..def6b6761 100644
--- a/app/docker/react/components/index.ts
+++ b/app/docker/react/components/index.ts
@@ -3,8 +3,6 @@ import angular from 'angular';
import { r2a } from '@/react-tools/react2angular';
import { withControlledInput } from '@/react-tools/withControlledInput';
import { StackContainersDatatable } from '@/react/common/stacks/ItemView/StackContainersDatatable';
-import { TemplateListDropdownAngular } from '@/react/docker/app-templates/TemplateListDropdown';
-import { TemplateListSortAngular } from '@/react/docker/app-templates/TemplateListSort';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withReactQuery } from '@/react-tools/withReactQuery';
import { withUIRouter } from '@/react-tools/withUIRouter';
@@ -39,8 +37,6 @@ const ngModule = angular
])
.component('dockerfileDetails', r2a(DockerfileDetails, ['image']))
.component('dockerHealthStatus', r2a(HealthStatus, ['health']))
- .component('templateListDropdown', TemplateListDropdownAngular)
- .component('templateListSort', TemplateListSortAngular)
.component(
'stackContainersDatatable',
r2a(
diff --git a/app/portainer/components/template-list/template-list-controller.js b/app/portainer/components/template-list/template-list-controller.js
deleted file mode 100644
index 7185250b3..000000000
--- a/app/portainer/components/template-list/template-list-controller.js
+++ /dev/null
@@ -1,149 +0,0 @@
-import _ from 'lodash-es';
-
-angular.module('portainer.app').controller('TemplateListController', TemplateListController);
-
-function TemplateListController($scope, $async, $state, DatatableService, Notifications, TemplateService) {
- var ctrl = this;
-
- this.state = {
- textFilter: '',
- selectedCategory: null,
- categories: [],
- typeFilters: [],
- filterByType: null,
- showContainerTemplates: true,
- selectedOrderBy: null,
- orderByFields: [],
- orderDesc: false,
- };
-
- this.onTextFilterChange = function () {
- DatatableService.setDataTableTextFilters(this.tableKey, this.state.textFilter);
- };
-
- ctrl.filterByTemplateType = function (item) {
- switch (item.Type) {
- case 1: // container
- return ctrl.state.showContainerTemplates;
- case 2: // swarm stack
- return ctrl.showSwarmStacks && !ctrl.state.showContainerTemplates;
- case 3: // docker compose
- return !ctrl.state.showContainerTemplates || null === ctrl.state.filterByType;
- case 4: // Edge stack templates
- return false;
- }
- return false;
- };
-
- ctrl.updateCategories = function () {
- var availableCategories = [];
-
- for (var i = 0; i < ctrl.templates.length; i++) {
- var template = ctrl.templates[i];
- if (ctrl.filterByTemplateType(template)) {
- availableCategories = availableCategories.concat(template.Categories);
- }
- }
-
- ctrl.state.categories = _.sortBy(_.uniq(availableCategories));
- };
-
- ctrl.filterByCategory = function (item) {
- if (!ctrl.state.selectedCategory) {
- return true;
- }
-
- return _.includes(item.Categories, ctrl.state.selectedCategory);
- };
-
- this.duplicateTemplate = duplicateTemplate.bind(this);
- this.duplicateTemplateAsync = duplicateTemplateAsync.bind(this);
- function duplicateTemplate(template) {
- return $async(this.duplicateTemplateAsync, template);
- }
-
- async function duplicateTemplateAsync(template) {
- try {
- const { FileContent: fileContent } = await TemplateService.templateFile(template.Repository.url, template.Repository.stackfile);
- let type = 0;
- if (template.Type === 2) {
- type = 1;
- }
- if (template.Type === 3) {
- type = 2;
- }
- $state.go('docker.templates.custom.new', { fileContent, type });
- } catch (err) {
- Notifications.error('Failure', err, 'Failed to duplicate template');
- }
- }
-
- 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;
- }
- this.updateCategories();
-
- var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
- 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/template-list.js b/app/portainer/components/template-list/template-list.js
deleted file mode 100644
index ec61e2a01..000000000
--- a/app/portainer/components/template-list/template-list.js
+++ /dev/null
@@ -1,13 +0,0 @@
-angular.module('portainer.app').component('templateList', {
- templateUrl: './templateList.html',
- controller: 'TemplateListController',
- bindings: {
- titleText: '@',
- titleIcon: '@',
- templates: '<',
- tableKey: '@',
- selectAction: '<',
- showSwarmStacks: '<',
- isSelected: '<',
- },
-});
diff --git a/app/portainer/components/template-list/templateList.html b/app/portainer/components/template-list/templateList.html
deleted file mode 100644
index 68c8ef110..000000000
--- a/app/portainer/components/template-list/templateList.html
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
Loading...
-
- No templates available.
-
-
-
-
-
diff --git a/app/portainer/react/components/custom-templates/index.ts b/app/portainer/react/components/custom-templates/index.ts
index 488bf752d..ca7aca80f 100644
--- a/app/portainer/react/components/custom-templates/index.ts
+++ b/app/portainer/react/components/custom-templates/index.ts
@@ -7,7 +7,6 @@ import { withControlledInput } from '@/react-tools/withControlledInput';
import { CustomTemplatesListItem } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesListItem';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { withUIRouter } from '@/react-tools/withUIRouter';
-import { AppTemplatesListItem } from '@/react/portainer/templates/app-templates/AppTemplatesListItem';
import {
CommonFields,
validation as commonFieldsValidation,
@@ -15,6 +14,7 @@ import {
import { PlatformField } from '@/react/portainer/custom-templates/components/PlatformSelector';
import { TemplateTypeSelector } from '@/react/portainer/custom-templates/components/TemplateTypeSelector';
import { withFormValidation } from '@/react-tools/withFormValidation';
+import { AppTemplatesList } from '@/react/portainer/templates/app-templates/AppTemplatesList';
import { VariablesFieldAngular } from './variables-field';
@@ -47,15 +47,7 @@ export const ngModule = angular
'isSelected',
])
)
- .component(
- 'appTemplatesListItem',
- r2a(withUIRouter(withCurrentUser(AppTemplatesListItem)), [
- 'onSelect',
- 'template',
- 'isSelected',
- 'onDuplicate',
- ])
- )
+
.component(
'customTemplatesPlatformSelector',
r2a(PlatformField, ['onChange', 'value'])
@@ -63,6 +55,15 @@ export const ngModule = angular
.component(
'customTemplatesTypeSelector',
r2a(TemplateTypeSelector, ['onChange', 'value'])
+ )
+ .component(
+ 'appTemplatesList',
+ r2a(withUIRouter(withCurrentUser(AppTemplatesList)), [
+ 'onSelect',
+ 'templates',
+ 'selectedId',
+ 'showSwarmStacks',
+ ])
);
withFormValidation(
diff --git a/app/portainer/services/api/templateService.js b/app/portainer/services/api/templateService.js
index 7259305f7..ec96917ca 100644
--- a/app/portainer/services/api/templateService.js
+++ b/app/portainer/services/api/templateService.js
@@ -1,5 +1,5 @@
import { commandStringToArray } from '@/docker/helpers/containers';
-import { TemplateViewModel } from '@/react/portainer/templates/app-templates/template';
+import { TemplateViewModel } from '@/react/portainer/templates/app-templates/view-model';
import { DockerHubViewModel } from 'Portainer/models/dockerhub';
angular.module('portainer.app').factory('TemplateService', TemplateServiceFactory);
diff --git a/app/portainer/views/templates/templates.html b/app/portainer/views/templates/templates.html
index a32a5f655..69ea724d8 100644
--- a/app/portainer/views/templates/templates.html
+++ b/app/portainer/views/templates/templates.html
@@ -270,18 +270,9 @@
-
+
diff --git a/app/portainer/views/templates/templatesController.js b/app/portainer/views/templates/templatesController.js
index 6edde966c..5e86e34d7 100644
--- a/app/portainer/views/templates/templatesController.js
+++ b/app/portainer/views/templates/templatesController.js
@@ -224,10 +224,6 @@ angular.module('portainer.app').controller('TemplatesController', [
}
};
- $scope.isSelected = function (template) {
- return $scope.state.selectedTemplate && $scope.state.selectedTemplate.Id === template.Id;
- };
-
$scope.unselectTemplate = function () {
return $async(async () => {
$scope.state.selectedTemplate = null;
@@ -237,7 +233,7 @@ angular.module('portainer.app').controller('TemplatesController', [
$scope.selectTemplate = function (template) {
return $async(async () => {
if ($scope.state.selectedTemplate) {
- $scope.unselectTemplate($scope.state.selectedTemplate);
+ await $scope.unselectTemplate($scope.state.selectedTemplate);
}
if (template.Network) {
diff --git a/app/react/docker/app-templates/TemplateListDropdown/TemplateListDropdown.tsx b/app/react/docker/app-templates/TemplateListDropdown/TemplateListDropdown.tsx
deleted file mode 100644
index c48fab24e..000000000
--- a/app/react/docker/app-templates/TemplateListDropdown/TemplateListDropdown.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Select } from '@@/form-components/ReactSelect';
-
-interface Filter {
- label?: string;
-}
-
-interface Props {
- options: string[];
- onChange: (value: string | null) => 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 (
-