argo-cd

Форк
0
213 строк · 10.4 Кб
1
import {Checkbox, DropDown, Duration, NotificationType, Ticker} from 'argo-ui';
2
import * as moment from 'moment';
3
import * as PropTypes from 'prop-types';
4
import * as React from 'react';
5

6
import {ErrorNotification, Revision, Timestamp} from '../../../shared/components';
7
import {AppContext} from '../../../shared/context';
8
import * as models from '../../../shared/models';
9
import {services} from '../../../shared/services';
10
import * as utils from '../utils';
11

12
import './application-operation-state.scss';
13

14
interface Props {
15
    application: models.Application;
16
    operationState: models.OperationState;
17
}
18

19
const Filter = (props: {filters: string[]; setFilters: (f: string[]) => void; options: string[]; title: string; style?: React.CSSProperties}) => {
20
    const {filters, setFilters, options, title, style} = props;
21
    return (
22
        <DropDown
23
            isMenu={true}
24
            anchor={() => (
25
                <div title='Filter' style={style}>
26
                    <button className='argo-button argo-button--base'>
27
                        {title} <i className='argo-icon-filter' aria-hidden='true' />
28
                    </button>
29
                </div>
30
            )}>
31
            {options.map(f => (
32
                <div key={f} style={{minWidth: '150px', lineHeight: '2em', padding: '5px'}}>
33
                    <Checkbox
34
                        checked={filters.includes(f)}
35
                        onChange={checked => {
36
                            const selectedValues = [...filters];
37
                            const idx = selectedValues.indexOf(f);
38
                            if (idx > -1 && !checked) {
39
                                selectedValues.splice(idx, 1);
40
                            } else {
41
                                selectedValues.push(f);
42
                            }
43
                            setFilters(selectedValues);
44
                        }}
45
                    />
46
                    <label htmlFor={`filter__${f}`}>{f}</label>
47
                </div>
48
            ))}
49
        </DropDown>
50
    );
51
};
52

53
export const ApplicationOperationState: React.StatelessComponent<Props> = ({application, operationState}, ctx: AppContext) => {
54
    const operationAttributes = [
55
        {title: 'OPERATION', value: utils.getOperationType(application)},
56
        {title: 'PHASE', value: operationState.phase},
57
        ...(operationState.message ? [{title: 'MESSAGE', value: operationState.message}] : []),
58
        {title: 'STARTED AT', value: <Timestamp date={operationState.startedAt} />},
59
        {
60
            title: 'DURATION',
61
            value: (
62
                <Ticker>
63
                    {time => <Duration durationMs={((operationState.finishedAt && moment(operationState.finishedAt)) || time).diff(moment(operationState.startedAt)) / 1000} />}
64
                </Ticker>
65
            )
66
        }
67
    ];
68

69
    if (operationState.finishedAt && operationState.phase !== 'Running') {
70
        operationAttributes.push({title: 'FINISHED AT', value: <Timestamp date={operationState.finishedAt} />});
71
    } else if (operationState.phase !== 'Terminating') {
72
        operationAttributes.push({
73
            title: '',
74
            value: (
75
                <button
76
                    className='argo-button argo-button--base'
77
                    onClick={async () => {
78
                        const confirmed = await ctx.apis.popup.confirm('Terminate operation', 'Are you sure you want to terminate operation?');
79
                        if (confirmed) {
80
                            try {
81
                                await services.applications.terminateOperation(application.metadata.name, application.metadata.namespace);
82
                            } catch (e) {
83
                                ctx.apis.notifications.show({
84
                                    content: <ErrorNotification title='Unable to terminate operation' e={e} />,
85
                                    type: NotificationType.Error
86
                                });
87
                            }
88
                        }
89
                    }}>
90
                    Terminate
91
                </button>
92
            )
93
        });
94
    }
95
    if (operationState.syncResult) {
96
        operationAttributes.push({title: 'REVISION', value: <Revision repoUrl={utils.getAppDefaultSource(application).repoURL} revision={operationState.syncResult.revision} />});
97
    }
98
    let initiator = '';
99
    if (operationState.operation.initiatedBy) {
100
        if (operationState.operation.initiatedBy.automated) {
101
            initiator = 'automated sync policy';
102
        } else {
103
            initiator = operationState.operation.initiatedBy.username;
104
        }
105
    }
106
    operationAttributes.push({title: 'INITIATED BY', value: initiator || 'Unknown'});
107

108
    const resultAttributes: {title: string; value: string}[] = [];
109
    const syncResult = operationState.syncResult;
110
    if (operationState.finishedAt) {
111
        if (syncResult) {
112
            (syncResult.resources || []).forEach(res => {
113
                resultAttributes.push({
114
                    title: `${res.namespace}/${res.kind}:${res.name}`,
115
                    value: res.message
116
                });
117
            });
118
        }
119
    }
120
    const [filters, setFilters] = React.useState([]);
121

122
    const Statuses = Object.keys(models.ResultCodes);
123
    const OperationPhases = Object.keys(models.OperationPhases);
124
    // const syncPhases = ['PreSync', 'Sync', 'PostSync', 'SyncFail'];
125
    // const hookPhases = ['Running', 'Terminating', 'Failed', 'Error', 'Succeeded'];
126

127
    let filtered: models.ResourceResult[] = [];
128
    if (syncResult) {
129
        if (syncResult.resources && syncResult.resources.length > 0) {
130
            filtered = syncResult.resources.filter(r => filters.length === 0 || filters.includes(getStatus(r)));
131
        }
132
    }
133
    return (
134
        <div>
135
            <div className='white-box'>
136
                <div className='white-box__details'>
137
                    {operationAttributes.map(attr => (
138
                        <div className='row white-box__details-row' key={attr.title}>
139
                            <div className='columns small-3'>{attr.title}</div>
140
                            <div className='columns small-9'>{attr.value}</div>
141
                        </div>
142
                    ))}
143
                </div>
144
            </div>
145
            {syncResult && syncResult.resources && syncResult.resources.length > 0 && (
146
                <React.Fragment>
147
                    <div style={{display: 'flex'}}>
148
                        <label style={{display: 'block', marginBottom: '1em'}}>RESULT</label>
149
                        <div style={{marginLeft: 'auto'}}>
150
                            <Filter options={Statuses} filters={filters} setFilters={setFilters} title='STATUS' style={{marginRight: '5px'}} />
151
                            <Filter options={OperationPhases} filters={filters} setFilters={setFilters} title='HOOK' />
152
                        </div>
153
                    </div>
154
                    <div className='argo-table-list'>
155
                        <div className='argo-table-list__head'>
156
                            <div className='row'>
157
                                <div className='columns large-1 show-for-large application-operation-state__icons_container_padding'>KIND</div>
158
                                <div className='columns large-2 show-for-large'>NAMESPACE</div>
159
                                <div className='columns large-2 small-2'>NAME</div>
160
                                <div className='columns large-1 small-2'>STATUS</div>
161
                                <div className='columns large-1 show-for-large'>HOOK</div>
162
                                <div className='columns large-4 small-8'>MESSAGE</div>
163
                            </div>
164
                        </div>
165
                        {filtered.length > 0 ? (
166
                            filtered.map((resource, i) => (
167
                                <div className='argo-table-list__row' key={i}>
168
                                    <div className='row'>
169
                                        <div className='columns large-1 show-for-large application-operation-state__icons_container_padding'>
170
                                            <div className='application-operation-state__icons_container'>
171
                                                {resource.hookType && <i title='Resource lifecycle hook' className='fa fa-anchor' />}
172
                                            </div>
173
                                            <span title={getKind(resource)}>{getKind(resource)}</span>
174
                                        </div>
175
                                        <div className='columns large-2 show-for-large' title={resource.namespace}>
176
                                            {resource.namespace}
177
                                        </div>
178
                                        <div className='columns large-2 small-2' title={resource.name}>
179
                                            {resource.name}
180
                                        </div>
181
                                        <div className='columns large-1 small-2' title={getStatus(resource)}>
182
                                            <utils.ResourceResultIcon resource={resource} /> {getStatus(resource)}
183
                                        </div>
184
                                        <div className='columns large-1 show-for-large' title={resource.hookType}>
185
                                            {resource.hookType}
186
                                        </div>
187
                                        <div className='columns large-4 small-8' title={resource.message}>
188
                                            <div className='application-operation-state__message'>{resource.message}</div>
189
                                        </div>
190
                                    </div>
191
                                </div>
192
                            ))
193
                        ) : (
194
                            <div style={{textAlign: 'center', marginTop: '2em', fontSize: '20px'}}>No Sync Results match filter</div>
195
                        )}
196
                    </div>
197
                </React.Fragment>
198
            )}
199
        </div>
200
    );
201
};
202

203
const getKind = (resource: models.ResourceResult): string => {
204
    return (resource.group ? `${resource.group}/${resource.version}` : resource.version) + `/${resource.kind}`;
205
};
206

207
const getStatus = (resource: models.ResourceResult): string => {
208
    return resource.hookType ? resource.hookPhase : resource.status;
209
};
210

211
ApplicationOperationState.contextTypes = {
212
    apis: PropTypes.object
213
};
214

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

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

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

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