argo-cd

Форк
0
251 строка · 15.4 Кб
1
import {ErrorNotification, FormField, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
2
import * as React from 'react';
3
import {Form, FormApi, Text} from 'react-form';
4

5
import {ARGO_WARNING_COLOR, CheckboxField, Spinner} from '../../../shared/components';
6
import {Consumer} from '../../../shared/context';
7
import * as models from '../../../shared/models';
8
import {services} from '../../../shared/services';
9
import {ApplicationRetryOptions} from '../application-retry-options/application-retry-options';
10
import {
11
    ApplicationManualSyncFlags,
12
    ApplicationSyncOptions,
13
    FORCE_WARNING,
14
    SyncFlags,
15
    REPLACE_WARNING,
16
    PRUNE_ALL_WARNING
17
} from '../application-sync-options/application-sync-options';
18
import {ComparisonStatusIcon, getAppDefaultSource, nodeKey} from '../utils';
19

20
import './application-sync-panel.scss';
21

22
export const ApplicationSyncPanel = ({application, selectedResource, hide}: {application: models.Application; selectedResource: string; hide: () => any}) => {
23
    const [form, setForm] = React.useState<FormApi>(null);
24
    const isVisible = !!(selectedResource && application);
25
    const appResources = ((application && selectedResource && application.status && application.status.resources) || [])
26
        .sort((first, second) => nodeKey(first).localeCompare(nodeKey(second)))
27
        .filter(item => !item.hook);
28
    const syncResIndex = appResources.findIndex(item => nodeKey(item) === selectedResource);
29
    const syncStrategy = {} as models.SyncStrategy;
30
    const [isPending, setPending] = React.useState(false);
31
    const source = getAppDefaultSource(application);
32

33
    return (
34
        <Consumer>
35
            {ctx => (
36
                <SlidingPanel
37
                    isMiddle={true}
38
                    isShown={isVisible}
39
                    onClose={() => hide()}
40
                    header={
41
                        <div>
42
                            <button
43
                                qe-id='application-sync-panel-button-synchronize'
44
                                className='argo-button argo-button--base'
45
                                disabled={isPending}
46
                                onClick={() => form.submitForm(null)}>
47
                                <Spinner show={isPending} style={{marginRight: '5px'}} />
48
                                Synchronize
49
                            </button>{' '}
50
                            <button onClick={() => hide()} className='argo-button argo-button--base-o'>
51
                                Cancel
52
                            </button>
53
                        </div>
54
                    }>
55
                    {isVisible && (
56
                        <Form
57
                            defaultValues={{
58
                                revision: new URLSearchParams(ctx.history.location.search).get('revision') || source.targetRevision || 'HEAD',
59
                                resources: appResources.map((_, i) => i === syncResIndex || syncResIndex === -1),
60
                                syncOptions: application.spec.syncPolicy ? application.spec.syncPolicy.syncOptions : []
61
                            }}
62
                            validateError={values => ({
63
                                resources: values.resources.every((item: boolean) => !item) && 'Select at least one resource'
64
                            })}
65
                            onSubmit={async (params: any) => {
66
                                setPending(true);
67
                                let selectedResources = appResources.filter((_, i) => params.resources[i]);
68
                                const allResourcesAreSelected = selectedResources.length === appResources.length;
69
                                const syncFlags = {...params.syncFlags} as SyncFlags;
70

71
                                const allRequirePruning = !selectedResources.some(resource => !resource?.requiresPruning);
72
                                if (syncFlags.Prune && allRequirePruning && allResourcesAreSelected) {
73
                                    const confirmed = await ctx.popup.confirm('Prune all resources?', () => (
74
                                        <div>
75
                                            <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} />
76
                                            {PRUNE_ALL_WARNING} Are you sure you want to continue?
77
                                        </div>
78
                                    ));
79
                                    if (!confirmed) {
80
                                        setPending(false);
81
                                        return;
82
                                    }
83
                                }
84
                                if (allResourcesAreSelected) {
85
                                    selectedResources = null;
86
                                }
87
                                const replace = params.syncOptions?.findIndex((opt: string) => opt === 'Replace=true') > -1;
88
                                if (replace) {
89
                                    const confirmed = await ctx.popup.confirm('Synchronize using replace?', () => (
90
                                        <div>
91
                                            <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {REPLACE_WARNING} Are you sure you want to continue?
92
                                        </div>
93
                                    ));
94
                                    if (!confirmed) {
95
                                        setPending(false);
96
                                        return;
97
                                    }
98
                                }
99

100
                                const force = syncFlags.Force || false;
101

102
                                if (syncFlags.ApplyOnly) {
103
                                    syncStrategy.apply = {force};
104
                                } else {
105
                                    syncStrategy.hook = {force};
106
                                }
107
                                if (force) {
108
                                    const confirmed = await ctx.popup.confirm('Synchronize with force?', () => (
109
                                        <div>
110
                                            <i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {FORCE_WARNING} Are you sure you want to continue?
111
                                        </div>
112
                                    ));
113
                                    if (!confirmed) {
114
                                        setPending(false);
115
                                        return;
116
                                    }
117
                                }
118

119
                                try {
120
                                    await services.applications.sync(
121
                                        application.metadata.name,
122
                                        application.metadata.namespace,
123
                                        params.revision,
124
                                        syncFlags.Prune || false,
125
                                        syncFlags.DryRun || false,
126
                                        syncStrategy,
127
                                        selectedResources,
128
                                        params.syncOptions,
129
                                        params.retryStrategy
130
                                    );
131
                                    hide();
132
                                } catch (e) {
133
                                    ctx.notifications.show({
134
                                        content: <ErrorNotification title='Unable to sync' e={e} />,
135
                                        type: NotificationType.Error
136
                                    });
137
                                } finally {
138
                                    setPending(false);
139
                                }
140
                            }}
141
                            getApi={setForm}>
142
                            {formApi => (
143
                                <form role='form' className='width-control' onSubmit={formApi.submitForm}>
144
                                    <h6>
145
                                        Synchronizing application manifests from <a href={source.repoURL}>{source.repoURL}</a>
146
                                    </h6>
147
                                    <div className='argo-form-row'>
148
                                        <FormField formApi={formApi} label='Revision' field='revision' component={Text} />
149
                                    </div>
150

151
                                    <div className='argo-form-row'>
152
                                        <div style={{marginBottom: '1em'}}>
153
                                            <FormField formApi={formApi} field='syncFlags' component={ApplicationManualSyncFlags} />
154
                                        </div>
155
                                        <div style={{marginBottom: '1em'}}>
156
                                            <label>Sync Options</label>
157
                                            <ApplicationSyncOptions
158
                                                options={formApi.values.syncOptions}
159
                                                onChanged={opts => {
160
                                                    formApi.setTouched('syncOptions', true);
161
                                                    formApi.setValue('syncOptions', opts);
162
                                                }}
163
                                                id='application-sync-panel'
164
                                            />
165
                                        </div>
166

167
                                        <ApplicationRetryOptions
168
                                            id='application-sync-panel'
169
                                            formApi={formApi}
170
                                            initValues={application.spec.syncPolicy ? application.spec.syncPolicy.retry : null}
171
                                        />
172

173
                                        <label>Synchronize resources:</label>
174
                                        <div style={{float: 'right'}}>
175
                                            <a
176
                                                onClick={() =>
177
                                                    formApi.setValue(
178
                                                        'resources',
179
                                                        formApi.values.resources.map(() => true)
180
                                                    )
181
                                                }>
182
                                                all
183
                                            </a>{' '}
184
                                            /{' '}
185
                                            <a
186
                                                onClick={() =>
187
                                                    formApi.setValue(
188
                                                        'resources',
189
                                                        application.status.resources
190
                                                            .filter(item => !item.hook)
191
                                                            .map((resource: models.ResourceStatus) => resource.status === models.SyncStatuses.OutOfSync)
192
                                                    )
193
                                                }>
194
                                                out of sync
195
                                            </a>{' '}
196
                                            /{' '}
197
                                            <a
198
                                                onClick={() =>
199
                                                    formApi.setValue(
200
                                                        'resources',
201
                                                        formApi.values.resources.map(() => false)
202
                                                    )
203
                                                }>
204
                                                none
205
                                            </a>
206
                                        </div>
207
                                        <div className='application-details__warning'>
208
                                            {!formApi.values.resources.every((item: boolean) => item) && <div>WARNING: partial synchronization is not recorded in history</div>}
209
                                        </div>
210
                                        <div>
211
                                            {application.status.resources
212
                                                .filter(item => !item.hook)
213
                                                .map((item, i) => {
214
                                                    const resKey = nodeKey(item);
215
                                                    const contentStart = resKey.substr(0, Math.floor(resKey.length / 2));
216
                                                    let contentEnd = resKey.substr(-Math.floor(resKey.length / 2));
217
                                                    // We want the ellipsis to be in the middle of our text, so we use RTL layout to put it there.
218
                                                    // Unfortunately, strong LTR characters get jumbled around, so make sure that the last character isn't strong.
219
                                                    const firstLetter = /[a-z]/i.exec(contentEnd);
220
                                                    if (firstLetter) {
221
                                                        contentEnd = contentEnd.slice(firstLetter.index);
222
                                                    }
223
                                                    const isLongLabel = resKey.length > 68;
224
                                                    return (
225
                                                        <div key={resKey} className='application-sync-panel__resource'>
226
                                                            <CheckboxField id={resKey} field={`resources[${i}]`} />
227
                                                            <Tooltip content={<div style={{wordBreak: 'break-all'}}>{resKey}</div>}>
228
                                                                <div className='container'>
229
                                                                    {isLongLabel ? (
230
                                                                        <label htmlFor={resKey} content-start={contentStart} content-end={contentEnd} />
231
                                                                    ) : (
232
                                                                        <label htmlFor={resKey}>{resKey}</label>
233
                                                                    )}
234
                                                                </div>
235
                                                            </Tooltip>
236
                                                            <ComparisonStatusIcon status={item.status} resource={item} />
237
                                                        </div>
238
                                                    );
239
                                                })}
240
                                            {formApi.errors.resources && <div className='argo-form-row__error-msg'>{formApi.errors.resources}</div>}
241
                                        </div>
242
                                    </div>
243
                                </form>
244
                            )}
245
                        </Form>
246
                    )}
247
                </SlidingPanel>
248
            )}
249
        </Consumer>
250
    );
251
};
252

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.