argo-cd
212 строк · 11.4 Кб
1import {HelpIcon} from 'argo-ui';
2import * as React from 'react';
3import {ARGO_GRAY6_COLOR, DataLoader} from '../../../shared/components';
4import {Revision} from '../../../shared/components/revision';
5import {Timestamp} from '../../../shared/components/timestamp';
6import * as models from '../../../shared/models';
7import {services} from '../../../shared/services';
8import {ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState} from '../utils';
9import {getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, helpTip} from '../utils';
10import {RevisionMetadataPanel} from './revision-metadata-panel';
11
12import './application-status-panel.scss';
13
14interface Props {
15application: models.Application;
16showDiff?: () => any;
17showOperation?: () => any;
18showConditions?: () => any;
19showExtension?: (id: string) => any;
20showMetadataInfo?: (revision: string) => any;
21}
22
23interface SectionInfo {
24title: string;
25helpContent?: string;
26}
27
28const sectionLabel = (info: SectionInfo) => (
29<label style={{fontSize: '12px', fontWeight: 600, color: ARGO_GRAY6_COLOR}}>
30{info.title}
31{info.helpContent && <HelpIcon title={info.helpContent} />}
32</label>
33);
34
35const sectionHeader = (info: SectionInfo, hasMultipleSources: boolean, onClick?: () => any) => {
36return (
37<div style={{display: 'flex', alignItems: 'center', marginBottom: '0.5em'}}>
38{sectionLabel(info)}
39{onClick && (
40<button className='application-status-panel__more-button' onClick={onClick} disabled={hasMultipleSources}>
41{hasMultipleSources && helpTip('More details are not supported for apps with multiple sources')}
42<i className='fa fa-ellipsis-h' />
43</button>
44)}
45</div>
46);
47};
48
49export const ApplicationStatusPanel = ({application, showDiff, showOperation, showConditions, showExtension, showMetadataInfo}: Props) => {
50const today = new Date();
51
52let daysSinceLastSynchronized = 0;
53const history = application.status.history || [];
54if (history.length > 0) {
55const deployDate = new Date(history[history.length - 1].deployedAt);
56daysSinceLastSynchronized = Math.round(Math.abs((today.getTime() - deployDate.getTime()) / (24 * 60 * 60 * 1000)));
57}
58const cntByCategory = (application.status.conditions || []).reduce(
59(map, next) => map.set(getConditionCategory(next), (map.get(getConditionCategory(next)) || 0) + 1),
60new Map<string, number>()
61);
62const appOperationState = getAppOperationState(application);
63if (application.metadata.deletionTimestamp && !appOperationState) {
64showOperation = null;
65}
66
67const statusExtensions = services.extensions.getStatusPanelExtensions();
68
69const infos = cntByCategory.get('info');
70const warnings = cntByCategory.get('warning');
71const errors = cntByCategory.get('error');
72const source = getAppDefaultSource(application);
73const hasMultipleSources = application.spec.sources && application.spec.sources.length > 0;
74return (
75<div className='application-status-panel row'>
76<div className='application-status-panel__item'>
77<div style={{lineHeight: '19.5px', marginBottom: '0.3em'}}>{sectionLabel({title: 'APP HEALTH', helpContent: 'The health status of your app'})}</div>
78<div className='application-status-panel__item-value'>
79<HealthStatusIcon state={application.status.health} />
80
81{application.status.health.status}
82</div>
83{application.status.health.message && <div className='application-status-panel__item-name'>{application.status.health.message}</div>}
84</div>
85<div className='application-status-panel__item'>
86<React.Fragment>
87{sectionHeader(
88{
89title: 'SYNC STATUS',
90helpContent: 'Whether or not the version of your app is up to date with your repo. You may wish to sync your app if it is out-of-sync.'
91},
92hasMultipleSources,
93() => showMetadataInfo(application.status.sync ? application.status.sync.revision : '')
94)}
95<div className={`application-status-panel__item-value${appOperationState?.phase ? ` application-status-panel__item-value--${appOperationState.phase}` : ''}`}>
96<div>
97{application.status.sync.status === models.SyncStatuses.OutOfSync ? (
98<a onClick={() => showDiff && showDiff()}>
99<ComparisonStatusIcon status={application.status.sync.status} label={true} />
100</a>
101) : (
102<ComparisonStatusIcon status={application.status.sync.status} label={true} />
103)}
104</div>
105<div className='application-status-panel__item-value__revision show-for-large'>{syncStatusMessage(application)}</div>
106</div>
107<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
108{application.spec.syncPolicy?.automated ? 'Auto sync is enabled.' : 'Auto sync is not enabled.'}
109</div>
110{application.status && application.status.sync && application.status.sync.revision && !application.spec.source.chart && (
111<div className='application-status-panel__item-name'>
112<RevisionMetadataPanel
113appName={application.metadata.name}
114appNamespace={application.metadata.namespace}
115type={source.chart && 'helm'}
116revision={application.status.sync.revision}
117/>
118</div>
119)}
120</React.Fragment>
121</div>
122{appOperationState && (
123<div className='application-status-panel__item'>
124<React.Fragment>
125{sectionHeader(
126{
127title: 'LAST SYNC',
128helpContent:
129'Whether or not your last app sync was successful. It has been ' +
130daysSinceLastSynchronized +
131' days since last sync. Click for the status of that sync.'
132},
133hasMultipleSources,
134() => showMetadataInfo(appOperationState.syncResult ? appOperationState.syncResult.revision : '')
135)}
136<div className={`application-status-panel__item-value application-status-panel__item-value--${appOperationState.phase}`}>
137<a onClick={() => showOperation && showOperation()}>
138<OperationState app={application} />{' '}
139</a>
140{appOperationState.syncResult && appOperationState.syncResult.revision && (
141<div className='application-status-panel__item-value__revision show-for-large'>
142to <Revision repoUrl={source.repoURL} revision={appOperationState.syncResult.revision} />
143</div>
144)}
145</div>
146
147<div className='application-status-panel__item-name' style={{marginBottom: '0.5em'}}>
148{appOperationState.phase} <Timestamp date={appOperationState.finishedAt || appOperationState.startedAt} />
149</div>
150{(appOperationState.syncResult && appOperationState.syncResult.revision && (
151<RevisionMetadataPanel
152appName={application.metadata.name}
153appNamespace={application.metadata.namespace}
154type={source.chart && 'helm'}
155revision={appOperationState.syncResult.revision}
156/>
157)) || <div className='application-status-panel__item-name'>{appOperationState.message}</div>}
158</React.Fragment>
159</div>
160)}
161{application.status.conditions && (
162<div className={`application-status-panel__item`}>
163{sectionLabel({title: 'APP CONDITIONS'})}
164<div className='application-status-panel__item-value application-status-panel__conditions' onClick={() => showConditions && showConditions()}>
165{infos && (
166<a className='info'>
167<i className='fa fa-info-circle' /> {infos} Info
168</a>
169)}
170{warnings && (
171<a className='warning'>
172<i className='fa fa-exclamation-triangle' /> {warnings} Warning{warnings !== 1 && 's'}
173</a>
174)}
175{errors && (
176<a className='error'>
177<i className='fa fa-exclamation-circle' /> {errors} Error{errors !== 1 && 's'}
178</a>
179)}
180</div>
181</div>
182)}
183<DataLoader
184noLoaderOnInputChange={true}
185input={application}
186load={async app => {
187return await services.applications.getApplicationSyncWindowState(app.metadata.name, app.metadata.namespace);
188}}>
189{(data: models.ApplicationSyncWindowState) => (
190<React.Fragment>
191{data.assignedWindows && (
192<div className='application-status-panel__item' style={{position: 'relative'}}>
193{sectionLabel({
194title: 'SYNC WINDOWS',
195helpContent:
196'The aggregate state of sync windows for this app. ' +
197'Red: no syncs allowed. ' +
198'Yellow: manual syncs allowed. ' +
199'Green: all syncs allowed'
200})}
201<div className='application-status-panel__item-value' style={{margin: 'auto 0'}}>
202<ApplicationSyncWindowStatusIcon project={application.spec.project} state={data} />
203</div>
204</div>
205)}
206</React.Fragment>
207)}
208</DataLoader>
209{statusExtensions && statusExtensions.map(ext => <ext.component key={ext.title} application={application} openFlyout={() => showExtension && showExtension(ext.id)} />)}
210</div>
211);
212};
213