diff --git a/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.controller.js b/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.controller.js
index 23f4b546a..97dcd5f86 100644
--- a/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.controller.js
+++ b/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.controller.js
@@ -22,11 +22,6 @@ export default class KubeCustomTemplatesViewController {
this.validateForm = this.validateForm.bind(this);
this.confirmDelete = this.confirmDelete.bind(this);
this.selectTemplate = this.selectTemplate.bind(this);
- this.isSelected = this.isSelected.bind(this);
- }
-
- isSelected(templateId) {
- return this.state.selectedTemplate && this.state.selectedTemplate.Id === templateId;
}
selectTemplate(template) {
@@ -70,7 +65,7 @@ export default class KubeCustomTemplatesViewController {
var template = _.find(this.templates, { Id: templateId });
await this.CustomTemplateService.remove(templateId);
this.Notifications.success('Template successfully deleted', template && template.Title);
- _.remove(this.templates, { Id: templateId });
+ this.templates = this.templates.filter((template) => template.Id !== templateId);
} catch (err) {
this.Notifications.error('Failure', err, 'Failed to delete template');
}
diff --git a/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.html b/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.html
index bc35c5194..c191d4e58 100644
--- a/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.html
+++ b/app/kubernetes/custom-templates/kube-custom-templates-view/kube-custom-templates-view.html
@@ -1,19 +1,8 @@
-
+
diff --git a/app/portainer/components/custom-templates-list/customTemplatesList.html b/app/portainer/components/custom-templates-list/customTemplatesList.html
deleted file mode 100644
index 97fd126f2..000000000
--- a/app/portainer/components/custom-templates-list/customTemplatesList.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
Loading...
-
No templates available.
-
-
-
-
diff --git a/app/portainer/components/custom-templates-list/customTemplatesListController.js b/app/portainer/components/custom-templates-list/customTemplatesListController.js
deleted file mode 100644
index 818e47aad..000000000
--- a/app/portainer/components/custom-templates-list/customTemplatesListController.js
+++ /dev/null
@@ -1,57 +0,0 @@
-const CUSTOM_TEMPLATES_TYPES = {
- SWARM: 1,
- STANDALONE: 2,
- KUBERNETES: 3,
-};
-
-angular.module('portainer.docker').controller('CustomTemplatesListController', function ($scope, $controller, DatatableService) {
- angular.extend(this, $controller('GenericDatatableController', { $scope: $scope }));
-
- this.typeLabel = typeLabel;
- this.$onInit = $onInit;
-
- function typeLabel(type) {
- switch (type) {
- case CUSTOM_TEMPLATES_TYPES.SWARM:
- return 'swarm';
- case CUSTOM_TEMPLATES_TYPES.KUBERNETES:
- return 'manifest';
- case CUSTOM_TEMPLATES_TYPES.STANDALONE:
- default:
- return 'standalone';
- }
- }
-
- function $onInit() {
- this.setDefaults();
- this.prepareTableFromDataset();
-
- this.state.orderBy = this.orderBy;
- var storedOrder = DatatableService.getDataTableOrder(this.tableKey);
- if (storedOrder !== null) {
- this.state.reverseOrder = storedOrder.reverse;
- this.state.orderBy = storedOrder.orderBy;
- }
-
- var textFilter = DatatableService.getDataTableTextFilters(this.tableKey);
- if (textFilter !== null) {
- this.state.textFilter = textFilter;
- this.onTextFilterChange();
- }
-
- var storedFilters = DatatableService.getDataTableFilters(this.tableKey);
- if (storedFilters !== null) {
- this.filters = storedFilters;
- }
- if (this.filters && this.filters.state) {
- this.filters.state.open = false;
- }
-
- var storedSettings = DatatableService.getDataTableSettings(this.tableKey);
- if (storedSettings !== null) {
- this.settings = storedSettings;
- this.settings.open = false;
- }
- this.onSettingsRepeaterChange();
- }
-});
diff --git a/app/portainer/components/custom-templates-list/index.js b/app/portainer/components/custom-templates-list/index.js
deleted file mode 100644
index cf6ca2144..000000000
--- a/app/portainer/components/custom-templates-list/index.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import angular from 'angular';
-
-angular.module('portainer.app').component('customTemplatesList', {
- templateUrl: './customTemplatesList.html',
- controller: 'CustomTemplatesListController',
- bindings: {
- titleText: '@',
- titleIcon: '@',
- templates: '<',
- tableKey: '@',
- onSelectClick: '<',
- showSwarmStacks: '<',
- onDeleteClick: '<',
- isEditAllowed: '<',
- createPath: '@',
- editPath: '@',
- isSelected: '<',
- },
-});
diff --git a/app/portainer/react/components/custom-templates/index.ts b/app/portainer/react/components/custom-templates/index.ts
index ca7aca80f..333cfe768 100644
--- a/app/portainer/react/components/custom-templates/index.ts
+++ b/app/portainer/react/components/custom-templates/index.ts
@@ -4,7 +4,6 @@ import { r2a } from '@/react-tools/react2angular';
import { CustomTemplatesVariablesDefinitionField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesDefinitionField';
import { CustomTemplatesVariablesField } from '@/react/portainer/custom-templates/components/CustomTemplatesVariablesField';
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 {
@@ -15,6 +14,7 @@ import { PlatformField } from '@/react/portainer/custom-templates/components/Pla
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 { CustomTemplatesList } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesList';
import { VariablesFieldAngular } from './variables-field';
@@ -39,15 +39,14 @@ export const ngModule = angular
])
)
.component(
- 'customTemplatesListItem',
- r2a(withUIRouter(withCurrentUser(CustomTemplatesListItem)), [
+ 'customTemplatesList',
+ r2a(withUIRouter(withCurrentUser(CustomTemplatesList)), [
'onDelete',
'onSelect',
- 'template',
- 'isSelected',
+ 'templates',
+ 'selectedId',
])
)
-
.component(
'customTemplatesPlatformSelector',
r2a(PlatformField, ['onChange', 'value'])
diff --git a/app/portainer/views/custom-templates/custom-templates-view/customTemplatesView.html b/app/portainer/views/custom-templates/custom-templates-view/customTemplatesView.html
index cf7108a0a..7a2cfff49 100644
--- a/app/portainer/views/custom-templates/custom-templates-view/customTemplatesView.html
+++ b/app/portainer/views/custom-templates/custom-templates-view/customTemplatesView.html
@@ -62,20 +62,10 @@
-
+
+
diff --git a/app/portainer/views/custom-templates/custom-templates-view/customTemplatesViewController.js b/app/portainer/views/custom-templates/custom-templates-view/customTemplatesViewController.js
index 52b0d65a5..6a6ece2dc 100644
--- a/app/portainer/views/custom-templates/custom-templates-view/customTemplatesViewController.js
+++ b/app/portainer/views/custom-templates/custom-templates-view/customTemplatesViewController.js
@@ -82,11 +82,6 @@ class CustomTemplatesViewController {
this.isEditAllowed = this.isEditAllowed.bind(this);
this.onChangeFormValues = this.onChangeFormValues.bind(this);
this.onChangeTemplateVariables = this.onChangeTemplateVariables.bind(this);
- this.isSelected = this.isSelected.bind(this);
- }
-
- isSelected(templateId) {
- return this.state.selectedTemplate && this.state.selectedTemplate.Id === templateId;
}
isEditAllowed(template) {
@@ -260,7 +255,7 @@ class CustomTemplatesViewController {
var template = _.find(this.templates, { Id: templateId });
await this.CustomTemplateService.remove(templateId);
this.Notifications.success('Template successfully deleted', template && template.Title);
- _.remove(this.templates, { Id: templateId });
+ this.templates = this.templates.filter((template) => template.Id !== templateId);
} catch (err) {
this.Notifications.error('Failure', err, 'Failed to delete template');
}
diff --git a/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesList.tsx b/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesList.tsx
new file mode 100644
index 000000000..abed14a46
--- /dev/null
+++ b/app/react/portainer/templates/custom-templates/ListView/CustomTemplatesList.tsx
@@ -0,0 +1,88 @@
+import { Edit, Plus } from 'lucide-react';
+import _ from 'lodash';
+import { useCallback, useState } from 'react';
+
+import { CustomTemplate } from '@/react/portainer/custom-templates/types';
+
+import { DatatableHeader } from '@@/datatables/DatatableHeader';
+import { Table } from '@@/datatables';
+import { useTableState } from '@@/datatables/useTableState';
+import { createPersistedStore } from '@@/datatables/types';
+import { DatatableFooter } from '@@/datatables/DatatableFooter';
+import { Button } from '@@/buttons';
+import { Link } from '@@/Link';
+
+import { CustomTemplatesListItem } from './CustomTemplatesListItem';
+
+const tableKey = 'custom-templates-list';
+const store = createPersistedStore(tableKey);
+
+export function CustomTemplatesList({
+ templates,
+ onSelect,
+ onDelete,
+ selectedId,
+}: {
+ templates?: CustomTemplate[];
+ onSelect: (template: CustomTemplate['Id']) => void;
+ onDelete: (template: CustomTemplate['Id']) => void;
+ selectedId: CustomTemplate['Id'];
+}) {
+ const [page, setPage] = useState(0);
+
+ const listState = useTableState(store, tableKey);
+
+ const filterBySearch = useCallback(
+ (item: CustomTemplate) =>
+ item.Title.includes(listState.search) ||
+ item.Description.includes(listState.search) ||
+ item.Note?.includes(listState.search),
+ [listState.search]
+ );
+
+ const filteredTemplates = templates?.filter(filterBySearch) || [];
+
+ const pagedTemplates =
+ _.chunk(filteredTemplates, listState.pageSize)[page] || [];
+
+ return (
+
+ (
+
+ )}
+ />
+
+
+ {pagedTemplates.map((template) => (
+
+ ))}
+ {!templates &&
Loading...
}
+ {filteredTemplates.length === 0 && (
+
No templates available.
+ )}
+
+
+
+
+ );
+}