mirror of https://github.com/portainer/portainer
address review comments
parent
218a32a49f
commit
17971a10cc
|
@ -1,6 +1,6 @@
|
|||
import { StackId } from '../types';
|
||||
|
||||
export const stacksQueryKeys = {
|
||||
stackFile: (stackId: StackId) => ['stacks', stackId, 'file'],
|
||||
stacks: ['stacks'],
|
||||
stackFile: (stackId: StackId) => ['stacks', stackId, 'file'] as const,
|
||||
stacks: ['stacks'] as const,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { Stack, StackId } from '../types';
|
||||
|
||||
import { stacksQueryKeys } from './query-keys';
|
||||
import { buildStackUrl } from './buildUrl';
|
||||
|
||||
export function useStack(stackId?: StackId) {
|
||||
return useQuery(
|
||||
stacksQueryKeys.stackFile(stackId || 0),
|
||||
() => getStack(stackId!),
|
||||
{
|
||||
...withError('Unable to retrieve stack'),
|
||||
enabled: !!stackId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getStack(stackId: StackId) {
|
||||
try {
|
||||
const { data } = await axios.get<Stack>(buildStackUrl(stackId));
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err, 'Unable to retrieve stack');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { StackFile, StackId } from '../types';
|
||||
|
||||
import { stacksQueryKeys } from './query-keys';
|
||||
import { buildStackUrl } from './buildUrl';
|
||||
|
||||
export function useStackFile(stackId?: StackId) {
|
||||
return useQuery(
|
||||
stacksQueryKeys.stackFile(stackId || 0),
|
||||
() => getStackFile(stackId!),
|
||||
{
|
||||
...withError('Unable to retrieve stack'),
|
||||
enabled: !!stackId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getStackFile(stackId: StackId) {
|
||||
try {
|
||||
const { data } = await axios.get<StackFile>(buildStackUrl(stackId, 'file'));
|
||||
return data;
|
||||
} catch (err) {
|
||||
throw parseAxiosError(err, 'Unable to retrieve stack file');
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import axios from '@/portainer/services/axios';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
|
||||
import { Stack, StackFile, StackId } from '../types';
|
||||
|
||||
import { stacksQueryKeys } from './query-keys';
|
||||
import { buildStackUrl } from './buildUrl';
|
||||
|
||||
export function useStackQuery(stackId?: StackId) {
|
||||
return useQuery(
|
||||
stacksQueryKeys.stackFile(stackId || 0),
|
||||
() => getStack(stackId),
|
||||
{
|
||||
...withError('Unable to retrieve stack'),
|
||||
enabled: !!stackId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getStack(stackId?: StackId) {
|
||||
if (!stackId) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const { data } = await axios.get<Stack>(buildStackUrl(stackId));
|
||||
return data;
|
||||
}
|
||||
|
||||
export function useStackFileQuery(stackId?: StackId) {
|
||||
return useQuery(
|
||||
stacksQueryKeys.stackFile(stackId || 0),
|
||||
() => getStackFile(stackId),
|
||||
{
|
||||
...withError('Unable to retrieve stack'),
|
||||
enabled: !!stackId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function getStackFile(stackId?: StackId) {
|
||||
if (!stackId) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const { data } = await axios.get<StackFile>(buildStackUrl(stackId, 'file'));
|
||||
return data;
|
||||
}
|
|
@ -1,44 +1,65 @@
|
|||
import { useMutation } from 'react-query';
|
||||
|
||||
import axios from '@/portainer/services/axios';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import {
|
||||
withError,
|
||||
withInvalidate,
|
||||
queryClient,
|
||||
} from '@/react-tools/react-query';
|
||||
import { StackId } from '@/react/common/stacks/types';
|
||||
import { Stack, StackId } from '@/react/common/stacks/types';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { AutoUpdateModel } from '@/react/portainer/gitops/types';
|
||||
import { stacksQueryKeys } from '@/react/common/stacks/queries/query-keys';
|
||||
import { buildStackUrl } from '@/react/common/stacks/queries/buildUrl';
|
||||
import {
|
||||
AutoUpdateResponse,
|
||||
GitAuthModel,
|
||||
GitCredentialsModel,
|
||||
} from '@/react/portainer/gitops/types';
|
||||
import { saveGitCredentialsIfNeeded } from '@/react/portainer/account/git-credentials/queries/useCreateGitCredentialsMutation';
|
||||
|
||||
import { stacksQueryKeys } from './query-keys';
|
||||
import { buildStackUrl } from './buildUrl';
|
||||
|
||||
type UpdateKubeGitStackPayload = {
|
||||
AutoUpdate: AutoUpdateModel;
|
||||
RepositoryAuthentication: boolean;
|
||||
RepositoryGitCredentialID: number;
|
||||
RepositoryPassword: string;
|
||||
export interface UpdateKubeGitStackPayload extends GitCredentialsModel {
|
||||
AutoUpdate: AutoUpdateResponse | null;
|
||||
RepositoryReferenceName: string;
|
||||
RepositoryUsername: string;
|
||||
TLSSkipVerify: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// update a stack from a git repository
|
||||
export function useUpdateKubeGitStackMutation(
|
||||
stackId: StackId,
|
||||
environmentId: EnvironmentId
|
||||
environmentId: EnvironmentId,
|
||||
userId: number
|
||||
) {
|
||||
return useMutation(
|
||||
(stack: UpdateKubeGitStackPayload) =>
|
||||
updateGitStack({ stack, stackId, environmentId }),
|
||||
async ({
|
||||
stack,
|
||||
authentication,
|
||||
}: {
|
||||
stack: UpdateKubeGitStackPayload;
|
||||
authentication: GitAuthModel;
|
||||
}) => {
|
||||
// save the new git credentials if the user has selected to save them
|
||||
const newGitAuth = await saveGitCredentialsIfNeeded(
|
||||
userId,
|
||||
authentication
|
||||
);
|
||||
const stackWithUpdatedAuth: UpdateKubeGitStackPayload = {
|
||||
...stack,
|
||||
...newGitAuth,
|
||||
};
|
||||
return updateGitStack({
|
||||
stack: stackWithUpdatedAuth,
|
||||
stackId,
|
||||
environmentId,
|
||||
});
|
||||
},
|
||||
{
|
||||
...withError('Unable to update stack'),
|
||||
...withError('Unable to update application'),
|
||||
...withInvalidate(queryClient, [stacksQueryKeys.stackFile(stackId)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateGitStack({
|
||||
async function updateGitStack({
|
||||
stackId,
|
||||
stack,
|
||||
environmentId,
|
||||
|
@ -47,7 +68,11 @@ function updateGitStack({
|
|||
stack: UpdateKubeGitStackPayload;
|
||||
environmentId: EnvironmentId;
|
||||
}) {
|
||||
return axios.put(buildStackUrl(stackId), stack, {
|
||||
params: { endpointId: environmentId },
|
||||
});
|
||||
try {
|
||||
return await axios.post<Stack>(buildStackUrl(stackId, 'git'), stack, {
|
||||
params: { endpointId: environmentId },
|
||||
});
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e, 'Unable to update stack');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
||||
|
@ -11,6 +11,7 @@ interface Props extends AutomationTestingProps {
|
|||
loadingText: string;
|
||||
isLoading: boolean;
|
||||
isValid: boolean;
|
||||
submitButtonIcon?: ReactNode;
|
||||
}
|
||||
|
||||
export function FormActions({
|
||||
|
@ -19,6 +20,7 @@ export function FormActions({
|
|||
isLoading,
|
||||
children,
|
||||
isValid,
|
||||
submitButtonIcon,
|
||||
'data-cy': dataCy,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
|
@ -30,6 +32,7 @@ export function FormActions({
|
|||
loadingText={loadingText}
|
||||
isLoading={isLoading}
|
||||
disabled={!isValid}
|
||||
icon={submitButtonIcon}
|
||||
data-cy={dataCy}
|
||||
>
|
||||
{submitLabel}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useCurrentStateAndParams } from '@uirouter/react';
|
|||
import { Pod } from 'kubernetes-types/core/v1';
|
||||
|
||||
import { Authorized } from '@/react/hooks/useUser';
|
||||
import { useStackFileQuery } from '@/react/common/stacks/queries/useStackQuery';
|
||||
import { useStackFile } from '@/react/common/stacks/queries/useStackFile';
|
||||
|
||||
import { Widget, WidgetBody } from '@@/Widget';
|
||||
import { Button } from '@@/buttons';
|
||||
|
@ -48,7 +48,7 @@ export function ApplicationDetailsWidget() {
|
|||
);
|
||||
const externalApp = app && isExternalApplication(app);
|
||||
const appStackId = Number(app?.metadata?.labels?.[appStackIdLabel]);
|
||||
const appStackFileQuery = useStackFileQuery(appStackId);
|
||||
const appStackFileQuery = useStackFile(appStackId);
|
||||
const { data: appServices } = useApplicationServices(
|
||||
environmentId,
|
||||
namespace,
|
||||
|
|
|
@ -1,53 +1,33 @@
|
|||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import { useRef, useState } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useRouter } from '@uirouter/react';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import {
|
||||
UpdateKubeGitStackPayload,
|
||||
useUpdateKubeGitStackMutation,
|
||||
} from '@/react/common/stacks/queries/useUpdateKubeGitStackMutation';
|
||||
import { Stack, StackId } from '@/react/common/stacks/types';
|
||||
import {
|
||||
parseAutoUpdateResponse,
|
||||
transformAutoUpdateViewModel,
|
||||
} from '@/react/portainer/gitops/AutoUpdateFieldset/utils';
|
||||
import { AutoUpdateFieldset } from '@/react/portainer/gitops/AutoUpdateFieldset';
|
||||
import {
|
||||
AutoUpdateMechanism,
|
||||
AutoUpdateModel,
|
||||
} from '@/react/portainer/gitops/types';
|
||||
import {
|
||||
baseStackWebhookUrl,
|
||||
createWebhookId,
|
||||
} from '@/portainer/helpers/webhookHelper';
|
||||
import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
|
||||
import { RefField } from '@/react/portainer/gitops/RefField';
|
||||
import { AutoUpdateMechanism } from '@/react/portainer/gitops/types';
|
||||
import { createWebhookId } from '@/portainer/helpers/webhookHelper';
|
||||
import { parseAuthResponse } from '@/react/portainer/gitops/AuthFieldset/utils';
|
||||
import { confirmEnableTLSVerify } from '@/react/portainer/gitops/utils';
|
||||
import { AuthFieldset } from '@/react/portainer/gitops/AuthFieldset';
|
||||
import { useGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
import { useAnalytics } from '@/react/hooks/useAnalytics';
|
||||
import { RepositoryMechanismTypes } from '@/kubernetes/models/deploy';
|
||||
import { useCreateGitCredentialMutation } from '@/react/portainer/account/git-credentials/queries/useCreateGitCredentialsMutation';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { useStackQuery } from '@/react/common/stacks/queries/useStackQuery';
|
||||
import { useStack } from '@/react/common/stacks/queries/useStack';
|
||||
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { LoadingButton } from '@@/buttons';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { InlineLoader } from '@@/InlineLoader';
|
||||
import { Alert } from '@@/Alert';
|
||||
|
||||
import { useUpdateKubeGitStackMutation } from './queries/useUpdateKubeGitStackMutation';
|
||||
import {
|
||||
KubeAppGitFormValues,
|
||||
RedeployGitStackPayload,
|
||||
UpdateKubeGitStackPayload,
|
||||
} from './types';
|
||||
import { KubeAppGitFormValues } from './types';
|
||||
import { redeployGitAppFormValidationSchema } from './redeployGitAppFormValidationSchema';
|
||||
import { useRedeployKubeGitStackMutation } from './queries/useRedeployKubeGitStackMutation';
|
||||
import { RedeployGitAppInnerForm } from './RedeployGitAppInnerForm';
|
||||
|
||||
type Props = {
|
||||
stackId: StackId;
|
||||
|
@ -55,7 +35,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export function RedeployGitAppForm({ stackId, namespaceName }: Props) {
|
||||
const { data: stack, ...stackQuery } = useStackQuery(stackId);
|
||||
const { data: stack, ...stackQuery } = useStack(stackId);
|
||||
const { trackEvent } = useAnalytics();
|
||||
const initialValues = getInitialValues(stack);
|
||||
const { user } = useCurrentUser();
|
||||
|
@ -63,10 +43,10 @@ export function RedeployGitAppForm({ stackId, namespaceName }: Props) {
|
|||
user.Id
|
||||
);
|
||||
const environmentId = useEnvironmentId();
|
||||
const createGitCredentialMutation = useCreateGitCredentialMutation();
|
||||
const updateKubeGitStackMutation = useUpdateKubeGitStackMutation(
|
||||
stack?.Id || 0,
|
||||
environmentId
|
||||
environmentId,
|
||||
user.Id
|
||||
);
|
||||
// keep a single generated webhook id, so that it doesn't change on every render
|
||||
const generatedWebhookId = useRef(createWebhookId());
|
||||
|
@ -122,40 +102,24 @@ export function RedeployGitAppForm({ stackId, namespaceName }: Props) {
|
|||
) {
|
||||
trackSubmit(values);
|
||||
|
||||
// save the new git credentials if the user has selected to save them
|
||||
if (
|
||||
values.authentication.SaveCredential &&
|
||||
values.authentication.NewCredentialName &&
|
||||
values.authentication.RepositoryPassword &&
|
||||
values.authentication.RepositoryUsername
|
||||
) {
|
||||
createGitCredentialMutation.mutate({
|
||||
name: values.authentication.NewCredentialName,
|
||||
username: values.authentication.RepositoryUsername,
|
||||
password: values.authentication.RepositoryPassword,
|
||||
userId: user.Id,
|
||||
});
|
||||
}
|
||||
|
||||
// update the kubernetes git stack
|
||||
const { authentication } = values;
|
||||
const updateKubeGitStack: UpdateKubeGitStackPayload = {
|
||||
RepositoryAuthentication:
|
||||
!!values.authentication.RepositoryAuthentication,
|
||||
RepositoryReferenceName: values.repositoryReferenceName,
|
||||
AutoUpdate: transformAutoUpdateViewModel(values.autoUpdate, webhookId),
|
||||
TLSSkipVerify: values.tlsSkipVerify,
|
||||
RepositoryGitCredentialID: authentication.RepositoryGitCredentialID,
|
||||
RepositoryPassword: authentication.RepositoryPassword,
|
||||
RepositoryUsername: authentication.RepositoryUsername,
|
||||
...authentication,
|
||||
};
|
||||
await updateKubeGitStackMutation.mutateAsync(updateKubeGitStack, {
|
||||
onSuccess: ({ data: newStack }) => {
|
||||
const newInitialValues = getInitialValues(newStack);
|
||||
notifySuccess('Success', 'Application saved successfully.');
|
||||
resetForm({ values: newInitialValues });
|
||||
},
|
||||
});
|
||||
await updateKubeGitStackMutation.mutateAsync(
|
||||
{ stack: updateKubeGitStack, authentication },
|
||||
{
|
||||
onSuccess: ({ data: newStack }) => {
|
||||
const newInitialValues = getInitialValues(newStack);
|
||||
notifySuccess('Success', 'Application saved successfully.');
|
||||
resetForm({ values: newInitialValues });
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function trackSubmit(values: KubeAppGitFormValues) {
|
||||
|
@ -184,168 +148,6 @@ export function RedeployGitAppForm({ stackId, namespaceName }: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function RedeployGitAppInnerForm({
|
||||
stack,
|
||||
namespaceName,
|
||||
webhookId,
|
||||
}: {
|
||||
stack: Stack;
|
||||
namespaceName: string;
|
||||
webhookId: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const environmentId = useEnvironmentId();
|
||||
const redeployKubeGitStackMutation = useRedeployKubeGitStackMutation(
|
||||
stack.Id,
|
||||
environmentId
|
||||
);
|
||||
const [isRedeployLoading, setIsRedeployLoading] = useState(false);
|
||||
const { trackEvent } = useAnalytics();
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
setFieldValue,
|
||||
handleSubmit,
|
||||
dirty,
|
||||
isValid,
|
||||
isSubmitting,
|
||||
setFieldTouched,
|
||||
} = useFormikContext<KubeAppGitFormValues>();
|
||||
|
||||
return (
|
||||
<Form className="form-horizontal" onSubmit={handleSubmit}>
|
||||
<AutoUpdateFieldset
|
||||
value={values.autoUpdate}
|
||||
onChange={(value: AutoUpdateModel) =>
|
||||
setFieldValue('autoUpdate', value)
|
||||
}
|
||||
baseWebhookUrl={baseStackWebhookUrl()}
|
||||
webhookId={webhookId}
|
||||
errors={errors.autoUpdate}
|
||||
environmentType="KUBERNETES"
|
||||
isForcePullVisible={false}
|
||||
webhooksDocs="https://docs.portainer.io/user/kubernetes/applications/webhooks"
|
||||
/>
|
||||
<TimeWindowDisplay />
|
||||
<FormSection title="Advanced configuration" isFoldable>
|
||||
<RefField
|
||||
value={values.repositoryReferenceName}
|
||||
onChange={(refName) =>
|
||||
setFieldValue('repositoryReferenceName', refName)
|
||||
}
|
||||
model={{
|
||||
...values.authentication,
|
||||
RepositoryURL: values.repositoryURL,
|
||||
}}
|
||||
error={errors.repositoryReferenceName}
|
||||
stackId={stack.Id}
|
||||
isUrlValid
|
||||
/>
|
||||
<AuthFieldset
|
||||
value={values.authentication}
|
||||
isAuthExplanationVisible
|
||||
onChange={(value) =>
|
||||
Object.entries(value).forEach(([key, value]) => {
|
||||
setFieldValue(key, value);
|
||||
// set touched after a delay to revalidate debounced username and access token fields
|
||||
setTimeout(() => setFieldTouched(key, true), 400);
|
||||
})
|
||||
}
|
||||
errors={errors.authentication}
|
||||
/>
|
||||
<SwitchField
|
||||
name="TLSSkipVerify"
|
||||
label="Skip TLS verification"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="Enabling this will allow skipping TLS validation for any self-signed certificate."
|
||||
checked={values.tlsSkipVerify}
|
||||
onChange={onChangeTLSSkipVerify}
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection title="Actions">
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
type="button"
|
||||
onClick={handleRedeploy}
|
||||
className="!ml-0"
|
||||
loadingText="In progress..."
|
||||
isLoading={isRedeployLoading}
|
||||
disabled={dirty}
|
||||
icon={RefreshCw}
|
||||
data-cy="application-redeploy-button"
|
||||
>
|
||||
Pull and update application
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
loadingText="In progress..."
|
||||
isLoading={isSubmitting}
|
||||
disabled={!dirty || !isValid}
|
||||
data-cy="application-redeploy-button"
|
||||
>
|
||||
Save settings
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</FormSection>
|
||||
</Form>
|
||||
);
|
||||
|
||||
async function handleRedeploy() {
|
||||
const confirmed = await confirm({
|
||||
title: 'Are you sure?',
|
||||
message:
|
||||
'Any changes to this application will be overridden by the definition in git and may cause a service interruption. Do you wish to continue?',
|
||||
confirmButton: buildConfirmButton('Update', 'warning'),
|
||||
modalType: ModalType.Warn,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
setIsRedeployLoading(true);
|
||||
|
||||
// track the redeploy event
|
||||
trackEvent('kubernetes-application-edit-git-pull', {
|
||||
category: 'kubernetes',
|
||||
});
|
||||
|
||||
// redeploy the application
|
||||
const redeployPayload: RedeployGitStackPayload = {
|
||||
RepositoryReferenceName: values.repositoryReferenceName,
|
||||
RepositoryAuthentication:
|
||||
!!values.authentication.RepositoryAuthentication,
|
||||
RepositoryGitCredentialID:
|
||||
values.authentication.RepositoryGitCredentialID,
|
||||
RepositoryPassword: values.authentication.RepositoryPassword,
|
||||
RepositoryUsername: values.authentication.RepositoryUsername,
|
||||
Namespace: namespaceName,
|
||||
};
|
||||
await redeployKubeGitStackMutation.mutateAsync(redeployPayload, {
|
||||
onSuccess: () => {
|
||||
notifySuccess('Success', 'Application redeployed successfully.');
|
||||
router.stateService.go('kubernetes.applications.application', {
|
||||
id: stack.Id,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
setIsRedeployLoading(false);
|
||||
}
|
||||
|
||||
async function onChangeTLSSkipVerify(value: boolean) {
|
||||
// If the user is disabling TLS verification, ask for confirmation
|
||||
if (stack.GitConfig?.TLSSkipVerify && !value) {
|
||||
const confirmed = await confirmEnableTLSVerify();
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setFieldValue('tlsSkipVerify', value);
|
||||
}
|
||||
}
|
||||
|
||||
function getInitialValues(stack?: Stack): KubeAppGitFormValues | undefined {
|
||||
if (!stack || !stack.GitConfig) {
|
||||
return undefined;
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
import { Form, useFormikContext } from 'formik';
|
||||
import { useState } from 'react';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { useRouter } from '@uirouter/react';
|
||||
|
||||
import { Stack } from '@/react/common/stacks/types';
|
||||
import { AutoUpdateFieldset } from '@/react/portainer/gitops/AutoUpdateFieldset';
|
||||
import { AutoUpdateModel } from '@/react/portainer/gitops/types';
|
||||
import { baseStackWebhookUrl } from '@/portainer/helpers/webhookHelper';
|
||||
import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
|
||||
import { RefField } from '@/react/portainer/gitops/RefField';
|
||||
import { confirmEnableTLSVerify } from '@/react/portainer/gitops/utils';
|
||||
import { AuthFieldset } from '@/react/portainer/gitops/AuthFieldset';
|
||||
import { useAnalytics } from '@/react/hooks/useAnalytics';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
import { SwitchField } from '@@/form-components/SwitchField';
|
||||
import { LoadingButton } from '@@/buttons';
|
||||
import { buildConfirmButton } from '@@/modals/utils';
|
||||
import { confirm } from '@@/modals/confirm';
|
||||
import { ModalType } from '@@/modals';
|
||||
import { FormActions } from '@@/form-components/FormActions';
|
||||
|
||||
import { KubeAppGitFormValues, RedeployGitStackPayload } from './types';
|
||||
import { useRedeployKubeGitStackMutation } from './queries/useRedeployKubeGitStackMutation';
|
||||
|
||||
type Props = {
|
||||
stack: Stack;
|
||||
namespaceName: string;
|
||||
webhookId: string;
|
||||
};
|
||||
|
||||
export function RedeployGitAppInnerForm({
|
||||
stack,
|
||||
namespaceName,
|
||||
webhookId,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const environmentId = useEnvironmentId();
|
||||
const redeployKubeGitStackMutation = useRedeployKubeGitStackMutation(
|
||||
stack.Id,
|
||||
environmentId
|
||||
);
|
||||
const [isRedeployLoading, setIsRedeployLoading] = useState(false);
|
||||
const { trackEvent } = useAnalytics();
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
setFieldValue,
|
||||
handleSubmit,
|
||||
dirty,
|
||||
isValid,
|
||||
isSubmitting,
|
||||
setFieldTouched,
|
||||
} = useFormikContext<KubeAppGitFormValues>();
|
||||
|
||||
return (
|
||||
<Form className="form-horizontal" onSubmit={handleSubmit}>
|
||||
<AutoUpdateFieldset
|
||||
value={values.autoUpdate}
|
||||
onChange={(value: AutoUpdateModel) =>
|
||||
setFieldValue('autoUpdate', value)
|
||||
}
|
||||
baseWebhookUrl={baseStackWebhookUrl()}
|
||||
webhookId={webhookId}
|
||||
errors={errors.autoUpdate}
|
||||
environmentType="KUBERNETES"
|
||||
isForcePullVisible={false}
|
||||
webhooksDocs="https://docs.portainer.io/user/kubernetes/applications/webhooks"
|
||||
/>
|
||||
<TimeWindowDisplay />
|
||||
<FormSection title="Advanced configuration" isFoldable>
|
||||
<RefField
|
||||
value={values.repositoryReferenceName}
|
||||
onChange={(refName) =>
|
||||
setFieldValue('repositoryReferenceName', refName)
|
||||
}
|
||||
model={{
|
||||
...values.authentication,
|
||||
RepositoryURL: values.repositoryURL,
|
||||
}}
|
||||
error={errors.repositoryReferenceName}
|
||||
stackId={stack.Id}
|
||||
isUrlValid
|
||||
/>
|
||||
<AuthFieldset
|
||||
value={values.authentication}
|
||||
isAuthExplanationVisible
|
||||
onChange={(value) =>
|
||||
Object.entries(value).forEach(([key, value]) => {
|
||||
setFieldValue(key, value);
|
||||
// set touched after a delay to revalidate debounced username and access token fields
|
||||
setTimeout(() => setFieldTouched(key, true), 400);
|
||||
})
|
||||
}
|
||||
errors={errors.authentication}
|
||||
/>
|
||||
<SwitchField
|
||||
name="TLSSkipVerify"
|
||||
label="Skip TLS verification"
|
||||
labelClass="col-sm-3 col-lg-2"
|
||||
tooltip="Enabling this will allow skipping TLS validation for any self-signed certificate."
|
||||
checked={values.tlsSkipVerify}
|
||||
onChange={onChangeTLSSkipVerify}
|
||||
/>
|
||||
</FormSection>
|
||||
<FormActions
|
||||
submitLabel="Save settings"
|
||||
loadingText="In progress..."
|
||||
isLoading={isSubmitting}
|
||||
isValid={dirty && isValid}
|
||||
data-cy="application-git-save-button"
|
||||
>
|
||||
<LoadingButton
|
||||
type="button"
|
||||
color="secondary"
|
||||
onClick={handleRedeploy}
|
||||
loadingText="In progress..."
|
||||
isLoading={isRedeployLoading}
|
||||
disabled={dirty}
|
||||
icon={RefreshCw}
|
||||
data-cy="application-redeploy-button"
|
||||
>
|
||||
Pull and update application
|
||||
</LoadingButton>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
|
||||
async function handleRedeploy() {
|
||||
const confirmed = await confirm({
|
||||
title: 'Are you sure?',
|
||||
message:
|
||||
'Any changes to this application will be overridden by the definition in git and may cause a service interruption. Do you wish to continue?',
|
||||
confirmButton: buildConfirmButton('Update', 'warning'),
|
||||
modalType: ModalType.Warn,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
setIsRedeployLoading(true);
|
||||
|
||||
// track the redeploy event
|
||||
trackEvent('kubernetes-application-edit-git-pull', {
|
||||
category: 'kubernetes',
|
||||
});
|
||||
|
||||
// redeploy the application
|
||||
const redeployPayload: RedeployGitStackPayload = {
|
||||
RepositoryReferenceName: values.repositoryReferenceName,
|
||||
RepositoryAuthentication:
|
||||
!!values.authentication.RepositoryAuthentication,
|
||||
RepositoryGitCredentialID:
|
||||
values.authentication.RepositoryGitCredentialID,
|
||||
RepositoryPassword: values.authentication.RepositoryPassword,
|
||||
RepositoryUsername: values.authentication.RepositoryUsername,
|
||||
Namespace: namespaceName,
|
||||
};
|
||||
await redeployKubeGitStackMutation.mutateAsync(redeployPayload, {
|
||||
onSuccess: () => {
|
||||
notifySuccess('Success', 'Application redeployed successfully.');
|
||||
router.stateService.go('kubernetes.applications.application', {
|
||||
id: stack.Id,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
setIsRedeployLoading(false);
|
||||
}
|
||||
|
||||
async function onChangeTLSSkipVerify(value: boolean) {
|
||||
// If the user is disabling TLS verification, ask for confirmation
|
||||
if (stack.GitConfig?.TLSSkipVerify && !value) {
|
||||
const confirmed = await confirmEnableTLSVerify();
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setFieldValue('tlsSkipVerify', value);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import { useMutation } from 'react-query';
|
||||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import {
|
||||
withError,
|
||||
withInvalidate,
|
||||
queryClient,
|
||||
} from '@/react-tools/react-query';
|
||||
import { Stack, StackId } from '@/react/common/stacks/types';
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { stacksQueryKeys } from '@/react/common/stacks/queries/query-keys';
|
||||
import { buildStackUrl } from '@/react/common/stacks/queries/buildUrl';
|
||||
|
||||
import { UpdateKubeGitStackPayload } from '../types';
|
||||
|
||||
// update a stack from a git repository
|
||||
export function useUpdateKubeGitStackMutation(
|
||||
stackId: StackId,
|
||||
environmentId: EnvironmentId
|
||||
) {
|
||||
return useMutation(
|
||||
(stack: UpdateKubeGitStackPayload) =>
|
||||
updateGitStack({ stack, stackId, environmentId }),
|
||||
{
|
||||
...withError('Unable to update application.'),
|
||||
...withInvalidate(queryClient, [stacksQueryKeys.stackFile(stackId)]),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function updateGitStack({
|
||||
stackId,
|
||||
stack,
|
||||
environmentId,
|
||||
}: {
|
||||
stackId: StackId;
|
||||
stack: UpdateKubeGitStackPayload;
|
||||
environmentId: EnvironmentId;
|
||||
}) {
|
||||
try {
|
||||
return await axios.post<Stack>(buildStackUrl(stackId, 'git'), stack, {
|
||||
params: { endpointId: environmentId },
|
||||
});
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
AutoUpdateModel,
|
||||
AutoUpdateResponse,
|
||||
GitAuthModel,
|
||||
GitCredentialsModel,
|
||||
} from '@/react/portainer/gitops/types';
|
||||
|
@ -13,12 +12,6 @@ export interface KubeAppGitFormValues {
|
|||
autoUpdate: AutoUpdateModel;
|
||||
}
|
||||
|
||||
export interface UpdateKubeGitStackPayload extends GitCredentialsModel {
|
||||
AutoUpdate: AutoUpdateResponse | null;
|
||||
RepositoryReferenceName: string;
|
||||
TLSSkipVerify: boolean;
|
||||
}
|
||||
|
||||
export interface RedeployGitStackPayload extends GitCredentialsModel {
|
||||
RepositoryReferenceName: string;
|
||||
Namespace: string;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useQueryClient, useMutation } from 'react-query';
|
|||
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
|
||||
import { GitAuthModel, GitFormModel } from '@/react/portainer/gitops/types';
|
||||
import { GitAuthModel } from '@/react/portainer/gitops/types';
|
||||
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||
import { UserId } from '@/portainer/users/types';
|
||||
|
||||
|
@ -84,8 +84,8 @@ export function useSaveCredentialsIfRequired() {
|
|||
|
||||
export async function saveGitCredentialsIfNeeded(
|
||||
userId: UserId,
|
||||
gitModel: GitFormModel
|
||||
) {
|
||||
gitModel: GitAuthModel
|
||||
): Promise<GitAuthModel> {
|
||||
let credentialsId = gitModel.RepositoryGitCredentialID;
|
||||
let username = gitModel.RepositoryUsername;
|
||||
let password = gitModel.RepositoryPassword;
|
||||
|
|
|
@ -84,7 +84,8 @@ async function createTemplateAndGitCredential(
|
|||
userId: UserId,
|
||||
{ Git: gitModel, ...values }: CreateTemplatePayload
|
||||
) {
|
||||
const newGitModel = await saveGitCredentialsIfNeeded(userId, gitModel);
|
||||
const newGitAuthModel = await saveGitCredentialsIfNeeded(userId, gitModel);
|
||||
const newGitModel = { ...gitModel, ...newGitAuthModel };
|
||||
|
||||
return createTemplateFromGit({
|
||||
...values,
|
||||
|
|
Loading…
Reference in New Issue