diff --git a/ui/src/app/app.tsx b/ui/src/app/app.tsx index 5a080e20d451f..74e71dad56f28 100644 --- a/ui/src/app/app.tsx +++ b/ui/src/app/app.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import {Helmet} from 'react-helmet'; import {Redirect, Route, RouteComponentProps, Router, Switch} from 'react-router'; import applications from './applications'; -// import applicationsets from './applicationsets'; import help from './help'; import login from './login'; import settings from './settings'; @@ -32,7 +31,7 @@ type Routes = {[path: string]: {component: React.ComponentType { return ReactDOM.createPortal(, sidebarTarget?.current); }; -export const NodeInfo = (node?: string): { key: string; container: number } => { - const nodeContainer = { key: '', container: 0 }; +export const NodeInfo = (node?: string): {key: string; container: number} => { + const nodeContainer = {key: '', container: 0}; if (node) { const parts = node.split('/'); nodeContainer.key = parts.slice(0, 4).join('/'); @@ -69,10 +77,10 @@ export const NodeInfo = (node?: string): { key: string; container: number } => { export const SelectNode = (fullName: string, containerIndex = 0, tab: string = null, appContext: ContextApis) => { const node = fullName ? `${fullName}/${containerIndex}` : null; - appContext.navigation.goto('.', { node, tab }, { replace: true }); + appContext.navigation.goto('.', {node, tab}, {replace: true}); }; -export class ApplicationDetails extends React.Component, ApplicationDetailsState> { +export class ApplicationDetails extends React.Component, ApplicationDetailsState> { public static contextTypes = { apis: PropTypes.object }; @@ -80,10 +88,10 @@ export class ApplicationDetails extends React.Component(null); private appNamespace: string; - constructor(props: RouteComponentProps<{ appnamespace: string; name: string }>) { + constructor(props: RouteComponentProps<{appnamespace: string; name: string}>) { super(props); const extensions = services.extensions.getAppViewExtensions(); - const extensionsMap: { [key: string]: AppViewExtension } = {}; + const extensionsMap: {[key: string]: AppViewExtension} = {}; extensions.forEach(ext => { extensionsMap[ext.title] = ext; }); @@ -113,11 +121,11 @@ export class ApplicationDetails extends React.Component= 0) { this.state.collapsedNodes.splice(index, 1); const updatedNodes = this.state.collapsedNodes.slice(); - this.setState({ collapsedNodes: updatedNodes }); + this.setState({collapsedNodes: updatedNodes}); } else if (!isExpanded && index < 0) { const updatedNodes = this.state.collapsedNodes.slice(); updatedNodes.push(node); - this.setState({ collapsedNodes: updatedNodes }); + this.setState({collapsedNodes: updatedNodes}); } } @@ -143,20 +151,22 @@ export class ApplicationDetails extends React.Component (usrMsg.appName === appName && usrMsg.msgKey === 'groupNodes' ? { ...usrMsg, display: true } : usrMsg)); + (pref as AppDetailsPreferences).userHelpTipMsgs = (pref as AppDetailsPreferences).userHelpTipMsgs.map(usrMsg => + usrMsg.appName === appName && usrMsg.msgKey === 'groupNodes' ? {...usrMsg, display: true} : usrMsg + ); } - services.viewPreferences.updatePreferences({ appDetails: { ...pref, groupNodes: !pref.groupNodes } }); + services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: !pref.groupNodes}}); } private getPageTitle(view: string) { if (isInvokedFromApps()) { - const { Tree, Pods, Network, List } = AppsDetailsViewKey; + const {Tree, Pods, Network, List} = AppsDetailsViewKey; switch (view) { case Tree: return 'Application Details Tree'; @@ -167,9 +177,8 @@ export class ApplicationDetails extends React.Component !!item); } if (params.get('view') != null) { - pref.view = isApp(application) ? params.get('view') as AppsDetailsViewType : params.get('view') as AppSetsDetailsViewType; + pref.view = isApp(application) ? (params.get('view') as AppsDetailsViewType) : (params.get('view') as AppSetsDetailsViewType); } else { - const appDefaultView = isApp(application) ? (application.metadata && - application.metadata.annotations && - application.metadata.annotations[appModels.AnnotationDefaultView]) as AppsDetailsViewType : (application.metadata && - application.metadata.annotations && - application.metadata.annotations[appModels.AnnotationDefaultView]) as AppSetsDetailsViewType; + const appDefaultView = isApp(application) + ? ((application.metadata && + application.metadata.annotations && + application.metadata.annotations[appModels.AnnotationDefaultView]) as AppsDetailsViewType) + : ((application.metadata && + application.metadata.annotations && + application.metadata.annotations[appModels.AnnotationDefaultView]) as AppSetsDetailsViewType); if (appDefaultView != null) { pref.view = appDefaultView; } @@ -215,26 +226,26 @@ export class ApplicationDetails extends React.Component - {({ application, tree, pref }: { application: appModels.AbstractApplication; tree: appModels.ApplicationTree; pref: AbstractAppDetailsPreferences }) => { + {({application, tree, pref}: {application: appModels.AbstractApplication; tree: appModels.ApplicationTree; pref: AbstractAppDetailsPreferences}) => { tree.nodes = tree.nodes || []; const treeFilter = this.getTreeFilter(pref.resourceFilter); const setFilter = (items: string[]) => { - this.appContext.apis.navigation.goto('.', { resource: items.join(',') }, { replace: true }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, resourceFilter: items } }); + this.appContext.apis.navigation.goto('.', {resource: items.join(',')}, {replace: true}); + services.viewPreferences.updatePreferences({appDetails: {...pref, resourceFilter: items}}); }; const clearFilter = () => setFilter([]); const refreshing = application.metadata.annotations && application.metadata.annotations[appModels.AnnotationRefreshKey]; @@ -246,8 +257,10 @@ export class ApplicationDetails extends React.Component usrMsg.appName === application.metadata.name) : null; + const source = isApp(application) ? getAppDefaultSource(application as models.Application) : null; + const showToolTip = isApp(application) + ? (pref as AppDetailsPreferences)?.userHelpTipMsgs.find(usrMsg => usrMsg.appName === application.metadata.name) + : null; const resourceNodes = (): any[] => { const statusByKey = new Map(); if (isApp(application)) { @@ -255,10 +268,10 @@ export class ApplicationDetails extends React.Component(); tree.nodes - .map(node => ({ ...node, orphaned: false })) - .concat(((pref.orphanedResources && tree.orphanedNodes) || []).map(node => ({ ...node, orphaned: true }))) + .map(node => ({...node, orphaned: false})) + .concat(((pref.orphanedResources && tree.orphanedNodes) || []).map(node => ({...node, orphaned: true}))) .forEach(node => { - const resource: any = { ...node }; + const resource: any = {...node}; resource.uid = node.uid; const status = statusByKey.get(AppUtils.nodeKey(node)); if (status) { @@ -275,7 +288,7 @@ export class ApplicationDetails extends React.Component { - const resNode: ResourceTreeNode = { ...res, root: null, info: null, parentRefs: [], resourceVersion: '', uid: '' }; + const resNode: ResourceTreeNode = {...res, root: null, info: null, parentRefs: [], resourceVersion: '', uid: ''}; resNode.root = resNode; return this.filterTreeNode(resNode, treeFilter); }); @@ -291,14 +304,14 @@ export class ApplicationDetails extends React.Component message.split(/\s/).map(part => urlPattern.test(part) ? ( - + {part}{' '} ) : ( part + ' ' ) ); - const { Tree, Pods, Network, List } = AppsDetailsViewKey; + const {Tree, Pods, Network, List} = AppsDetailsViewKey; const zoomNum = (pref.zoom * 100).toFixed(0); const setZoom = (s: number) => { let targetZoom: number = pref.zoom + s; @@ -307,17 +320,19 @@ export class ApplicationDetails extends React.Component 2.0) { targetZoom = 2.0; } - services.viewPreferences.updatePreferences({ appDetails: { ...pref, zoom: targetZoom } }); + services.viewPreferences.updatePreferences({appDetails: {...pref, zoom: targetZoom}}); }; const setFilterGraph = (filterGraph: any[]) => { - this.setState({ filteredGraph: filterGraph }); + this.setState({filteredGraph: filterGraph}); }; const setShowCompactNodes = (showCompactView: boolean) => { - services.viewPreferences.updatePreferences({ appDetails: { ...pref, groupNodes: showCompactView } }); + services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: showCompactView}}); }; const updateHelpTipState = (usrHelpTip: models.UserMessages) => { if (isApp(application)) { - const existingIndex = (pref as AppDetailsPreferences).userHelpTipMsgs.findIndex(msg => msg.appName === usrHelpTip.appName && msg.msgKey === usrHelpTip.msgKey); + const existingIndex = (pref as AppDetailsPreferences).userHelpTipMsgs.findIndex( + msg => msg.appName === usrHelpTip.appName && msg.msgKey === usrHelpTip.msgKey + ); if (existingIndex !== -1) { (pref as AppDetailsPreferences).userHelpTipMsgs[existingIndex] = usrHelpTip; } else { @@ -326,18 +341,18 @@ export class ApplicationDetails extends React.Component { - this.setState({ truncateNameOnRight: !this.state.truncateNameOnRight }); + this.setState({truncateNameOnRight: !this.state.truncateNameOnRight}); }; const expandAll = () => { - this.setState({ collapsedNodes: [] }); + this.setState({collapsedNodes: []}); }; const collapseAll = () => { const nodes = new Array(); tree.nodes - .map(node => ({ ...node, orphaned: false })) - .concat((tree.orphanedNodes || []).map(node => ({ ...node, orphaned: true }))) + .map(node => ({...node, orphaned: false})) + .concat((tree.orphanedNodes || []).map(node => ({...node, orphaned: true}))) .forEach(node => { - const resourceNode: ResourceTreeNode = { ...node }; + const resourceNode: ResourceTreeNode = {...node}; nodes.push(resourceNode); }); const collapsedNodesList = this.state.collapsedNodes.slice(); @@ -349,7 +364,7 @@ export class ApplicationDetails extends React.Component { @@ -363,7 +378,7 @@ export class ApplicationDetails extends React.Component } + {title: 'Applications', path: '/applications'}, + {title: } ], - actionMenu: { items: this.getApplicationActionMenu(application, true) }, + actionMenu: {items: this.getApplicationActionMenu(application, true)}, tools: (
{ - this.appContext.apis.navigation.goto('.', { view: Tree }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, view: Tree } }); + this.appContext.apis.navigation.goto('.', {view: Tree}); + services.viewPreferences.updatePreferences({appDetails: {...pref, view: Tree}}); }} /> - {isApp(application) && { - this.appContext.apis.navigation.goto('.', { view: Pods }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, view: Pods } }); - }} - />} - {isApp(application) && { - this.appContext.apis.navigation.goto('.', { view: Network }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, view: Network } }); - }} - />} + {isApp(application) && ( + { + this.appContext.apis.navigation.goto('.', {view: Pods}); + services.viewPreferences.updatePreferences({appDetails: {...pref, view: Pods}}); + }} + /> + )} + {isApp(application) && ( + { + this.appContext.apis.navigation.goto('.', {view: Network}); + services.viewPreferences.updatePreferences({appDetails: {...pref, view: Network}}); + }} + /> + )} { - this.appContext.apis.navigation.goto('.', { view: List }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, view: List } }); + this.appContext.apis.navigation.goto('.', {view: List}); + services.viewPreferences.updatePreferences({appDetails: {...pref, view: List}}); }} /> {this.state.extensions && (this.state.extensions || []).map(ext => ( { - this.appContext.apis.navigation.goto('.', { view: ext.title }); - services.viewPreferences.updatePreferences({ appDetails: { ...pref, view: ext.title } }); + this.appContext.apis.navigation.goto('.', {view: ext.title}); + services.viewPreferences.updatePreferences({appDetails: {...pref, view: ext.title}}); }} /> ))} @@ -592,11 +615,11 @@ export class ApplicationDetails extends React.Component )) || ( - -

No resources found

-
Try to change filter criteria
-
- )} + +

No resources found

+
Try to change filter criteria
+
+ )}
)} @@ -606,14 +629,14 @@ export class ApplicationDetails extends React.Component this.setState({ slidingPanelPage: page })} + onPageChange={page => this.setState({slidingPanelPage: page})} preferencesKey='grouped-nodes-details'> {data => ( this.selectNode(fullName)} resources={data} nodeMenu={node => - AppUtils.renderResourceMenu({ ...node, root: node }, application, tree, this.appContext.apis, this.appChanged, () => + AppUtils.renderResourceMenu({...node, root: node}, application, tree, this.appContext.apis, this.appChanged, () => this.getApplicationActionMenu(application, false) ) } @@ -628,7 +651,7 @@ export class ApplicationDetails extends React.Component this.updateApp(app, query)} + updateApp={(app: models.Application, query: {validate?: boolean}) => this.updateApp(app, query)} selectedNode={selectedNode} tab={tab} /> @@ -654,7 +677,7 @@ export class ApplicationDetails extends React.Component this.setConditionsStatusVisible(false)}> {conditions && } - this.setState({ revision: null })}> + this.setState({revision: null})}> {this.state.revision && (source.chart ? ( {(m: ChartDetails) => ( -
+
Revision:
@@ -707,7 +730,7 @@ export class ApplicationDetails extends React.Component {metadata => ( -
+
SHA:
@@ -741,7 +764,7 @@ export class ApplicationDetails extends React.Component
Message:
-
+
{renderCommitMessage(metadata.message)}
@@ -763,84 +786,86 @@ export class ApplicationDetails extends React.Component {prop.actionLabel}; + const fullName = AppUtils.nodeKey({group: 'argoproj.io', kind: app.kind, name: app.metadata.name, namespace: app.metadata.namespace}); + const ActionMenuItem = (prop: {actionLabel: string}) => {prop.actionLabel}; const hasMultipleSources = isApp(app) ? app.spec.sources && app.spec.sources.length > 0 : false; - return isApp(app) ? [ - { - iconClassName: 'fa fa-info-circle', - title: , - action: () => this.selectNode(fullName) - }, - { - iconClassName: 'fa fa-file-medical', - title: , - action: () => this.selectNode(fullName, 0, 'diff'), - disabled: (app as models.Application).status.sync.status === appModels.SyncStatuses.Synced - }, - { - iconClassName: 'fa fa-sync', - title: , - action: () => AppUtils.showDeploy('all', null, this.appContext.apis) - }, - { - iconClassName: 'fa fa-info-circle', - title: , - action: () => this.setOperationStatusVisible(true), - disabled: !(app as models.Application).status.operationState - }, - { - iconClassName: 'fa fa-history', - title: hasMultipleSources ? ( - - - {helpTip('Rollback is not supported for apps with multiple sources')} - - ) : ( - - ), - action: () => { - this.setRollbackPanelVisible(0); - }, - disabled: !(app as models.Application).status.operationState || hasMultipleSources - }, - { - iconClassName: 'fa fa-times-circle', - title: , - action: () => this.deleteApplication() - }, - { - iconClassName: classNames('fa fa-redo', { 'status-icon--spin': !!refreshing }), - title: ( - - {' '} - !refreshing && services.applications.get(app.metadata.name, app.metadata.namespace, 'hard') - } - ]} - anchor={() => } - /> - - ), - disabled: !!refreshing, - action: () => { - if (!refreshing) { - services.applications.get(app.metadata.name, app.metadata.namespace, 'normal'); - AppUtils.setAppRefreshing(app); - this.appChanged.next(app); - } - } - } - ] : [ - { - iconClassName: 'fa fa-info-circle', - title: , - action: () => this.selectNode(fullName) - } - ]; + return isApp(app) + ? [ + { + iconClassName: 'fa fa-info-circle', + title: , + action: () => this.selectNode(fullName) + }, + { + iconClassName: 'fa fa-file-medical', + title: , + action: () => this.selectNode(fullName, 0, 'diff'), + disabled: (app as models.Application).status.sync.status === appModels.SyncStatuses.Synced + }, + { + iconClassName: 'fa fa-sync', + title: , + action: () => AppUtils.showDeploy('all', null, this.appContext.apis) + }, + { + iconClassName: 'fa fa-info-circle', + title: , + action: () => this.setOperationStatusVisible(true), + disabled: !(app as models.Application).status.operationState + }, + { + iconClassName: 'fa fa-history', + title: hasMultipleSources ? ( + + + {helpTip('Rollback is not supported for apps with multiple sources')} + + ) : ( + + ), + action: () => { + this.setRollbackPanelVisible(0); + }, + disabled: !(app as models.Application).status.operationState || hasMultipleSources + }, + { + iconClassName: 'fa fa-times-circle', + title: , + action: () => this.deleteApplication() + }, + { + iconClassName: classNames('fa fa-redo', {'status-icon--spin': !!refreshing}), + title: ( + + {' '} + !refreshing && services.applications.get(app.metadata.name, app.metadata.namespace, 'hard') + } + ]} + anchor={() => } + /> + + ), + disabled: !!refreshing, + action: () => { + if (!refreshing) { + services.applications.get(app.metadata.name, app.metadata.namespace, 'normal'); + AppUtils.setAppRefreshing(app); + this.appChanged.next(app); + } + } + } + ] + : [ + { + iconClassName: 'fa fa-info-circle', + title: , + action: () => this.selectNode(fullName) + } + ]; } private filterTreeNode(node: ResourceTreeNode, filterInput: FilterInput): boolean { @@ -884,12 +909,12 @@ export class ApplicationDetails extends React.Component { + private loadAppInfo(name: string, appNamespace: string): Observable<{application: appModels.AbstractApplication; tree: appModels.ApplicationTree}> { return from(services.applications.get(name, appNamespace)) .pipe( mergeMap(app => { const fallbackTree = { - nodes: isApp(app) ? (app as models.Application).status.resources.map(res => ({ ...res, parentRefs: [], info: [], resourceVersion: '', uid: '' })) : [], + nodes: isApp(app) ? (app as models.Application).status.resources.map(res => ({...res, parentRefs: [], info: [], resourceVersion: '', uid: ''})) : [], orphanedNodes: [], hosts: [] } as appModels.ApplicationTree; @@ -899,7 +924,7 @@ export class ApplicationDetails extends React.Component !!item)), AppUtils.handlePageVisibility(() => services.applications - .watch({ name, appNamespace }) + .watch({name, appNamespace}) .pipe( map(watchEvent => { if (watchEvent.type === 'DELETED') { @@ -926,15 +951,15 @@ export class ApplicationDetails extends React.Component !!application && !!tree)) - .pipe(map(([application, tree]) => ({ application, tree }))); + .pipe(map(([application, tree]) => ({application, tree}))); } private onAppDeleted() { - this.appContext.apis.notifications.show({ type: NotificationType.Success, content: `Application '${this.props.match.params.name}' was deleted` }); + this.appContext.apis.notifications.show({type: NotificationType.Success, content: `Application '${this.props.match.params.name}' was deleted`}); this.appContext.apis.navigation.goto('/applications'); } - private async updateApp(app: appModels.Application, query: { validate?: boolean }) { + private async updateApp(app: appModels.Application, query: {validate?: boolean}) { const latestApp = await services.applications.get(app.metadata.name, app.metadata.namespace); latestApp.metadata.labels = app.metadata.labels; latestApp.metadata.annotations = app.metadata.annotations; @@ -946,7 +971,7 @@ export class ApplicationDetails extends React.Component(); tree.nodes.concat(tree.orphanedNodes || []).forEach(node => nodeByKey.set(AppUtils.nodeKey(node), node)); - nodeByKey.set(AppUtils.nodeKey({ group: 'argoproj.io', kind: application.kind, name: application.metadata.name, namespace: application.metadata.namespace }), application); + nodeByKey.set(AppUtils.nodeKey({group: 'argoproj.io', kind: application.kind, name: application.metadata.name, namespace: application.metadata.namespace}), application); return nodeByKey; } @@ -976,19 +1001,19 @@ export class ApplicationDetails extends React.Component { - const { extension, application, tree } = props; +const ExtensionView = (props: {extension: AppViewExtension; application: models.Application; tree: models.ApplicationTree}) => { + const {extension, application, tree} = props; return ; }; diff --git a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx index af98f9c646362..365c0535ef8ce 100644 --- a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx +++ b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx @@ -1,13 +1,13 @@ -import { HelpIcon } from 'argo-ui'; +import {HelpIcon} from 'argo-ui'; import * as React from 'react'; -import { ARGO_GRAY6_COLOR, DataLoader } from '../../../shared/components'; -import { Revision } from '../../../shared/components/revision'; -import { Timestamp } from '../../../shared/components/timestamp'; +import {ARGO_GRAY6_COLOR, DataLoader} from '../../../shared/components'; +import {Revision} from '../../../shared/components/revision'; +import {Timestamp} from '../../../shared/components/timestamp'; import * as models from '../../../shared/models'; -import { services } from '../../../shared/services'; -import { AppSetHealthStatusIcon, ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState, getAppSetHealthStatus, isApp } from '../utils'; -import { getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, helpTip } from '../utils'; -import { RevisionMetadataPanel } from './revision-metadata-panel'; +import {services} from '../../../shared/services'; +import {AppSetHealthStatusIcon, ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState, getAppSetHealthStatus, isApp} from '../utils'; +import {getConditionCategory, HealthStatusIcon, OperationState, syncStatusMessage, helpTip} from '../utils'; +import {RevisionMetadataPanel} from './revision-metadata-panel'; import './application-status-panel.scss'; @@ -25,7 +25,7 @@ interface SectionInfo { } const sectionLabel = (info: SectionInfo) => ( -