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
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:
parent
5c6c66f010
commit
94c91035a7
28 changed files with 200 additions and 617 deletions
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { ListView } from './ListView';
|
|
@ -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,
|
||||
}
|
||||
: {};
|
||||
}
|
|
@ -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)}
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
>
|
|
@ -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}
|
||||
/>
|
|
@ -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);
|
|
@ -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',
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue