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 @@ -
- - -
-
-
- -
- Custom Templates -
- -
- -
-
- -
- - - -
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.
+ )} +
+ + +
+ ); +}