mirror of https://github.com/portainer/portainer
				
				
				
			fix(edge/templates): fix issues with git templates [EE-6357] (#10679)
							parent
							
								
									974378c9b5
								
							
						
					
					
						commit
						2a18c9f215
					
				| 
						 | 
				
			
			@ -112,8 +112,7 @@ export default class CreateEdgeStackViewController {
 | 
			
		|||
              PrePullImage: template.EdgeSettings.PrePullImage || false,
 | 
			
		||||
              RetryDeploy: template.EdgeSettings.RetryDeploy || false,
 | 
			
		||||
              PrivateRegistryId: template.EdgeSettings.PrivateRegistryId || null,
 | 
			
		||||
              SupportRelativePath: template.EdgeSettings.RelativePathSettings.SupportRelativePath || false,
 | 
			
		||||
              FilesystemPath: template.EdgeSettings.RelativePathSettings.FilesystemPath || '',
 | 
			
		||||
              ...template.EdgeSettings.RelativePathSettings,
 | 
			
		||||
            }
 | 
			
		||||
          : {}),
 | 
			
		||||
      };
 | 
			
		||||
| 
						 | 
				
			
			@ -195,11 +194,7 @@ export default class CreateEdgeStackViewController {
 | 
			
		|||
  createStack() {
 | 
			
		||||
    return this.$async(async () => {
 | 
			
		||||
      const name = this.formValues.Name;
 | 
			
		||||
      let method = this.state.Method;
 | 
			
		||||
 | 
			
		||||
      if (method === 'template') {
 | 
			
		||||
        method = 'editor';
 | 
			
		||||
      }
 | 
			
		||||
      const method = getMethod(this.state.Method, this.state.templateValues.template);
 | 
			
		||||
 | 
			
		||||
      if (!this.validateForm(method)) {
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -338,3 +333,20 @@ export default class CreateEdgeStackViewController {
 | 
			
		|||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param {'template'|'repository' | 'editor' | 'upload'} method
 | 
			
		||||
 * @param {import('@/react/portainer/templates/custom-templates/types').CustomTemplate | undefined} template
 | 
			
		||||
 * @returns 'repository' | 'editor' | 'upload'
 | 
			
		||||
 */
 | 
			
		||||
function getMethod(method, template) {
 | 
			
		||||
  if (method !== 'template') {
 | 
			
		||||
    return method;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (template && template.GitConfig) {
 | 
			
		||||
    return 'repository';
 | 
			
		||||
  }
 | 
			
		||||
  return 'editor';
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,11 @@ class DockerComposeFormController {
 | 
			
		|||
    this.onChangeFile = this.onChangeFile.bind(this);
 | 
			
		||||
    this.onChangeMethod = this.onChangeMethod.bind(this);
 | 
			
		||||
    this.onChangeFormValues = this.onChangeFormValues.bind(this);
 | 
			
		||||
    this.isGitTemplate = this.isGitTemplate.bind(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isGitTemplate() {
 | 
			
		||||
    return this.state.Method === 'template' && !!this.templateValues.template && !!this.templateValues.template.GitConfig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChangeFormValues(newValues) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,7 +15,8 @@
 | 
			
		|||
  ng-required="true"
 | 
			
		||||
  yml="true"
 | 
			
		||||
  placeholder="Define or paste the content of your docker compose file here"
 | 
			
		||||
  read-only="$ctrl.state.Method === 'template' && $ctrl.template.GitConfig"
 | 
			
		||||
  versions="$ctrl.formValues.versions"
 | 
			
		||||
  read-only="$ctrl.isGitTemplate()"
 | 
			
		||||
>
 | 
			
		||||
  <editor-description>
 | 
			
		||||
    You can get more information about Compose file format in the
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@
 | 
			
		|||
  <file-upload-description> You can upload a Compose file from your computer. </file-upload-description>
 | 
			
		||||
</file-upload-form>
 | 
			
		||||
 | 
			
		||||
<div ng-if="$ctrl.state.Method == 'repository'">
 | 
			
		||||
<div ng-if="$ctrl.state.Method == 'repository' || $ctrl.isGitTemplate()">
 | 
			
		||||
  <git-form
 | 
			
		||||
    value="$ctrl.formValues"
 | 
			
		||||
    on-change="($ctrl.onChangeFormValues)"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,14 +27,13 @@ import { EdgeGroup } from '@/react/edge/edge-groups/types';
 | 
			
		|||
import { DeploymentType, EdgeStack } from '@/react/edge/edge-stacks/types';
 | 
			
		||||
import { EdgeGroupsSelector } from '@/react/edge/edge-stacks/components/EdgeGroupsSelector';
 | 
			
		||||
import { EdgeStackDeploymentTypeSelector } from '@/react/edge/edge-stacks/components/EdgeStackDeploymentTypeSelector';
 | 
			
		||||
import { useCurrentUser } from '@/react/hooks/useUser';
 | 
			
		||||
import { useCreateGitCredentialMutation } from '@/react/portainer/account/git-credentials/git-credentials.service';
 | 
			
		||||
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
 | 
			
		||||
import { notifySuccess } from '@/portainer/services/notifications';
 | 
			
		||||
import { EnvironmentType } from '@/react/portainer/environments/types';
 | 
			
		||||
import { Registry } from '@/react/portainer/registries/types';
 | 
			
		||||
import { useRegistries } from '@/react/portainer/registries/queries/useRegistries';
 | 
			
		||||
import { RelativePathFieldset } from '@/react/portainer/gitops/RelativePathFieldset/RelativePathFieldset';
 | 
			
		||||
import { parseRelativePathResponse } from '@/react/portainer/gitops/RelativePathFieldset/utils';
 | 
			
		||||
import { useSaveCredentialsIfRequired } from '@/react/portainer/account/git-credentials/queries/useCreateGitCredentialsMutation';
 | 
			
		||||
 | 
			
		||||
import { LoadingButton } from '@@/buttons';
 | 
			
		||||
import { FormSection } from '@@/form-components/FormSection';
 | 
			
		||||
| 
						 | 
				
			
			@ -65,8 +64,8 @@ interface FormValues {
 | 
			
		|||
export function GitForm({ stack }: { stack: EdgeStack }) {
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const updateStackMutation = useUpdateEdgeStackGitMutation();
 | 
			
		||||
  const saveCredentialsMutation = useCreateGitCredentialMutation();
 | 
			
		||||
  const { user } = useCurrentUser();
 | 
			
		||||
  const { saveCredentials, isLoading: isSaveCredentialsLoading } =
 | 
			
		||||
    useSaveCredentialsIfRequired();
 | 
			
		||||
 | 
			
		||||
  if (!stack.GitConfig) {
 | 
			
		||||
    return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +94,9 @@ export function GitForm({ stack }: { stack: EdgeStack }) {
 | 
			
		|||
            onUpdateSettingsClick={handleUpdateSettings}
 | 
			
		||||
            gitPath={gitConfig.ConfigFilePath}
 | 
			
		||||
            gitUrl={gitConfig.URL}
 | 
			
		||||
            isLoading={updateStackMutation.isLoading}
 | 
			
		||||
            isLoading={
 | 
			
		||||
              updateStackMutation.isLoading || isSaveCredentialsLoading
 | 
			
		||||
            }
 | 
			
		||||
            isUpdateVersion={!!updateStackMutation.variables?.updateVersion}
 | 
			
		||||
          />
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -105,9 +106,7 @@ export function GitForm({ stack }: { stack: EdgeStack }) {
 | 
			
		|||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const credentialId = await saveCredentialsIfRequired(
 | 
			
		||||
            values.authentication
 | 
			
		||||
          );
 | 
			
		||||
          const credentialId = await saveCredentials(values.authentication);
 | 
			
		||||
 | 
			
		||||
          updateStackMutation.mutate(getPayload(values, credentialId, false), {
 | 
			
		||||
            onSuccess() {
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +120,7 @@ export function GitForm({ stack }: { stack: EdgeStack }) {
 | 
			
		|||
  );
 | 
			
		||||
 | 
			
		||||
  async function handleSubmit(values: FormValues) {
 | 
			
		||||
    const credentialId = await saveCredentialsIfRequired(values.authentication);
 | 
			
		||||
    const credentialId = await saveCredentials(values.authentication);
 | 
			
		||||
 | 
			
		||||
    updateStackMutation.mutate(getPayload(values, credentialId, true), {
 | 
			
		||||
      onSuccess() {
 | 
			
		||||
| 
						 | 
				
			
			@ -151,29 +150,6 @@ export function GitForm({ stack }: { stack: EdgeStack }) {
 | 
			
		|||
      ...values,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async function saveCredentialsIfRequired(authentication: GitAuthModel) {
 | 
			
		||||
    if (
 | 
			
		||||
      !authentication.SaveCredential ||
 | 
			
		||||
      !authentication.RepositoryPassword ||
 | 
			
		||||
      !authentication.NewCredentialName
 | 
			
		||||
    ) {
 | 
			
		||||
      return authentication.RepositoryGitCredentialID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const credential = await saveCredentialsMutation.mutateAsync({
 | 
			
		||||
        userId: user.Id,
 | 
			
		||||
        username: authentication.RepositoryUsername,
 | 
			
		||||
        password: authentication.RepositoryPassword,
 | 
			
		||||
        name: authentication.NewCredentialName,
 | 
			
		||||
      });
 | 
			
		||||
      return credential.id;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      notifyError('Error', err as Error, 'Unable to save credentials');
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function InnerForm({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ export function PrivateRegistryFieldsetWrapper({
 | 
			
		|||
}) {
 | 
			
		||||
  const dryRunMutation = useParseRegistries();
 | 
			
		||||
 | 
			
		||||
  const registriesQuery = useRegistries();
 | 
			
		||||
  const registriesQuery = useRegistries({ hideDefault: true });
 | 
			
		||||
 | 
			
		||||
  if (!registriesQuery.data) {
 | 
			
		||||
    return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,9 +7,12 @@ import { useCreateTemplateMutation } from '@/react/portainer/templates/custom-te
 | 
			
		|||
import { Platform } from '@/react/portainer/templates/types';
 | 
			
		||||
import { useFetchTemplateFile } from '@/react/portainer/templates/app-templates/queries/useFetchTemplateFile';
 | 
			
		||||
import { getDefaultEdgeTemplateSettings } from '@/react/portainer/templates/custom-templates/types';
 | 
			
		||||
import { useSaveCredentialsIfRequired } from '@/react/portainer/account/git-credentials/queries/useCreateGitCredentialsMutation';
 | 
			
		||||
 | 
			
		||||
import { editor } from '@@/BoxSelector/common-options/build-methods';
 | 
			
		||||
 | 
			
		||||
import { toGitRequest } from '../common/git';
 | 
			
		||||
 | 
			
		||||
import { InnerForm } from './InnerForm';
 | 
			
		||||
import { FormValues } from './types';
 | 
			
		||||
import { useValidation } from './useValidation';
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +22,8 @@ export function CreateTemplateForm() {
 | 
			
		|||
  const mutation = useCreateTemplateMutation();
 | 
			
		||||
  const validation = useValidation();
 | 
			
		||||
  const { appTemplateId, type } = useParams();
 | 
			
		||||
  const { saveCredentials, isLoading: isSaveCredentialsLoading } =
 | 
			
		||||
    useSaveCredentialsIfRequired();
 | 
			
		||||
 | 
			
		||||
  const fileContentQuery = useFetchTemplateFile(appTemplateId);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,13 +63,19 @@ export function CreateTemplateForm() {
 | 
			
		|||
      validationSchema={validation}
 | 
			
		||||
      validateOnMount
 | 
			
		||||
    >
 | 
			
		||||
      <InnerForm isLoading={mutation.isLoading} />
 | 
			
		||||
      <InnerForm isLoading={mutation.isLoading || isSaveCredentialsLoading} />
 | 
			
		||||
    </Formik>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  function handleSubmit(values: FormValues) {
 | 
			
		||||
  async function handleSubmit(values: FormValues) {
 | 
			
		||||
    const credentialId = await saveCredentials(values.Git);
 | 
			
		||||
 | 
			
		||||
    mutation.mutate(
 | 
			
		||||
      { ...values, EdgeTemplate: true },
 | 
			
		||||
      {
 | 
			
		||||
        ...values,
 | 
			
		||||
        EdgeTemplate: true,
 | 
			
		||||
        Git: toGitRequest(values.Git, credentialId),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        onSuccess() {
 | 
			
		||||
          notifySuccess('Success', 'Template created');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ export function InnerForm({ isLoading }: { isLoading: boolean }) {
 | 
			
		|||
  usePreventExit(
 | 
			
		||||
    initialValues.FileContent,
 | 
			
		||||
    values.FileContent,
 | 
			
		||||
    values.Method === editor.value && !isSubmitting
 | 
			
		||||
    values.Method === editor.value && !isSubmitting && !isLoading
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const isGit = values.Method === git.value;
 | 
			
		||||
| 
						 | 
				
			
			@ -108,16 +108,7 @@ export function InnerForm({ isLoading }: { isLoading: boolean }) {
 | 
			
		|||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {isTemplateVariablesEnabled && (
 | 
			
		||||
        <CustomTemplatesVariablesDefinitionField
 | 
			
		||||
          value={values.Variables}
 | 
			
		||||
          onChange={(values) => setFieldValue('Variables', values)}
 | 
			
		||||
          isVariablesNamesFromParent={values.Method === editor.value}
 | 
			
		||||
          errors={errors.Variables}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {values.Method === git.value && (
 | 
			
		||||
      {isGit && (
 | 
			
		||||
        <GitForm
 | 
			
		||||
          value={values.Git}
 | 
			
		||||
          onChange={(newValues) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +121,15 @@ export function InnerForm({ isLoading }: { isLoading: boolean }) {
 | 
			
		|||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {isTemplateVariablesEnabled && (
 | 
			
		||||
        <CustomTemplatesVariablesDefinitionField
 | 
			
		||||
          value={values.Variables}
 | 
			
		||||
          onChange={(values) => setFieldValue('Variables', values)}
 | 
			
		||||
          isVariablesNamesFromParent={values.Method === editor.value}
 | 
			
		||||
          errors={errors.Variables}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {values.EdgeSettings && (
 | 
			
		||||
        <EdgeSettingsFieldset
 | 
			
		||||
          setValues={(edgeSetValues) =>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,9 @@ import {
 | 
			
		|||
  isTemplateVariablesEnabled,
 | 
			
		||||
} from '@/react/portainer/custom-templates/components/utils';
 | 
			
		||||
import { toGitFormModel } from '@/react/portainer/gitops/types';
 | 
			
		||||
import { useSaveCredentialsIfRequired } from '@/react/portainer/account/git-credentials/queries/useCreateGitCredentialsMutation';
 | 
			
		||||
 | 
			
		||||
import { toGitRequest } from '../common/git';
 | 
			
		||||
 | 
			
		||||
import { InnerForm } from './InnerForm';
 | 
			
		||||
import { FormValues } from './types';
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +25,8 @@ export function EditTemplateForm({ template }: { template: CustomTemplate }) {
 | 
			
		|||
  const isGit = !!template.GitConfig;
 | 
			
		||||
  const validation = useValidation(template.Id, isGit);
 | 
			
		||||
  const fileQuery = useCustomTemplateFile(template.Id, isGit);
 | 
			
		||||
  const { saveCredentials, isLoading: isSaveCredentialsLoading } =
 | 
			
		||||
    useSaveCredentialsIfRequired();
 | 
			
		||||
 | 
			
		||||
  if (fileQuery.isLoading) {
 | 
			
		||||
    return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +54,7 @@ export function EditTemplateForm({ template }: { template: CustomTemplate }) {
 | 
			
		|||
      validateOnMount
 | 
			
		||||
    >
 | 
			
		||||
      <InnerForm
 | 
			
		||||
        isLoading={mutation.isLoading}
 | 
			
		||||
        isLoading={mutation.isLoading || isSaveCredentialsLoading}
 | 
			
		||||
        isEditorReadonly={isGit}
 | 
			
		||||
        gitFileContent={isGit ? fileQuery.data : ''}
 | 
			
		||||
        refreshGitFile={fileQuery.refetch}
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +65,9 @@ export function EditTemplateForm({ template }: { template: CustomTemplate }) {
 | 
			
		|||
    </Formik>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  function handleSubmit(values: FormValues) {
 | 
			
		||||
  async function handleSubmit(values: FormValues) {
 | 
			
		||||
    const credentialId = await saveCredentials(values.Git);
 | 
			
		||||
 | 
			
		||||
    mutation.mutate(
 | 
			
		||||
      {
 | 
			
		||||
        id: template.Id,
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +81,7 @@ export function EditTemplateForm({ template }: { template: CustomTemplate }) {
 | 
			
		|||
        Platform: values.Platform,
 | 
			
		||||
        Variables: values.Variables,
 | 
			
		||||
        EdgeSettings: values.EdgeSettings,
 | 
			
		||||
        ...values.Git,
 | 
			
		||||
        ...(values.Git ? toGitRequest(values.Git, credentialId) : {}),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        onSuccess() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ export function InnerForm({
 | 
			
		|||
  usePreventExit(
 | 
			
		||||
    initialValues.FileContent,
 | 
			
		||||
    values.FileContent,
 | 
			
		||||
    !isEditorReadonly && !isSubmitting
 | 
			
		||||
    !isEditorReadonly && !isSubmitting && !isLoading
 | 
			
		||||
  );
 | 
			
		||||
  return (
 | 
			
		||||
    <Form className="form-horizontal">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
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,
 | 
			
		||||
      }
 | 
			
		||||
    : {};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,25 +6,7 @@ import { UserId } from '@/portainer/users/types';
 | 
			
		|||
 | 
			
		||||
import { isBE } from '../../feature-flags/feature-flags.service';
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  CreateGitCredentialPayload,
 | 
			
		||||
  GitCredential,
 | 
			
		||||
  UpdateGitCredentialPayload,
 | 
			
		||||
} from './types';
 | 
			
		||||
 | 
			
		||||
export async function createGitCredential(
 | 
			
		||||
  gitCredential: CreateGitCredentialPayload
 | 
			
		||||
) {
 | 
			
		||||
  try {
 | 
			
		||||
    const { data } = await axios.post<{ gitCredential: GitCredential }>(
 | 
			
		||||
      buildGitUrl(gitCredential.userId),
 | 
			
		||||
      gitCredential
 | 
			
		||||
    );
 | 
			
		||||
    return data.gitCredential;
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    throw parseAxiosError(e as Error, 'Unable to create git credential');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import { GitCredential, UpdateGitCredentialPayload } from './types';
 | 
			
		||||
 | 
			
		||||
export async function getGitCredentials(userId: number) {
 | 
			
		||||
  try {
 | 
			
		||||
| 
						 | 
				
			
			@ -141,24 +123,7 @@ export function useGitCredential(userId: number, id: number) {
 | 
			
		|||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useCreateGitCredentialMutation() {
 | 
			
		||||
  const queryClient = useQueryClient();
 | 
			
		||||
 | 
			
		||||
  return useMutation(createGitCredential, {
 | 
			
		||||
    onSuccess: (_, payload) => {
 | 
			
		||||
      notifySuccess('Credentials created successfully', payload.name);
 | 
			
		||||
      return queryClient.invalidateQueries(['gitcredentials']);
 | 
			
		||||
    },
 | 
			
		||||
    meta: {
 | 
			
		||||
      error: {
 | 
			
		||||
        title: 'Failure',
 | 
			
		||||
        message: 'Unable to create credential',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function buildGitUrl(userId: number, credentialId?: number) {
 | 
			
		||||
export function buildGitUrl(userId: number, credentialId?: number) {
 | 
			
		||||
  return credentialId
 | 
			
		||||
    ? `/users/${userId}/gitcredentials/${credentialId}`
 | 
			
		||||
    : `/users/${userId}/gitcredentials`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,82 @@
 | 
			
		|||
import { useQueryClient, useMutation } from 'react-query';
 | 
			
		||||
 | 
			
		||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
 | 
			
		||||
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
 | 
			
		||||
import { GitAuthModel } from '@/react/portainer/gitops/types';
 | 
			
		||||
import { useCurrentUser } from '@/react/hooks/useUser';
 | 
			
		||||
 | 
			
		||||
import { GitCredential } from '../types';
 | 
			
		||||
import { buildGitUrl } from '../git-credentials.service';
 | 
			
		||||
 | 
			
		||||
export interface CreateGitCredentialPayload {
 | 
			
		||||
  userId: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  username?: string;
 | 
			
		||||
  password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useCreateGitCredentialMutation() {
 | 
			
		||||
  const queryClient = useQueryClient();
 | 
			
		||||
 | 
			
		||||
  return useMutation(createGitCredential, {
 | 
			
		||||
    onSuccess: (_, payload) => {
 | 
			
		||||
      notifySuccess('Credentials created successfully', payload.name);
 | 
			
		||||
      return queryClient.invalidateQueries(['gitcredentials']);
 | 
			
		||||
    },
 | 
			
		||||
    meta: {
 | 
			
		||||
      error: {
 | 
			
		||||
        title: 'Failure',
 | 
			
		||||
        message: 'Unable to create credential',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function createGitCredential(gitCredential: CreateGitCredentialPayload) {
 | 
			
		||||
  try {
 | 
			
		||||
    const { data } = await axios.post<{ gitCredential: GitCredential }>(
 | 
			
		||||
      buildGitUrl(gitCredential.userId),
 | 
			
		||||
      gitCredential
 | 
			
		||||
    );
 | 
			
		||||
    return data.gitCredential;
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    throw parseAxiosError(e as Error, 'Unable to create git credential');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useSaveCredentialsIfRequired() {
 | 
			
		||||
  const saveCredentialsMutation = useCreateGitCredentialMutation();
 | 
			
		||||
  const { user } = useCurrentUser();
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    saveCredentials: saveCredentialsIfRequired,
 | 
			
		||||
    isLoading: saveCredentialsMutation.isLoading,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  async function saveCredentialsIfRequired(authentication?: GitAuthModel) {
 | 
			
		||||
    if (!authentication) {
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      !authentication.SaveCredential ||
 | 
			
		||||
      !authentication.RepositoryPassword ||
 | 
			
		||||
      !authentication.NewCredentialName
 | 
			
		||||
    ) {
 | 
			
		||||
      return authentication.RepositoryGitCredentialID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const credential = await saveCredentialsMutation.mutateAsync({
 | 
			
		||||
        userId: user.Id,
 | 
			
		||||
        username: authentication.RepositoryUsername,
 | 
			
		||||
        password: authentication.RepositoryPassword,
 | 
			
		||||
        name: authentication.NewCredentialName,
 | 
			
		||||
      });
 | 
			
		||||
      return credential.id;
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      notifyError('Error', err as Error, 'Unable to save credentials');
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13,13 +13,6 @@ export interface GitCredentialFormValues {
 | 
			
		|||
  password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CreateGitCredentialPayload {
 | 
			
		||||
  userId: number;
 | 
			
		||||
  name: string;
 | 
			
		||||
  username?: string;
 | 
			
		||||
  password: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface UpdateGitCredentialPayload {
 | 
			
		||||
  name: string;
 | 
			
		||||
  username?: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ export function RelativePathFieldset({
 | 
			
		|||
 | 
			
		||||
  const { errors } = useValidation(value);
 | 
			
		||||
 | 
			
		||||
  const { enableFsPath0, enableFsPath1, toggleFsPath } = useEnableFsPath();
 | 
			
		||||
  const { enableFsPath0, enableFsPath1, toggleFsPath } = useEnableFsPath(value);
 | 
			
		||||
 | 
			
		||||
  const pathTip0 =
 | 
			
		||||
    'For relative path volumes use with Docker Swarm, you must have a network filesystem which all of your nodes can access.';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,11 @@
 | 
			
		|||
import { useState } from 'react';
 | 
			
		||||
 | 
			
		||||
export function useEnableFsPath() {
 | 
			
		||||
  const [state, setState] = useState<number[]>([]);
 | 
			
		||||
import { RelativePathModel } from './types';
 | 
			
		||||
 | 
			
		||||
export function useEnableFsPath(initialValue: RelativePathModel) {
 | 
			
		||||
  const [state, setState] = useState<number[]>(() =>
 | 
			
		||||
    initialValue.SupportPerDeviceConfigs ? [1] : []
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const enableFsPath0 = state.length && state[0] === 0;
 | 
			
		||||
  const enableFsPath1 = state.length && state[0] === 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,11 +9,7 @@ import { usePublicSettings } from '../../settings/queries';
 | 
			
		|||
import { queryKeys } from './query-keys';
 | 
			
		||||
 | 
			
		||||
export function useRegistries<T = Registry[]>(
 | 
			
		||||
  queryOptions: {
 | 
			
		||||
    enabled?: boolean;
 | 
			
		||||
    select?: (registries: Registry[]) => T;
 | 
			
		||||
    onSuccess?: (data: T) => void;
 | 
			
		||||
  } = {}
 | 
			
		||||
  queryOptions: GenericRegistriesQueryOptions<T> = {}
 | 
			
		||||
) {
 | 
			
		||||
  return useGenericRegistriesQuery(
 | 
			
		||||
    queryKeys.base(),
 | 
			
		||||
| 
						 | 
				
			
			@ -22,13 +18,11 @@ export function useRegistries<T = Registry[]>(
 | 
			
		|||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @field hideDefault - is used to hide the default registry from the list of registries, regardless of the user's settings. Kubernetes views use this.
 | 
			
		||||
 */
 | 
			
		||||
export type GenericRegistriesQueryOptions<T> = {
 | 
			
		||||
  enabled?: boolean;
 | 
			
		||||
  select?: (registries: Registry[]) => T;
 | 
			
		||||
  onSuccess?: (data: T) => void;
 | 
			
		||||
  /** is used to hide the default registry from the list of registries, regardless of the user's settings. Kubernetes views use this. */
 | 
			
		||||
  hideDefault?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue