mirror of https://github.com/portainer/portainer
refactor(custom-templates): migrate list component to react [EE-6206] (#10440)
parent
14129632a3
commit
10c3ed42f0
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
<page-header title="'Custom Templates'" breadcrumbs="['Custom Templates']" reload="true"></page-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<custom-templates-list
|
||||
ng-if="$ctrl.templates"
|
||||
title-text="Templates"
|
||||
title-icon="edit"
|
||||
templates="$ctrl.templates"
|
||||
table-key="customTemplates"
|
||||
is-edit-allowed="$ctrl.isEditAllowed"
|
||||
on-select-click="($ctrl.selectTemplate)"
|
||||
on-delete-click="($ctrl.confirmDelete)"
|
||||
create-path="kubernetes.templates.custom.new"
|
||||
edit-path="kubernetes.templates.custom.edit"
|
||||
is-selected="($ctrl.isSelected)"
|
||||
></custom-templates-list>
|
||||
</div>
|
||||
</div>
|
||||
<custom-templates-list
|
||||
templates="$ctrl.templates"
|
||||
on-select="($ctrl.selectTemplate)"
|
||||
on-delete="($ctrl.confirmDelete)"
|
||||
selected-id="$ctrl.state.selectedTemplate.Id"
|
||||
></custom-templates-list>
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<div class="datatable">
|
||||
<rd-widget>
|
||||
<rd-widget-body classes="no-padding">
|
||||
<div class="toolBar vertical-center flex-wrap !gap-x-5 !gap-y-1">
|
||||
<div class="toolBarTitle vertical-center">
|
||||
<div class="widget-icon space-right">
|
||||
<pr-icon icon="$ctrl.titleIcon"></pr-icon>
|
||||
</div>
|
||||
Custom Templates
|
||||
</div>
|
||||
<div class="searchBar vertical-center !mr-0">
|
||||
<pr-icon icon="'search'" class-name="'searchIcon'"></pr-icon>
|
||||
<input
|
||||
type="text"
|
||||
class="searchInput"
|
||||
ng-model="$ctrl.state.textFilter"
|
||||
ng-change="$ctrl.onTextFilterChange()"
|
||||
placeholder="Search for a template..."
|
||||
auto-focus
|
||||
ng-model-options="{ debounce: 300 }"
|
||||
/>
|
||||
</div>
|
||||
<div class="actionBar">
|
||||
<button type="button" class="btn btn-sm btn-primary" ui-state="$ctrl.createPath">
|
||||
<pr-icon icon="'plus'"></pr-icon>
|
||||
Add Custom Template
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blocklist gap-y-2 !px-[20px] !pb-[20px]">
|
||||
<custom-templates-list-item
|
||||
ng-repeat="template in $ctrl.templates | filter:$ctrl.state.textFilter"
|
||||
template="template"
|
||||
on-select="($ctrl.onSelectClick)"
|
||||
on-delete="($ctrl.onDeleteClick)"
|
||||
is-selected="$ctrl.isSelected(template)"
|
||||
>
|
||||
</custom-templates-list-item>
|
||||
|
||||
<div ng-if="!$ctrl.templates" class="text-muted text-center"> Loading... </div>
|
||||
<div ng-if="($ctrl.templates | filter: $ctrl.state.textFilter).length === 0" class="text-muted text-center"> No templates available. </div>
|
||||
</div>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
|
@ -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();
|
||||
}
|
||||
});
|
|
@ -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: '<',
|
||||
},
|
||||
});
|
|
@ -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'])
|
||||
|
|
|
@ -62,20 +62,10 @@
|
|||
</advanced-form>
|
||||
</stack-from-template-form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<custom-templates-list
|
||||
ng-if="$ctrl.templates"
|
||||
title-text="Templates"
|
||||
title-icon="edit"
|
||||
templates="$ctrl.templates"
|
||||
table-key="customTemplates"
|
||||
create-path="docker.templates.custom.new"
|
||||
edit-path="docker.templates.custom.edit"
|
||||
is-edit-allowed="$ctrl.isEditAllowed"
|
||||
on-select-click="($ctrl.selectTemplate)"
|
||||
on-delete-click="($ctrl.confirmDelete)"
|
||||
is-selected="($ctrl.isSelected)"
|
||||
></custom-templates-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<custom-templates-list
|
||||
templates="$ctrl.templates"
|
||||
on-select="($ctrl.selectTemplate)"
|
||||
on-delete="($ctrl.confirmDelete)"
|
||||
selected-id="$ctrl.state.selectedTemplate.Id"
|
||||
></custom-templates-list>
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Table.Container>
|
||||
<DatatableHeader
|
||||
onSearchChange={listState.setSearch}
|
||||
searchValue={listState.search}
|
||||
title="Custom Templates"
|
||||
titleIcon={Edit}
|
||||
renderTableActions={() => (
|
||||
<Button as={Link} props={{ to: '.new' }} icon={Plus}>
|
||||
Add Custom Template
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="blocklist gap-y-2 !px-[20px] !pb-[20px]">
|
||||
{pagedTemplates.map((template) => (
|
||||
<CustomTemplatesListItem
|
||||
key={template.Id}
|
||||
template={template}
|
||||
onSelect={onSelect}
|
||||
isSelected={template.Id === selectedId}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
{!templates && <div className="text-muted text-center">Loading...</div>}
|
||||
{filteredTemplates.length === 0 && (
|
||||
<div className="text-muted text-center">No templates available.</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DatatableFooter
|
||||
onPageChange={setPage}
|
||||
page={page}
|
||||
onPageSizeChange={listState.setPageSize}
|
||||
pageSize={listState.pageSize}
|
||||
pageCount={Math.ceil(filteredTemplates.length / listState.pageSize)}
|
||||
totalSelected={0}
|
||||
/>
|
||||
</Table.Container>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue