argo-cd

Форк
0
465 строк · 27.4 Кб
1
import {DataLoader, DropDown, DropDownMenu, MenuItem, Tooltip} from 'argo-ui';
2
import * as PropTypes from 'prop-types';
3
import * as React from 'react';
4
import Moment from 'react-moment';
5

6
import {AppContext} from '../../../shared/context';
7
import {EmptyState} from '../../../shared/components';
8
import {Application, ApplicationTree, HostResourceInfo, InfoItem, Node, Pod, ResourceName, ResourceNode, ResourceStatus} from '../../../shared/models';
9
import {PodViewPreferences, services, ViewPreferences} from '../../../shared/services';
10

11
import {ResourceTreeNode} from '../application-resource-tree/application-resource-tree';
12
import {ResourceIcon} from '../resource-icon';
13
import {ResourceLabel} from '../resource-label';
14
import {ComparisonStatusIcon, isYoungerThanXMinutes, HealthStatusIcon, nodeKey, PodHealthIcon, deletePodAction} from '../utils';
15

16
import './pod-view.scss';
17
import {PodTooltip} from './pod-tooltip';
18

19
interface PodViewProps {
20
    tree: ApplicationTree;
21
    onItemClick: (fullName: string) => void;
22
    app: Application;
23
    nodeMenu?: (node: ResourceNode) => React.ReactNode;
24
    quickStarts?: (node: ResourceNode) => React.ReactNode;
25
}
26

27
export type PodGroupType = 'topLevelResource' | 'parentResource' | 'node';
28

29
export interface PodGroup extends Partial<ResourceNode> {
30
    type: PodGroupType;
31
    pods: Pod[];
32
    info?: InfoItem[];
33
    hostResourcesInfo?: HostResourceInfo[];
34
    resourceStatus?: Partial<ResourceStatus>;
35
    renderMenu?: () => React.ReactNode;
36
    renderQuickStarts?: () => React.ReactNode;
37
    fullName?: string;
38
}
39

40
export class PodView extends React.Component<PodViewProps> {
41
    private get appContext(): AppContext {
42
        return this.context as AppContext;
43
    }
44

45
    public static contextTypes = {
46
        apis: PropTypes.object
47
    };
48

49
    public render() {
50
        return (
51
            <DataLoader load={() => services.viewPreferences.getPreferences()}>
52
                {prefs => {
53
                    const podPrefs = prefs.appDetails.podView || ({} as PodViewPreferences);
54
                    const groups = this.processTree(podPrefs.sortMode, this.props.tree.hosts || []) || [];
55

56
                    return (
57
                        <React.Fragment>
58
                            <div className='pod-view__settings'>
59
                                <div className='pod-view__settings__section'>
60
                                    GROUP BY:&nbsp;
61
                                    <DropDownMenu
62
                                        anchor={() => (
63
                                            <button className='argo-button argo-button--base-o'>
64
                                                {labelForSortMode[podPrefs.sortMode]}&nbsp;&nbsp;
65
                                                <i className='fa fa-chevron-circle-down' />
66
                                            </button>
67
                                        )}
68
                                        items={this.menuItemsFor(['node', 'parentResource', 'topLevelResource'], prefs)}
69
                                    />
70
                                </div>
71
                                {podPrefs.sortMode === 'node' && (
72
                                    <div className='pod-view__settings__section'>
73
                                        <button
74
                                            className={`argo-button argo-button--base${podPrefs.hideUnschedulable ? '-o' : ''}`}
75
                                            style={{border: 'none', width: '170px'}}
76
                                            onClick={() =>
77
                                                services.viewPreferences.updatePreferences({
78
                                                    appDetails: {...prefs.appDetails, podView: {...podPrefs, hideUnschedulable: !podPrefs.hideUnschedulable}}
79
                                                })
80
                                            }>
81
                                            <i className={`fa fa-${podPrefs.hideUnschedulable ? 'eye-slash' : 'eye'}`} style={{width: '15px', marginRight: '5px'}} />
82
                                            UNSCHEDULABLE
83
                                        </button>
84
                                    </div>
85
                                )}
86
                            </div>
87
                            {groups.length > 0 ? (
88
                                <div className='pod-view__nodes-container'>
89
                                    {groups.map(group => {
90
                                        if (group.type === 'node' && group.name === 'Unschedulable' && podPrefs.hideUnschedulable) {
91
                                            return <React.Fragment />;
92
                                        }
93
                                        return (
94
                                            <div className={`pod-view__node white-box ${group.kind === 'node' && 'pod-view__node--large'}`} key={group.fullName || group.name}>
95
                                                <div
96
                                                    className='pod-view__node__container--header'
97
                                                    onClick={() => this.props.onItemClick(group.fullName)}
98
                                                    style={group.kind === 'node' ? {} : {cursor: 'pointer'}}>
99
                                                    <div style={{display: 'flex', alignItems: 'center'}}>
100
                                                        <div style={{marginRight: '10px'}}>
101
                                                            <ResourceIcon kind={group.kind || 'Unknown'} />
102
                                                            <br />
103
                                                            {<div style={{textAlign: 'center'}}>{ResourceLabel({kind: group.kind})}</div>}
104
                                                        </div>
105
                                                        <div style={{lineHeight: '15px'}}>
106
                                                            <b style={{wordWrap: 'break-word'}}>{group.name || 'Unknown'}</b>
107
                                                            {group.resourceStatus && (
108
                                                                <div>
109
                                                                    {group.resourceStatus.health && <HealthStatusIcon state={group.resourceStatus.health} />}
110
                                                                    &nbsp;
111
                                                                    {group.resourceStatus.status && (
112
                                                                        <ComparisonStatusIcon status={group.resourceStatus.status} resource={group.resourceStatus} />
113
                                                                    )}
114
                                                                </div>
115
                                                            )}
116
                                                        </div>
117
                                                        <div style={{marginLeft: 'auto'}}>
118
                                                            {group.renderMenu && (
119
                                                                <DropDown
120
                                                                    isMenu={true}
121
                                                                    anchor={() => (
122
                                                                        <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
123
                                                                            <i className='fa fa-ellipsis-v' />
124
                                                                        </button>
125
                                                                    )}>
126
                                                                    {() => group.renderMenu()}
127
                                                                </DropDown>
128
                                                            )}
129
                                                        </div>
130
                                                    </div>
131
                                                    {group.type === 'node' ? (
132
                                                        <div className='pod-view__node__info--large'>
133
                                                            {(group.info || []).map(item => (
134
                                                                <div key={item.name}>
135
                                                                    {item.name}: <div>{item.value}</div>
136
                                                                </div>
137
                                                            ))}
138
                                                        </div>
139
                                                    ) : (
140
                                                        <div className='pod-view__node__info'>
141
                                                            {group.createdAt ? (
142
                                                                <div>
143
                                                                    <Moment fromNow={true} ago={true}>
144
                                                                        {group.createdAt}
145
                                                                    </Moment>
146
                                                                </div>
147
                                                            ) : null}
148
                                                            {group.info?.map(infoItem => (
149
                                                                <div key={infoItem.name}>{infoItem.value}</div>
150
                                                            ))}
151
                                                        </div>
152
                                                    )}
153
                                                </div>
154
                                                <div className='pod-view__node__container'>
155
                                                    {(group.hostResourcesInfo || []).length > 0 && (
156
                                                        <div className='pod-view__node__container pod-view__node__container--stats'>
157
                                                            {group.hostResourcesInfo.map(info => renderStats(info))}
158
                                                        </div>
159
                                                    )}
160
                                                    <div className='pod-view__node__pod-container pod-view__node__container'>
161
                                                        <div className='pod-view__node__pod-container__pods'>
162
                                                            {group.pods.map(pod => (
163
                                                                <DropDownMenu
164
                                                                    key={pod.uid}
165
                                                                    anchor={() => (
166
                                                                        <Tooltip
167
                                                                            content={<PodTooltip pod={pod} />}
168
                                                                            popperOptions={{
169
                                                                                modifiers: {
170
                                                                                    preventOverflow: {
171
                                                                                        enabled: true
172
                                                                                    },
173
                                                                                    hide: {
174
                                                                                        enabled: false
175
                                                                                    },
176
                                                                                    flip: {
177
                                                                                        enabled: false
178
                                                                                    }
179
                                                                                }
180
                                                                            }}
181
                                                                            key={pod.metadata.name}>
182
                                                                            <div style={{position: 'relative'}}>
183
                                                                                {isYoungerThanXMinutes(pod, 30) && (
184
                                                                                    <i className='fas fa-circle pod-view__node__pod pod-view__node__pod__new-pod-icon' />
185
                                                                                )}
186
                                                                                <div className={`pod-view__node__pod pod-view__node__pod--${pod.health.toLowerCase()}`}>
187
                                                                                    <PodHealthIcon state={{status: pod.health, message: ''}} />
188
                                                                                </div>
189
                                                                            </div>
190
                                                                        </Tooltip>
191
                                                                    )}
192
                                                                    items={[
193
                                                                        {
194
                                                                            title: (
195
                                                                                <React.Fragment>
196
                                                                                    <i className='fa fa-info-circle' /> Info
197
                                                                                </React.Fragment>
198
                                                                            ),
199
                                                                            action: () => this.props.onItemClick(pod.fullName)
200
                                                                        },
201
                                                                        {
202
                                                                            title: (
203
                                                                                <React.Fragment>
204
                                                                                    <i className='fa fa-align-left' /> Logs
205
                                                                                </React.Fragment>
206
                                                                            ),
207
                                                                            action: () => {
208
                                                                                this.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'logs'}, {replace: true});
209
                                                                            }
210
                                                                        },
211
                                                                        {
212
                                                                            title: (
213
                                                                                <React.Fragment>
214
                                                                                    <i className='fa fa-terminal' /> Exec
215
                                                                                </React.Fragment>
216
                                                                            ),
217
                                                                            action: () => {
218
                                                                                this.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'exec'}, {replace: true});
219
                                                                            }
220
                                                                        },
221
                                                                        {
222
                                                                            title: (
223
                                                                                <React.Fragment>
224
                                                                                    <i className='fa fa-times-circle' /> Delete
225
                                                                                </React.Fragment>
226
                                                                            ),
227
                                                                            action: () => {
228
                                                                                deletePodAction(
229
                                                                                    pod,
230
                                                                                    this.appContext,
231
                                                                                    this.props.app.metadata.name,
232
                                                                                    this.props.app.metadata.namespace
233
                                                                                );
234
                                                                            }
235
                                                                        }
236
                                                                    ]}
237
                                                                />
238
                                                            ))}
239
                                                        </div>
240
                                                        <div className='pod-view__node__label'>PODS</div>
241
                                                        {(podPrefs.sortMode === 'parentResource' || podPrefs.sortMode === 'topLevelResource') && (
242
                                                            <div key={group.uid}>{group.renderQuickStarts()}</div>
243
                                                        )}
244
                                                    </div>
245
                                                </div>
246
                                            </div>
247
                                        );
248
                                    })}
249
                                </div>
250
                            ) : (
251
                                <EmptyState icon=' fa fa-th'>
252
                                    <h4>Your application has no pod groups</h4>
253
                                    <h5>Try switching to tree or list view</h5>
254
                                </EmptyState>
255
                            )}
256
                        </React.Fragment>
257
                    );
258
                }}
259
            </DataLoader>
260
        );
261
    }
262

263
    private menuItemsFor(modes: PodGroupType[], prefs: ViewPreferences): MenuItem[] {
264
        const podPrefs = prefs.appDetails.podView || ({} as PodViewPreferences);
265
        return modes.map(mode => ({
266
            title: (
267
                <React.Fragment>
268
                    {podPrefs.sortMode === mode && <i className='fa fa-check' />} {labelForSortMode[mode]}{' '}
269
                </React.Fragment>
270
            ),
271
            action: () => {
272
                this.appContext.apis.navigation.goto('.', {podSortMode: mode});
273
                services.viewPreferences.updatePreferences({appDetails: {...prefs.appDetails, podView: {...podPrefs, sortMode: mode}}});
274
            }
275
        }));
276
    }
277

278
    private processTree(sortMode: PodGroupType, initNodes: Node[]): PodGroup[] {
279
        const tree = this.props.tree;
280
        if (!tree) {
281
            return [];
282
        }
283
        const groupRefs: {[key: string]: PodGroup} = {};
284
        const parentsFor: {[key: string]: PodGroup[]} = {};
285

286
        if (sortMode === 'node' && initNodes) {
287
            initNodes.forEach(infraNode => {
288
                const nodeName = infraNode.name;
289
                groupRefs[nodeName] = {
290
                    ...infraNode,
291
                    type: 'node',
292
                    kind: 'node',
293
                    name: nodeName,
294
                    pods: [],
295
                    info: [
296
                        {name: 'Kernel Version', value: infraNode.systemInfo.kernelVersion},
297
                        {name: 'OS/Arch', value: `${infraNode.systemInfo.operatingSystem}/${infraNode.systemInfo.architecture}`}
298
                    ],
299
                    hostResourcesInfo: infraNode.resourcesInfo
300
                };
301
            });
302
        }
303

304
        const statusByKey = new Map<string, ResourceStatus>();
305
        this.props.app.status?.resources?.forEach(res => statusByKey.set(nodeKey(res), res));
306

307
        (tree.nodes || []).forEach((rnode: ResourceTreeNode) => {
308
            // make sure each node has not null/undefined parentRefs field
309
            rnode.parentRefs = rnode.parentRefs || [];
310

311
            if (sortMode !== 'node') {
312
                parentsFor[rnode.uid] = rnode.parentRefs as PodGroup[];
313
                const fullName = nodeKey(rnode);
314
                const status = statusByKey.get(fullName);
315

316
                if ((rnode.parentRefs || []).length === 0) {
317
                    rnode.root = rnode;
318
                }
319
                groupRefs[rnode.uid] = {
320
                    pods: [] as Pod[],
321
                    fullName,
322
                    ...groupRefs[rnode.uid],
323
                    ...rnode,
324
                    info: (rnode.info || []).filter(i => !i.name.includes('Resource.')),
325
                    createdAt: rnode.createdAt,
326
                    resourceStatus: {health: rnode.health, status: status ? status.status : null, requiresPruning: status && status.requiresPruning ? true : false},
327
                    renderMenu: () => this.props.nodeMenu(rnode),
328
                    renderQuickStarts: () => this.props.quickStarts(rnode)
329
                };
330
            }
331
        });
332
        (tree.nodes || []).forEach((rnode: ResourceTreeNode) => {
333
            if (rnode.kind !== 'Pod') {
334
                return;
335
            }
336

337
            const p: Pod = {
338
                ...rnode,
339
                fullName: nodeKey(rnode),
340
                metadata: {name: rnode.name},
341
                spec: {nodeName: 'Unknown'},
342
                health: rnode.health ? rnode.health.status : 'Unknown'
343
            } as Pod;
344

345
            // Get node name for Pod
346
            rnode.info?.forEach(i => {
347
                if (i.name === 'Node') {
348
                    p.spec.nodeName = i.value;
349
                }
350
            });
351

352
            if (sortMode === 'node') {
353
                if (groupRefs[p.spec.nodeName]) {
354
                    const curNode = groupRefs[p.spec.nodeName];
355
                    curNode.pods.push(p);
356
                } else {
357
                    if (groupRefs.Unschedulable) {
358
                        groupRefs.Unschedulable.pods.push(p);
359
                    } else {
360
                        groupRefs.Unschedulable = {
361
                            type: 'node',
362
                            kind: 'node',
363
                            name: 'Unschedulable',
364
                            pods: [p],
365
                            info: [
366
                                {name: 'Kernel Version', value: 'N/A'},
367
                                {name: 'OS/Arch', value: 'N/A'}
368
                            ],
369
                            hostResourcesInfo: []
370
                        };
371
                    }
372
                }
373
            } else if (sortMode === 'parentResource') {
374
                rnode.parentRefs.forEach(parentRef => {
375
                    if (!groupRefs[parentRef.uid]) {
376
                        groupRefs[parentRef.uid] = {
377
                            kind: parentRef.kind,
378
                            type: sortMode,
379
                            name: parentRef.name,
380
                            pods: [p]
381
                        };
382
                    } else {
383
                        groupRefs[parentRef.uid].pods.push(p);
384
                    }
385
                });
386
            } else if (sortMode === 'topLevelResource') {
387
                let cur = rnode.uid;
388
                let parents = parentsFor[rnode.uid];
389
                while ((parents || []).length > 0) {
390
                    cur = parents[0].uid;
391
                    parents = parentsFor[cur];
392
                }
393
                if (groupRefs[cur]) {
394
                    groupRefs[cur].pods.push(p);
395
                }
396
            }
397
        });
398

399
        Object.values(groupRefs).forEach(group => group.pods.sort((first, second) => nodeKey(first).localeCompare(nodeKey(second))));
400

401
        return Object.values(groupRefs)
402
            .sort((a, b) => (a.name > b.name ? 1 : a.name === b.name ? 0 : -1)) // sort by name
403
            .filter(i => (i.pods || []).length > 0); // filter out groups with no pods
404
    }
405
}
406

407
const labelForSortMode = {
408
    node: 'Node',
409
    parentResource: 'Parent Resource',
410
    topLevelResource: 'Top Level Resource'
411
};
412

413
const sizes = ['Bytes', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
414
function formatSize(bytes: number) {
415
    if (!bytes) {
416
        return '0 Bytes';
417
    }
418
    const k = 1024;
419
    const dm = 2;
420
    const i = Math.floor(Math.log(bytes) / Math.log(k));
421
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
422
}
423

424
function formatMetric(name: ResourceName, val: number) {
425
    if (name === ResourceName.ResourceStorage || name === ResourceName.ResourceMemory) {
426
        // divide by 1000 to convert "milli bytes" to bytes
427
        return formatSize(val / 1000);
428
    }
429
    // cpu millicores
430
    return (val || '0') + 'm';
431
}
432

433
function renderStats(info: HostResourceInfo) {
434
    const neighborsHeight = 100 * (info.requestedByNeighbors / info.capacity);
435
    const appHeight = 100 * (info.requestedByApp / info.capacity);
436
    return (
437
        <div className='pod-view__node__pod__stat' key={info.resourceName}>
438
            <Tooltip
439
                key={info.resourceName}
440
                content={
441
                    <React.Fragment>
442
                        <div>{info.resourceName.toUpperCase()}:</div>
443
                        <div className='pod-view__node__pod__stat-tooltip'>
444
                            <div>Requests:</div>
445
                            <div>
446
                                {' '}
447
                                <i className='pod-view__node__pod__stat-icon-app' /> {formatMetric(info.resourceName, info.requestedByApp)} (App)
448
                            </div>
449
                            <div>
450
                                {' '}
451
                                <i className='pod-view__node__pod__stat-icon-neighbors' /> {formatMetric(info.resourceName, info.requestedByNeighbors)} (Neighbors)
452
                            </div>
453
                            <div>Capacity: {formatMetric(info.resourceName, info.capacity)}</div>
454
                        </div>
455
                    </React.Fragment>
456
                }>
457
                <div className='pod-view__node__pod__stat__bar'>
458
                    <div className='pod-view__node__pod__stat__bar--fill pod-view__node__pod__stat__bar--neighbors' style={{height: `${neighborsHeight}%`}} />
459
                    <div className='pod-view__node__pod__stat__bar--fill' style={{bottom: `${neighborsHeight}%`, height: `${appHeight}%`}} />
460
                </div>
461
            </Tooltip>
462
            <div className='pod-view__node__label'>{info.resourceName.slice(0, 3).toUpperCase()}</div>
463
        </div>
464
    );
465
}
466

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

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

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

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