1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

refactor(custom-templates): migrate list view to react [EE-2256] (#11611)
Some checks failed
ci / build_images (map[arch:amd64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
ci / build_images (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
ci / build_images (map[arch:arm platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:arm64 platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:ppc64le platform:linux version:]) (push) Has been cancelled
ci / build_images (map[arch:s390x platform:linux version:]) (push) Has been cancelled
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:linux]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:1809]) (push) Has been cancelled
Test / test-server (map[arch:amd64 platform:windows version:ltsc2022]) (push) Has been cancelled
Test / test-server (map[arch:arm64 platform:linux]) (push) Has been cancelled
ci / build_manifests (push) Has been cancelled

This commit is contained in:
Chaim Lev-Ari 2024-05-30 12:04:28 +03:00 committed by GitHub
parent 5c6c66f010
commit 94c91035a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 200 additions and 617 deletions

View file

@ -25,17 +25,18 @@ export function FormActions({
<FormSection title="Actions">
<div className="form-group">
<div className="col-sm-12">
<LoadingButton
className="!ml-0"
loadingText={loadingText}
isLoading={isLoading}
disabled={!isValid}
data-cy={dataCy}
>
{submitLabel}
</LoadingButton>
{children}
<div className="flex item-center gap-3">
<LoadingButton
className="!ml-0"
loadingText={loadingText}
isLoading={isLoading}
disabled={!isValid}
data-cy={dataCy}
>
{submitLabel}
</LoadingButton>
{children}
</div>
</div>
</div>
</FormSection>

View file

@ -1,47 +0,0 @@
import { notifySuccess } from '@/portainer/services/notifications';
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
import { useDeleteTemplateMutation } from '@/react/portainer/templates/custom-templates/queries/useDeleteTemplateMutation';
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
import { CustomTemplatesList } from '@/react/portainer/templates/custom-templates/ListView/CustomTemplatesList';
import { PageHeader } from '@@/PageHeader';
import { confirmDelete } from '@@/modals/confirm';
export function ListView() {
const templatesQuery = useCustomTemplates({
params: {
edge: true,
},
});
const deleteMutation = useDeleteTemplateMutation();
return (
<>
<PageHeader title="Custom Templates" breadcrumbs="Custom Templates" />
<CustomTemplatesList
templates={templatesQuery.data}
onDelete={handleDelete}
templateLinkParams={(template) => ({
to: 'edge.stacks.new',
params: { templateId: template.Id, templateType: 'custom' },
})}
storageKey="edge-custom-templates"
/>
</>
);
async function handleDelete(templateId: CustomTemplate['Id']) {
if (
!(await confirmDelete('Are you sure you want to delete this template?'))
) {
return;
}
deleteMutation.mutate(templateId, {
onSuccess: () => {
notifySuccess('Success', 'Template deleted');
},
});
}
}

View file

@ -1 +0,0 @@
export { ListView } from './ListView';

View file

@ -1,35 +0,0 @@
import { transformGitAuthenticationViewModel } from '@/react/portainer/gitops/AuthFieldset/utils';
import { GitFormModel } from '@/react/portainer/gitops/types';
export function toGitRequest(
gitConfig: GitFormModel,
credentialId: number | undefined
): GitFormModel {
return {
...gitConfig,
...getGitAuthValues(gitConfig, credentialId),
};
}
function getGitAuthValues(
gitConfig: GitFormModel | undefined,
credentialId: number | undefined
) {
if (!credentialId) {
return gitConfig;
}
const authModel = transformGitAuthenticationViewModel({
...gitConfig,
RepositoryGitCredentialID: credentialId,
});
return authModel
? {
RepositoryAuthentication: true,
RepositoryGitCredentialID: authModel.GitCredentialID,
RepositoryPassword: authModel.Password,
RepositoryUsername: authModel.Username,
}
: {};
}

View file

@ -15,20 +15,20 @@ import { CustomTemplatesListItem } from './CustomTemplatesListItem';
export function CustomTemplatesList({
templates,
onSelect,
onDelete,
selectedId,
templateLinkParams,
storageKey,
}: {
templates?: CustomTemplate[];
onSelect?: (template: CustomTemplate['Id']) => void;
onDelete: (template: CustomTemplate['Id']) => void;
onDelete: (templateId: CustomTemplate['Id']) => void;
selectedId?: CustomTemplate['Id'];
templateLinkParams?: (template: CustomTemplate) => {
to: string;
params: object;
};
templateLinkParams?: (template: CustomTemplate) =>
| {
to: string;
params: object;
}
| undefined;
storageKey: string;
}) {
const [page, setPage] = useState(0);
@ -68,7 +68,6 @@ export function CustomTemplatesList({
<CustomTemplatesListItem
key={template.Id}
template={template}
onSelect={onSelect}
isSelected={template.Id === selectedId}
onDelete={onDelete}
linkParams={templateLinkParams?.(template)}

View file

@ -0,0 +1,58 @@
import { notifySuccess } from '@/portainer/services/notifications';
import { useParamState } from '@/react/hooks/useParamState';
import { PageHeader } from '@@/PageHeader';
import { confirmDelete } from '@@/modals/confirm';
import { useCustomTemplates } from '../queries/useCustomTemplates';
import { useDeleteTemplateMutation } from '../queries/useDeleteTemplateMutation';
import { CustomTemplate } from '../types';
import { StackFromCustomTemplateFormWidget } from './StackFromCustomTemplateFormWidget';
import { CustomTemplatesList } from './CustomTemplatesList';
import { useViewParams } from './useViewParams';
export function ListView() {
const { params, getTemplateLinkParams, storageKey, viewType } =
useViewParams();
const templatesQuery = useCustomTemplates({
params,
});
const deleteMutation = useDeleteTemplateMutation();
const [selectedTemplateId] = useParamState<number>('template', (param) =>
param ? parseInt(param, 10) : 0
);
return (
<>
<PageHeader title="Custom Templates" breadcrumbs="Custom Templates" />
{viewType === 'docker' && !!selectedTemplateId && (
<StackFromCustomTemplateFormWidget templateId={selectedTemplateId} />
)}
<CustomTemplatesList
templates={templatesQuery.data}
onDelete={handleDelete}
templateLinkParams={getTemplateLinkParams}
storageKey={storageKey}
selectedId={selectedTemplateId}
/>
</>
);
async function handleDelete(templateId: CustomTemplate['Id']) {
if (
!(await confirmDelete('Are you sure you want to delete this template?'))
) {
return;
}
deleteMutation.mutate(templateId, {
onSuccess: () => {
notifySuccess('Success', 'Template deleted');
},
});
}
}

View file

@ -23,26 +23,24 @@ import {
import { StackType } from '@/react/common/stacks/types';
import { toGitFormModel } from '@/react/portainer/gitops/types';
import { AdvancedSettings } from '@/react/portainer/templates/app-templates/DeployFormWidget/AdvancedSettings';
import { useSwarmId } from '@/react/docker/proxy/queries/useSwarm';
import { Button } from '@@/buttons';
import { FormActions } from '@@/form-components/FormActions';
import { FormSection } from '@@/form-components/FormSection';
import { WebEditorForm } from '@@/WebEditorForm';
import { useSwarmId } from '../../proxy/queries/useSwarm';
import { Link } from '@@/Link';
import { FormValues } from './types';
import { useValidation } from './useValidation';
export function DeployForm({
template,
unselect,
templateFile,
isDeployable,
}: {
template: CustomTemplate;
templateFile: string;
unselect: () => void;
isDeployable: boolean;
}) {
const router = useRouter();
@ -157,7 +155,12 @@ export function DeployForm({
>
<Button
type="reset"
onClick={() => unselect()}
as={Link}
props={{
to: '.',
'data-cy': 'cancel-stack-creation',
params: { template: null },
}}
color="default"
data-cy="cancel-stack-creation"
>

View file

@ -1,6 +1,7 @@
import { DeployWidget } from '@/react/portainer/templates/components/DeployWidget';
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
import { useCustomTemplateFile } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplateFile';
import { useCustomTemplate } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplate';
import { TextTip } from '@@/Tip/TextTip';
@ -9,19 +10,21 @@ import { DeployForm } from './DeployForm';
import { TemplateLoadError } from './TemplateLoadError';
export function StackFromCustomTemplateFormWidget({
template,
unselect,
templateId,
}: {
template: CustomTemplate;
unselect: () => void;
templateId: CustomTemplate['Id'];
}) {
const isDeployable = useIsDeployable(template.Type);
const fileQuery = useCustomTemplateFile(template.Id);
const templateQuery = useCustomTemplate(templateId);
if (fileQuery.isLoading) {
const isDeployable = useIsDeployable(templateQuery.data?.Type);
const fileQuery = useCustomTemplateFile(templateId);
if (fileQuery.isLoading || !templateQuery.data) {
return null;
}
const template = templateQuery.data;
return (
<DeployWidget
logo={template.Logo}
@ -44,10 +47,10 @@ export function StackFromCustomTemplateFormWidget({
</div>
</div>
)}
{fileQuery.isSuccess && isDeployable && (
<DeployForm
template={template}
unselect={unselect}
templateFile={fileQuery.data}
isDeployable={isDeployable}
/>

View file

@ -1,9 +1,8 @@
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { StackType } from '@/react/common/stacks/types';
import { useIsSwarm } from '@/react/docker/proxy/queries/useInfo';
import { useIsSwarm } from '../../proxy/queries/useInfo';
export function useIsDeployable(type: StackType) {
export function useIsDeployable(type: StackType | undefined) {
const environmentId = useEnvironmentId();
const isSwarm = useIsSwarm(environmentId);

View file

@ -0,0 +1,73 @@
import { StackType } from '@/react/common/stacks/types';
import { useAuthorizations } from '@/react/hooks/useUser';
import { CustomTemplatesListParams } from '../queries/useCustomTemplates';
import { CustomTemplate } from '../types';
import { TemplateViewType, useViewType } from '../useViewType';
export function useViewParams(): {
viewType: TemplateViewType;
params: CustomTemplatesListParams;
getTemplateLinkParams?: (template: CustomTemplate) => {
to: string;
params: object;
};
storageKey: string;
} {
const viewType = useViewType();
const isAllowedDeploymentKubeQuery = useAuthorizations(
'K8sApplicationsAdvancedDeploymentRW'
);
const isAllowedDeploymentDockerQuery = useAuthorizations([
'DockerContainerCreate',
'PortainerStackCreate',
]);
switch (viewType) {
case 'kube':
return {
viewType,
params: { edge: false, type: [StackType.Kubernetes] },
getTemplateLinkParams: isAllowedDeploymentKubeQuery.authorized
? (template: CustomTemplate) => ({
to: 'kubernetes.deploy',
params: { templateId: template.Id, templateType: 'custom' },
})
: undefined,
storageKey: 'kube-custom-templates',
};
case 'edge':
return {
viewType,
params: { edge: true },
getTemplateLinkParams: (template: CustomTemplate) => ({
to: 'edge.stacks.new',
params: { templateId: template.Id, templateType: 'custom' },
}),
storageKey: 'edge-custom-templates',
};
case 'docker':
return {
viewType,
params: {
edge: false,
type: [StackType.DockerCompose, StackType.DockerSwarm],
},
getTemplateLinkParams: isAllowedDeploymentDockerQuery.authorized
? (template: CustomTemplate) => ({
to: '.',
params: { template: template.Id },
})
: undefined,
storageKey: 'docker-custom-templates',
};
default:
return {
viewType,
params: {},
getTemplateLinkParams: undefined,
storageKey: 'custom-templates',
};
}
}

View file

@ -20,6 +20,8 @@ type Params = {
edge?: boolean;
};
export { type Params as CustomTemplatesListParams };
export function useCustomTemplates<T = Array<CustomTemplate>>({
select,
params,
@ -38,6 +40,9 @@ async function getCustomTemplates({ type, edge }: Params = {}) {
type,
edge,
},
paramsSerializer: {
indexes: null,
},
});
return data;
} catch (e) {

View file

@ -4,15 +4,19 @@ export type TemplateViewType = 'kube' | 'docker' | 'edge';
export function useViewType(): TemplateViewType {
const {
state: { name },
state: { name = '' },
} = useCurrentStateAndParams();
if (name?.includes('kubernetes')) {
if (name.includes('kubernetes')) {
return 'kube';
}
if (name?.includes('docker')) {
if (name.includes('docker')) {
return 'docker';
}
return 'edge';
if (name.includes('edge')) {
return 'edge';
}
throw new Error(`Unknown view type: ${name}`);
}