refactor(custom-templates): migrate list component to react [EE-6206] (#10440)

pull/6697/merge
Chaim Lev-Ari 2023-10-23 20:00:50 +03:00 committed by GitHub
parent 14129632a3
commit 10c3ed42f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 174 deletions

View File

@ -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');
}

View File

@ -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>

View File

@ -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>

View File

@ -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();
}
});

View File

@ -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: '<',
},
});

View File

@ -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'])

View File

@ -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>

View File

@ -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');
}

View File

@ -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>
);
}