From f5b98ce8f42d35a72e6e564cdf97ea68c1f414f9 Mon Sep 17 00:00:00 2001 From: Keith Chong Date: Tue, 10 Oct 2023 17:39:53 -0400 Subject: [PATCH] AppSetUI Updates Signed-off-by: Keith Chong --- ui/src/app/app.tsx | 6 +- .../application-details-app-dropdown.tsx | 4 +- .../application-details.tsx | 348 +++++++------ .../application-resource-filter.tsx | 16 +- .../application-resource-list.tsx | 1 + .../application-pod-view/pod-view.tsx | 9 +- .../application-resource-events.tsx | 6 +- .../application-resource-tree.tsx | 204 ++++---- .../application-status-panel.tsx | 116 +++-- .../application-summary.tsx | 4 +- .../components/applications-container.tsx | 3 + .../applications-list/applications-filter.tsx | 187 +++++-- .../applications-list/applications-list.tsx | 488 +++++++++++------- .../applications-status-bar.tsx | 93 ++-- .../applications-summary.tsx | 92 ++-- .../applications-list/applications-table.tsx | 70 +-- .../applications-list/applications-tiles.tsx | 233 +++++---- .../applications-refresh-panel.tsx | 4 +- .../resource-details/resource-details.tsx | 168 +++--- ui/src/app/applications/components/utils.tsx | 262 ++++++---- .../project-role-policies-edit.tsx | 2 +- .../components/settings-container.tsx | 2 + .../settings-overview/settings-overview.tsx | 5 + .../application-selector.tsx | 26 +- .../app/shared/components/layout/layout.tsx | 16 +- ui/src/app/shared/models.ts | 75 ++- .../shared/services/applications-service.ts | 201 +++++--- .../app/shared/services/extensions-service.ts | 93 +++- ui/src/app/shared/services/index.ts | 5 + .../services/view-preferences-service.ts | 163 ++++-- ui/src/app/sidebar/sidebar.tsx | 83 ++- 31 files changed, 1892 insertions(+), 1093 deletions(-) diff --git a/ui/src/app/app.tsx b/ui/src/app/app.tsx index e38e28d91a9db..468c2709de4d8 100644 --- a/ui/src/app/app.tsx +++ b/ui/src/app/app.tsx @@ -5,6 +5,7 @@ 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'; @@ -30,6 +31,7 @@ type Routes = {[path: string]: {component: React.ComponentType { +export const ApplicationsDetailsAppDropdown = (props: {isAppSet: boolean, appName: string}) => { const [opened, setOpened] = React.useState(false); const [appFilter, setAppFilter] = React.useState(''); const ctx = React.useContext(Context); @@ -34,7 +34,7 @@ export const ApplicationsDetailsAppDropdown = (props: {appName: string}) => { } /> - services.applications.list([], {fields: ['items.metadata.name']})}> + services.applications.list(!props.isAppSet, [], {fields: ['items.metadata.name']})}> {apps => apps.items .filter(app => { diff --git a/ui/src/app/applications/components/application-details/application-details.tsx b/ui/src/app/applications/components/application-details/application-details.tsx index 3a556dd0b2524..e20b9777adc79 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -1,36 +1,36 @@ -import {DropDownMenu, NotificationType, SlidingPanel, Tooltip} from 'argo-ui'; +import { DropDownMenu, NotificationType, SlidingPanel, Tooltip } from 'argo-ui'; import * as classNames from 'classnames'; import * as PropTypes from 'prop-types'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as models from '../../../shared/models'; -import {RouteComponentProps} from 'react-router'; -import {BehaviorSubject, combineLatest, from, merge, Observable} from 'rxjs'; -import {delay, filter, map, mergeMap, repeat, retryWhen} from 'rxjs/operators'; +import { RouteComponentProps } from 'react-router'; +import { BehaviorSubject, combineLatest, from, merge, Observable } from 'rxjs'; +import { delay, filter, map, mergeMap, repeat, retryWhen } from 'rxjs/operators'; -import {DataLoader, EmptyState, ErrorNotification, ObservableQuery, Page, Paginate, Revision, Timestamp} from '../../../shared/components'; -import {AppContext, ContextApis} from '../../../shared/context'; +import { DataLoader, EmptyState, ErrorNotification, ObservableQuery, Page, Paginate, Revision, Timestamp } from '../../../shared/components'; +import { AppContext, ContextApis } from '../../../shared/context'; import * as appModels from '../../../shared/models'; -import {AppDetailsPreferences, AppsDetailsViewKey, AppsDetailsViewType, services} from '../../../shared/services'; - -import {ApplicationConditions} from '../application-conditions/application-conditions'; -import {ApplicationDeploymentHistory} from '../application-deployment-history/application-deployment-history'; -import {ApplicationOperationState} from '../application-operation-state/application-operation-state'; -import {PodGroupType, PodView} from '../application-pod-view/pod-view'; -import {ApplicationResourceTree, ResourceTreeNode} from '../application-resource-tree/application-resource-tree'; -import {ApplicationStatusPanel} from '../application-status-panel/application-status-panel'; -import {ApplicationSyncPanel} from '../application-sync-panel/application-sync-panel'; -import {ResourceDetails} from '../resource-details/resource-details'; +import { AbstractAppDetailsPreferences, AppDetailsPreferences, AppSetsDetailsViewKey, AppSetsDetailsViewType, AppsDetailsViewKey, AppsDetailsViewType, services } from '../../../shared/services'; + +import { ApplicationConditions } from '../application-conditions/application-conditions'; +import { ApplicationDeploymentHistory } from '../application-deployment-history/application-deployment-history'; +import { ApplicationOperationState } from '../application-operation-state/application-operation-state'; +import { PodGroupType, PodView } from '../application-pod-view/pod-view'; +import { ApplicationResourceTree, ResourceTreeNode } from '../application-resource-tree/application-resource-tree'; +import { ApplicationStatusPanel } from '../application-status-panel/application-status-panel'; +import { ApplicationSyncPanel } from '../application-sync-panel/application-sync-panel'; +import { ResourceDetails } from '../resource-details/resource-details'; import * as AppUtils from '../utils'; -import {ApplicationResourceList} from './application-resource-list'; -import {Filters, FiltersProps} from './application-resource-filter'; -import {getAppDefaultSource, urlPattern, helpTip} from '../utils'; -import {ChartDetails, ResourceStatus} from '../../../shared/models'; -import {ApplicationsDetailsAppDropdown} from './application-details-app-dropdown'; -import {useSidebarTarget} from '../../../sidebar/sidebar'; +import { ApplicationResourceList } from './application-resource-list'; +import { AbstractFiltersProps, Filters } from './application-resource-filter'; +import { getAppDefaultSource, urlPattern, helpTip, isApp } from '../utils'; +import { ChartDetails, ResourceStatus } from '../../../shared/models'; +import { ApplicationsDetailsAppDropdown } from './application-details-app-dropdown'; +import { useSidebarTarget } from '../../../sidebar/sidebar'; import './application-details.scss'; -import {AppViewExtension} from '../../../shared/services/extensions-service'; +import { AppViewExtension } from '../../../shared/services/extensions-service'; interface ApplicationDetailsState { page: number; @@ -41,7 +41,7 @@ interface ApplicationDetailsState { truncateNameOnRight?: boolean; collapsedNodes?: string[]; extensions?: AppViewExtension[]; - extensionsMap?: {[key: string]: AppViewExtension}; + extensionsMap?: { [key: string]: AppViewExtension }; } interface FilterInput { @@ -52,13 +52,13 @@ interface FilterInput { namespace: string[]; } -const ApplicationDetailsFilters = (props: FiltersProps) => { +const ApplicationDetailsFilters = (props: AbstractFiltersProps) => { const sidebarTarget = useSidebarTarget(); 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,21 +69,21 @@ 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 }; - private appChanged = new BehaviorSubject(null); + private appChanged = new BehaviorSubject(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 +113,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,37 +143,55 @@ export class ApplicationDetails extends React.Component (usrMsg.appName === appName && usrMsg.msgKey === 'groupNodes' ? {...usrMsg, display: true} : usrMsg)); - services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: !pref.groupNodes}}); + private toggleCompactView(appName: string, pref: AbstractAppDetailsPreferences) { + console.log("application-details - props is " + this.props.location.pathname) + let isAppSet = this.props.location.pathname.includes('applicationsets') + if (!isAppSet) { + (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 } }); } private getPageTitle(view: string) { - const {Tree, Pods, Network, List} = AppsDetailsViewKey; - switch (view) { - case Tree: - return 'Application Details Tree'; - case Network: - return 'Application Details Network'; - case Pods: - return 'Application Details Pods'; - case List: - return 'Application Details List'; + console.log("application-details - props is " + this.props.location.pathname) + let isAppSet = this.props.location.pathname.includes('applicationsets') + if (!isAppSet) { + const { Tree, Pods, Network, List } = AppsDetailsViewKey; + switch (view) { + case Tree: + return 'Application Details Tree'; + case Network: + return 'Application Details Network'; + case Pods: + return 'Application Details Pods'; + case List: + return 'Application Details List'; + } + } + else { + const { Tree, List } = AppSetsDetailsViewKey; + switch (view) { + case Tree: + return 'ApplicationSet Details Tree'; + case List: + return 'ApplicationSet Details List'; + } } return ''; } public render() { + let isAppSet = this.props.location.pathname.includes('applicationsets') return ( {q => ( {error}} - loadingRenderer={() => Loading...} + errorRenderer={error => {error}} + loadingRenderer={() => Loading...} input={this.props.match.params.name} load={name => combineLatest([this.loadAppInfo(name, this.appNamespace), services.viewPreferences.getPreferences(), q]).pipe( @@ -188,11 +206,13 @@ export class ApplicationDetails extends React.Component !!item); } if (params.get('view') != null) { - pref.view = params.get('view') as AppsDetailsViewType; + pref.view = isApp(application) ? params.get('view') as AppsDetailsViewType : params.get('view') as AppSetsDetailsViewType; } else { - const appDefaultView = (application.metadata && + const appDefaultView = isApp(application) ? (application.metadata && application.metadata.annotations && - application.metadata.annotations[appModels.AnnotationDefaultView]) as AppsDetailsViewType; + 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; } @@ -200,26 +220,26 @@ export class ApplicationDetails extends React.Component - {({application, tree, pref}: {application: appModels.Application; tree: appModels.ApplicationTree; pref: AppDetailsPreferences}) => { + {({ 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]; @@ -227,21 +247,23 @@ export class ApplicationDetails extends React.Component usrMsg.appName === application.metadata.name); + 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(); - application.status.resources.forEach(res => statusByKey.set(AppUtils.nodeKey(res), res)); + if (isApp(application)) { + (application as models.Application).status.resources.forEach(res => statusByKey.set(AppUtils.nodeKey(res), res)); + } const resources = new Map(); 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) { @@ -258,7 +280,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); }); @@ -274,14 +296,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; @@ -290,35 +312,37 @@ 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) => { - const existingIndex = pref.userHelpTipMsgs.findIndex(msg => msg.appName === usrHelpTip.appName && msg.msgKey === usrHelpTip.msgKey); - if (existingIndex !== -1) { - pref.userHelpTipMsgs[existingIndex] = usrHelpTip; - } else { - (pref.userHelpTipMsgs || []).push(usrHelpTip); + if (isApp(application)) { + 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 { + ((pref as AppDetailsPreferences).userHelpTipMsgs || []).push(usrHelpTip); + } } }; const toggleNameDirection = () => { - 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(); @@ -330,7 +354,7 @@ export class ApplicationDetails extends React.Component { @@ -344,7 +368,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 } }); }} /> - { - this.appContext.apis.navigation.goto('.', {view: Pods}); - services.viewPreferences.updatePreferences({appDetails: {...pref, view: Pods}}); + 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: 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 } }); }} /> ))} @@ -422,7 +446,7 @@ export class ApplicationDetails extends React.Component this.selectNode(appFullName, 0, 'diff')} showOperation={() => this.setOperationStatusVisible(true)} showConditions={() => this.setConditionsStatusVisible(true)} - showMetadataInfo={revision => this.setState({...this.state, revision})} + showMetadataInfo={revision => this.setState({ ...this.state, revision })} />
@@ -497,7 +521,7 @@ export class ApplicationDetails extends React.Component openGroupNodeDetails(groupdedNodeIds)} zoom={pref.zoom} - podGroupCount={pref.podGroupCount} + // podGroupCount={isApp(application) ? (pref as AppDetailsPreferences).podGroupCount : 0} appContext={this.appContext} nameDirection={this.state.truncateNameOnRight} filters={pref.resourceFilter} @@ -550,7 +574,7 @@ export class ApplicationDetails extends React.Component this.setState({page})} + onPageChange={page => this.setState({ page })} preferencesKey='application-details'> {data => ( AppUtils.renderResourceMenu( - {...node, root: node}, + { ...node, root: node }, application, tree, this.appContext.apis, @@ -571,11 +595,11 @@ export class ApplicationDetails extends React.Component )) || ( - -

No resources found

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

No resources found

+
Try to change filter criteria
+
+ )}
)} @@ -584,14 +608,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) ) } @@ -606,7 +630,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} /> @@ -632,7 +656,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:
@@ -685,7 +709,7 @@ export class ApplicationDetails extends React.Component {metadata => ( -
+
SHA:
@@ -719,7 +743,7 @@ export class ApplicationDetails extends React.Component
Message:
-
+
{renderCommitMessage(metadata.message)}
@@ -739,12 +763,12 @@ export class ApplicationDetails extends React.Component {prop.actionLabel}; - const hasMultipleSources = app.spec.sources && app.spec.sources.length > 0; - return [ + 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: , @@ -754,7 +778,7 @@ export class ApplicationDetails extends React.Component, action: () => this.selectNode(fullName, 0, 'diff'), - disabled: app.status.sync.status === appModels.SyncStatuses.Synced + disabled: (app as models.Application).status.sync.status === appModels.SyncStatuses.Synced }, { iconClassName: 'fa fa-sync', @@ -765,7 +789,7 @@ export class ApplicationDetails extends React.Component, action: () => this.setOperationStatusVisible(true), - disabled: !app.status.operationState + disabled: !(app as models.Application).status.operationState }, { iconClassName: 'fa fa-history', @@ -780,7 +804,7 @@ export class ApplicationDetails extends React.Component { this.setRollbackPanelVisible(0); }, - disabled: !app.status.operationState || hasMultipleSources + disabled: !(app as models.Application).status.operationState || hasMultipleSources }, { iconClassName: 'fa fa-times-circle', @@ -788,7 +812,7 @@ export class ApplicationDetails extends React.Component this.deleteApplication() }, { - iconClassName: classNames('fa fa-redo', {'status-icon--spin': !!refreshing}), + iconClassName: classNames('fa fa-redo', { 'status-icon--spin': !!refreshing }), title: ( {' '} @@ -796,7 +820,7 @@ export class ApplicationDetails extends React.Component !refreshing && services.applications.get(app.metadata.name, app.metadata.namespace, 'hard') + action: () => !refreshing && services.applications.get(!this.props.location.pathname.includes('applicationsets'), app.metadata.name, app.metadata.namespace, 'hard') } ]} anchor={() => } @@ -806,12 +830,18 @@ export class ApplicationDetails extends React.Component { if (!refreshing) { - services.applications.get(app.metadata.name, app.metadata.namespace, 'normal'); + services.applications.get(!this.props.location.pathname.includes('applicationsets'), app.metadata.name, app.metadata.namespace, 'normal'); AppUtils.setAppRefreshing(app); this.appChanged.next(app); } } } + ] : [ + { + iconClassName: 'fa fa-info-circle', + title: , + action: () => this.selectNode(fullName) + } ]; } @@ -856,12 +886,12 @@ export class ApplicationDetails extends React.Component { - return from(services.applications.get(name, appNamespace)) + private loadAppInfo(name: string, appNamespace: string): Observable<{ application: appModels.AbstractApplication; tree: appModels.ApplicationTree }> { + return from(services.applications.get(!this.props.location.pathname.includes('applicationsets'), name, appNamespace)) .pipe( mergeMap(app => { const fallbackTree = { - nodes: app.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; @@ -871,7 +901,7 @@ export class ApplicationDetails extends React.Component !!item)), AppUtils.handlePageVisibility(() => services.applications - .watch({name, appNamespace}) + .watch(!isApp(app), { name, appNamespace }) .pipe( map(watchEvent => { if (watchEvent.type === 'DELETED') { @@ -886,10 +916,10 @@ export class ApplicationDetails extends React.Component fallbackTree), + services.applications.resourceTree(isApp(app), name, appNamespace).catch(() => fallbackTree), AppUtils.handlePageVisibility(() => services.applications - .watchResourceTree(name, appNamespace) + .watchResourceTree(isApp(app), name, appNamespace) .pipe(repeat()) .pipe(retryWhen(errors => errors.pipe(delay(500)))) ) @@ -898,16 +928,16 @@ 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}) { - const latestApp = await services.applications.get(app.metadata.name, app.metadata.namespace); + private async updateApp(app: appModels.Application, query: { validate?: boolean }) { + const latestApp = await services.applications.get(!this.props.location.pathname.includes('applicationsets'), app.metadata.name, app.metadata.namespace); latestApp.metadata.labels = app.metadata.labels; latestApp.metadata.annotations = app.metadata.annotations; latestApp.spec = app.spec; @@ -918,7 +948,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; } @@ -948,19 +978,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-details/application-resource-filter.tsx b/ui/src/app/applications/components/application-details/application-resource-filter.tsx index a3d99f92488f3..f4ffd6c0169d6 100644 --- a/ui/src/app/applications/components/application-details/application-resource-filter.tsx +++ b/ui/src/app/applications/components/application-details/application-resource-filter.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {Checkbox} from 'argo-ui/v2'; import {ApplicationTree, HealthStatusCode, HealthStatuses, SyncStatusCode, SyncStatuses} from '../../../shared/models'; -import {AppDetailsPreferences, services} from '../../../shared/services'; +import {AppDetailsPreferences, AppSetDetailsPreferences, services} from '../../../shared/services'; import {Context} from '../../../shared/context'; import {Filter, FiltersGroup} from '../filter/filter'; import {ComparisonStatusIcon, HealthStatusIcon} from '../utils'; @@ -14,9 +14,9 @@ function toOption(label: string) { return {label}; } -export interface FiltersProps { +export interface AbstractFiltersProps { children?: React.ReactNode; - pref: AppDetailsPreferences; + pref: AppDetailsPreferences | AppSetDetailsPreferences; tree: ApplicationTree; resourceNodes: models.ResourceStatus[]; onSetFilter: (items: string[]) => void; @@ -24,7 +24,15 @@ export interface FiltersProps { collapsed?: boolean; } -export const Filters = (props: FiltersProps) => { +export interface FiltersProps extends AbstractFiltersProps{ + pref: AppDetailsPreferences; +} + +export interface AppSetFiltersProps extends AbstractFiltersProps{ + pref: AppSetDetailsPreferences; +} + +export const Filters = (props: AbstractFiltersProps) => { const ctx = React.useContext(Context); const {pref, tree, onSetFilter} = props; diff --git a/ui/src/app/applications/components/application-details/application-resource-list.tsx b/ui/src/app/applications/components/application-details/application-resource-list.tsx index dba61c5135492..656d17814a374 100644 --- a/ui/src/app/applications/components/application-details/application-resource-list.tsx +++ b/ui/src/app/applications/components/application-details/application-resource-list.tsx @@ -31,6 +31,7 @@ export const ApplicationResourceList = ({ return null; } const parentNode = ((resources || []).length > 0 && (getResNode(tree.nodes, nodeKey(resources[0])) as ResourceNode)?.parentRefs?.[0]) || ({} as ResourceRef); + // debugger; return (
diff --git a/ui/src/app/applications/components/application-pod-view/pod-view.tsx b/ui/src/app/applications/components/application-pod-view/pod-view.tsx index 2c1bb54770abf..392d6dfe37975 100644 --- a/ui/src/app/applications/components/application-pod-view/pod-view.tsx +++ b/ui/src/app/applications/components/application-pod-view/pod-view.tsx @@ -6,7 +6,7 @@ import Moment from 'react-moment'; import {AppContext} from '../../../shared/context'; import {EmptyState} from '../../../shared/components'; import {Application, ApplicationTree, HostResourceInfo, InfoItem, Node, Pod, ResourceName, ResourceNode, ResourceStatus} from '../../../shared/models'; -import {PodViewPreferences, services, ViewPreferences} from '../../../shared/services'; +import {AppDetailsPreferences, PodViewPreferences, services, ViewPreferences} from '../../../shared/services'; import {ResourceTreeNode} from '../application-resource-tree/application-resource-tree'; import {ResourceIcon} from '../resource-icon'; @@ -15,6 +15,7 @@ import {ComparisonStatusIcon, isYoungerThanXMinutes, HealthStatusIcon, nodeKey, import './pod-view.scss'; import {PodTooltip} from './pod-tooltip'; +import { Observable } from 'rxjs'; interface PodViewProps { tree: ApplicationTree; @@ -48,7 +49,7 @@ export class PodView extends React.Component { public render() { return ( - services.viewPreferences.getPreferences()}> + services.viewPreferences.getPreferences() as Observable}> {prefs => { const podPrefs = prefs.appDetails.podView || ({} as PodViewPreferences); const groups = this.processTree(podPrefs.sortMode, this.props.tree.hosts || []) || []; @@ -75,7 +76,7 @@ export class PodView extends React.Component { style={{border: 'none', width: '170px'}} onClick={() => services.viewPreferences.updatePreferences({ - appDetails: {...prefs.appDetails, podView: {...podPrefs, hideUnschedulable: !podPrefs.hideUnschedulable}} + appDetails: {...prefs.appDetails, podView: {...podPrefs, hideUnschedulable: !podPrefs.hideUnschedulable} } as AppDetailsPreferences }) }> @@ -270,7 +271,7 @@ export class PodView extends React.Component { ), action: () => { this.appContext.apis.navigation.goto('.', {podSortMode: mode}); - services.viewPreferences.updatePreferences({appDetails: {...prefs.appDetails, podView: {...podPrefs, sortMode: mode}}}); + services.viewPreferences.updatePreferences({appDetails: {...prefs.appDetails, podView: {...podPrefs, sortMode: mode}} as AppDetailsPreferences}); } })); } diff --git a/ui/src/app/applications/components/application-resource-events/application-resource-events.tsx b/ui/src/app/applications/components/application-resource-events/application-resource-events.tsx index 87993e0a3858f..b0a0a9e41f169 100644 --- a/ui/src/app/applications/components/application-resource-events/application-resource-events.tsx +++ b/ui/src/app/applications/components/application-resource-events/application-resource-events.tsx @@ -4,13 +4,13 @@ import * as React from 'react'; import {DataLoader, EventsList} from '../../../shared/components'; import {services} from '../../../shared/services'; -export const ApplicationResourceEvents = (props: {applicationName: string; applicationNamespace: string; resource?: {namespace: string; name: string; uid: string}}) => ( +export const ApplicationResourceEvents = (props: {isApp: boolean, applicationName: string; applicationNamespace: string; resource?: {namespace: string; name: string; uid: string}}) => (
props.resource - ? services.applications.resourceEvents(props.applicationName, props.applicationNamespace, props.resource) - : services.applications.events(props.applicationName, props.applicationNamespace) + ? services.applications.resourceEvents(props.isApp, props.applicationName, props.applicationNamespace, props.resource) + : services.applications.events(props.isApp, props.applicationName, props.applicationNamespace) } loadingRenderer={() => }> {events => } diff --git a/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx b/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx index 6f28a40ea5046..0dcbfc956ea92 100644 --- a/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx +++ b/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx @@ -7,11 +7,11 @@ import * as moment from 'moment'; import * as models from '../../../shared/models'; -import {EmptyState} from '../../../shared/components'; -import {AppContext, Consumer} from '../../../shared/context'; -import {ApplicationURLs} from '../application-urls'; -import {ResourceIcon} from '../resource-icon'; -import {ResourceLabel} from '../resource-label'; +import { EmptyState } from '../../../shared/components'; +import { AppContext, Consumer } from '../../../shared/context'; +import { ApplicationURLs } from '../application-urls'; +import { ResourceIcon } from '../resource-icon'; +import { ResourceLabel } from '../resource-label'; import { BASE_COLORS, ComparisonStatusIcon, @@ -19,18 +19,19 @@ import { getAppOverridesCount, HealthStatusIcon, isAppNode, + isApp, isYoungerThanXMinutes, NodeId, nodeKey, PodHealthIcon, getUsrMsgKeyToDisplay } from '../utils'; -import {NodeUpdateAnimation} from './node-update-animation'; -import {PodGroup} from '../application-pod-view/pod-view'; +import { NodeUpdateAnimation } from './node-update-animation'; +import { PodGroup } from '../application-pod-view/pod-view'; import './application-resource-tree.scss'; -import {ArrowConnector} from './arrow-connector'; +import { ArrowConnector } from './arrow-connector'; -function treeNodeKey(node: NodeId & {uid?: string}) { +function treeNodeKey(node: NodeId & { uid?: string }) { return node.uid || nodeKey(node); } @@ -47,8 +48,8 @@ export interface ResourceTreeNode extends models.ResourceNode { isExpanded?: boolean; } -export interface ApplicationResourceTreeProps { - app: models.Application; +export interface AbstractApplicationResourceTreeProps { + app: models.AbstractApplication; tree: models.ApplicationTree; useNetworkingHierarchy: boolean; nodeFilter: (node: ResourceTreeNode) => boolean; @@ -64,7 +65,7 @@ export interface ApplicationResourceTreeProps { updateUsrHelpTipMsgs: (userMsgs: models.UserMessages) => void; setShowCompactNodes: (showCompactNodes: boolean) => void; zoom: number; - podGroupCount: number; + // podGroupCount: number; filters?: string[]; setTreeFilterGraph?: (filterGraph: any[]) => void; nameDirection: boolean; @@ -72,6 +73,13 @@ export interface ApplicationResourceTreeProps { getNodeExpansion: (node: string) => boolean; } +export interface ApplicationResourceTreeProps extends AbstractApplicationResourceTreeProps { + podGroupCount: number; +} + +export interface ApplicationSetResourceTreeProps extends AbstractApplicationResourceTreeProps { +} + interface Line { x1: number; y1: number; @@ -104,14 +112,14 @@ const TRAFFIC_COLORS = [0, 0.25, 0.4, 0.6] ) .reduce((first, second) => first.concat(second), []); -function getGraphSize(nodes: dagre.Node[]): {width: number; height: number} { +function getGraphSize(nodes: dagre.Node[]): { width: number; height: number } { let width = 0; let height = 0; nodes.forEach(node => { width = Math.max(node.x + node.width, width); height = Math.max(node.y + node.height, height); }); - return {width, height}; + return { width, height }; } function groupNodes(nodes: ResourceTreeNode[], graph: dagre.graphlib.Graph) { @@ -125,7 +133,7 @@ function groupNodes(nodes: ResourceTreeNode[], graph: dagre.graphlib.Graph) { }; } - function filterNoChildNode(nodeInfo: {childIds: dagre.Node[]}) { + function filterNoChildNode(nodeInfo: { childIds: dagre.Node[] }) { return nodeInfo.childIds.length === 0; } @@ -146,8 +154,8 @@ function groupNodes(nodes: ResourceTreeNode[], graph: dagre.graphlib.Graph) { const groupedNodesArr = siblingNodesArr .map(eachLevel => { return eachLevel.reduce( - (groupedNodesInfo: {kind: string; nodeIds?: string[]; parentIds?: dagre.Node[]}[], currentNodeInfo: {kind: string; nodeId: string; parentIds: dagre.Node[]}) => { - const index = groupedNodesInfo.findIndex((nodeInfo: {kind: string}) => currentNodeInfo.kind === nodeInfo.kind); + (groupedNodesInfo: { kind: string; nodeIds?: string[]; parentIds?: dagre.Node[] }[], currentNodeInfo: { kind: string; nodeId: string; parentIds: dagre.Node[] }) => { + const index = groupedNodesInfo.findIndex((nodeInfo: { kind: string }) => currentNodeInfo.kind === nodeInfo.kind); if (index > -1) { groupedNodesInfo[index].nodeIds.push(currentNodeInfo.nodeId); } @@ -171,12 +179,12 @@ function groupNodes(nodes: ResourceTreeNode[], graph: dagre.graphlib.Graph) { .reduce((flattedNodesGroup, groupedNodes) => { return flattedNodesGroup.concat(groupedNodes); }, []) - .filter((eachArr: {nodeIds: string[]}) => eachArr.nodeIds.length > 1); + .filter((eachArr: { nodeIds: string[] }) => eachArr.nodeIds.length > 1); // update graph if (groupedNodesArr.length > 0) { - groupedNodesArr.forEach((obj: {kind: string; nodeIds: string[]; parentIds: dagre.Node[]}) => { - const {nodeIds, kind, parentIds} = obj; + groupedNodesArr.forEach((obj: { kind: string; nodeIds: string[]; parentIds: dagre.Node[] }) => { + const { nodeIds, kind, parentIds } = obj; const groupedNodeIds: string[] = []; const podGroupIds: string[] = []; nodeIds.forEach((nodeId: string) => { @@ -249,11 +257,11 @@ export function compareNodes(first: ResourceTreeNode, second: ResourceTreeNode) ); } -function appNodeKey(app: models.Application) { - return nodeKey({group: 'argoproj.io', kind: app.kind, name: app.metadata.name, namespace: app.metadata.namespace}); +function appNodeKey(app: models.AbstractApplication) { + return nodeKey({ group: 'argoproj.io', kind: app.kind, name: app.metadata.name, namespace: app.metadata.namespace }); } -function renderFilteredNode(node: {count: number} & dagre.Node, onClearFilter: () => any) { +function renderFilteredNode(node: { count: number } & dagre.Node, onClearFilter: () => any) { const indicators = new Array(); let count = Math.min(node.count - 1, 3); while (count > 0) { @@ -261,7 +269,7 @@ function renderFilteredNode(node: {count: number} & dagre.Node, onClearFilter: ( } return ( -
+
@@ -275,14 +283,14 @@ function renderFilteredNode(node: {count: number} & dagre.Node, onClearFilter: (
))} ); } -function renderGroupedNodes(props: ApplicationResourceTreeProps, node: {count: number} & dagre.Node & ResourceTreeNode) { +function renderGroupedNodes(props: ApplicationResourceTreeProps, node: { count: number } & dagre.Node & ResourceTreeNode) { const indicators = new Array(); let count = Math.min(node.count - 1, 3); while (count > 0) { @@ -290,18 +298,18 @@ function renderGroupedNodes(props: ApplicationResourceTreeProps, node: {count: n } return ( -
+

-
{ResourceLabel({kind: node.kind})}
+
{ResourceLabel({ kind: node.kind })}
props.onGroupdNodeClick && props.onGroupdNodeClick(node.groupedNodeIds)} title={`Click to see details of ${node.count} collapsed ${node.kind} and doesn't contains any active pods`}> {node.kind} - + {node.kind === 'ReplicaSet' ? ( ))} @@ -327,15 +335,15 @@ function renderGroupedNodes(props: ApplicationResourceTreeProps, node: {count: n function renderTrafficNode(node: dagre.Node) { return ( -
-
+
+
); } -function renderLoadBalancerNode(node: dagre.Node & {label: string; color: string}) { +function renderLoadBalancerNode(node: dagre.Node & { label: string; color: string }) { return (
- +
{node.label} @@ -387,8 +395,8 @@ function processPodGroup(targetPodGroup: ResourceTreeNode, child: ResourceTreeNo const p: models.Pod = { ...child, fullName: nodeKey(child), - metadata: {name: child.name}, - spec: {nodeName: 'Unknown'}, + metadata: { name: child.name }, + spec: { nodeName: 'Unknown' }, health: child.health ? child.health.status : 'Unknown' } as models.Pod; @@ -412,7 +420,7 @@ function renderPodGroup(props: ApplicationResourceTreeProps, id: string, node: R } const appNode = isAppNode(node); const rootNode = !node.root; - const extLinks: string[] = props.app.status.summary.externalURLs; + const extLinks: string[] = isApp(props.app) ? (props.app as models.Application).status.summary.externalURLs : []; const podGroupChildren = childMap.get(treeNodeKey(node)); const nonPodChildren = podGroupChildren?.reduce((acc, child) => { if (child.kind !== 'Pod') { @@ -471,7 +479,7 @@ function renderPodGroup(props: ApplicationResourceTreeProps, id: string, node: R })}>
- {!rootNode &&
{ResourceLabel({kind: node.kind})}
} + {!rootNode &&
{ResourceLabel({ kind: node.kind })}
}

{ expandCollapse(node, props); @@ -581,7 +589,7 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any, {pods.length !== 0 && showPodGroupByStatus ? (
- +
@@ -632,13 +640,13 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any, } }} key={pod.metadata.name}> -
+
{isYoungerThanXMinutes(pod, 30) && ( )}
- +
@@ -659,7 +667,7 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any, ), action: () => { - props.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'logs'}, {replace: true}); + props.appContext.apis.navigation.goto('.', { node: pod.fullName, tab: 'logs' }, { replace: true }); } }, { @@ -686,7 +694,7 @@ function expandCollapse(node: ResourceTreeNode, props: ApplicationResourceTreePr props.setNodeExpansion(node.uid, isExpanded); } -function NodeInfoDetails({tag: tag, kind: kind}: {tag: models.InfoItem; kind: string}) { +function NodeInfoDetails({ tag: tag, kind: kind }: { tag: models.InfoItem; kind: string }) { if (kind === 'Pod') { const val = `${tag.name}`; if (val === 'Status Reason') { @@ -751,7 +759,7 @@ function renderResourceNode(props: ApplicationResourceTreeProps, id: string, nod } const appNode = isAppNode(node); const rootNode = !node.root; - const extLinks: string[] = props.app.status.summary.externalURLs; + const extLinks: string[] = isApp(props.app) ? (props.app as models.Application).status.summary.externalURLs : []; const childCount = nodesHavingChildren.get(node.uid); return (

- {!rootNode &&
{ResourceLabel({kind: node.kind})}
} + {!rootNode &&
{ResourceLabel({ kind: node.kind })}
}
{ +export const ApplicationResourceTree = (props: AbstractApplicationResourceTreeProps) => { const graph = new dagre.graphlib.Graph(); - graph.setGraph({nodesep: 25, rankdir: 'LR', marginy: 45, marginx: -100, ranksep: 80}); + graph.setGraph({ nodesep: 25, rankdir: 'LR', marginy: 45, marginx: -100, ranksep: 80 }); graph.setDefaultEdgeLabel(() => ({})); const overridesCount = getAppOverridesCount(props.app); const appNode = { @@ -891,29 +899,31 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => group: 'argoproj.io', version: '', children: Array(), - status: props.app.status.sync.status, - health: props.app.status.health, + status: isApp(props.app) ? props.app.status.sync.status : null, + health: isApp(props.app) ? props.app.status.health : props.app.status, uid: props.app.kind + '-' + props.app.metadata.namespace + '-' + props.app.metadata.name, info: overridesCount > 0 ? [ - { - name: 'Parameter overrides', - value: `${overridesCount} parameter override(s)` - } - ] + { + name: 'Parameter overrides', + value: `${overridesCount} parameter override(s)` + } + ] : [] }; const statusByKey = new Map(); - props.app.status.resources.forEach(res => statusByKey.set(nodeKey(res), res)); + if (isApp(props.app)) { + (props.app as models.Application).status.resources.forEach(res => statusByKey.set(nodeKey(res), res)); + } const nodeByKey = new Map(); props.tree.nodes - .map(node => ({...node, orphaned: false})) - .concat(((props.showOrphanedResources && props.tree.orphanedNodes) || []).map(node => ({...node, orphaned: true}))) + .map(node => ({ ...node, orphaned: false })) + .concat(((props.showOrphanedResources && props.tree.orphanedNodes) || []).map(node => ({ ...node, orphaned: true }))) .forEach(node => { const status = statusByKey.get(nodeKey(node)); - const resourceNode: ResourceTreeNode = {...node}; + const resourceNode: ResourceTreeNode = { ...node }; if (status) { resourceNode.health = status.health; resourceNode.status = status.status; @@ -938,18 +948,20 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => props.setTreeFilterGraph(filteredGraph); } }, [props.filters]); - const {podGroupCount, userMsgs, updateUsrHelpTipMsgs, setShowCompactNodes} = props; - const podCount = nodes.filter(node => node.kind === 'Pod').length; - React.useEffect(() => { - if (podCount > podGroupCount) { - const userMsg = getUsrMsgKeyToDisplay(appNode.name, 'groupNodes', userMsgs); - updateUsrHelpTipMsgs(userMsg); - if (!userMsg.display) { - setShowCompactNodes(true); + if (isApp(props.app)) { + const podCount = nodes.filter(node => node.kind === 'Pod').length; + const { podGroupCount, userMsgs, updateUsrHelpTipMsgs, setShowCompactNodes } = props as ApplicationResourceTreeProps; + React.useEffect(() => { + if (podCount > podGroupCount) { + const userMsg = getUsrMsgKeyToDisplay(appNode.name, 'groupNodes', userMsgs); + updateUsrHelpTipMsgs(userMsg); + if (!userMsg.display) { + setShowCompactNodes(true); + } } - } - }, [podCount]); + }, [podCount]); + } function filterGraph(app: models.Application, filteredIndicatorParent: string, graphNodesFilter: dagre.graphlib.Graph, predicate: (node: ResourceTreeNode) => boolean) { const appKey = appNodeKey(app); @@ -971,12 +983,12 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => } }); if (filtered) { - graphNodesFilter.setNode(FILTERED_INDICATOR_NODE, {height: NODE_HEIGHT, width: NODE_WIDTH, count: filtered, type: NODE_TYPES.filteredIndicator}); + graphNodesFilter.setNode(FILTERED_INDICATOR_NODE, { height: NODE_HEIGHT, width: NODE_WIDTH, count: filtered, type: NODE_TYPES.filteredIndicator }); graphNodesFilter.setEdge(filteredIndicatorParent, FILTERED_INDICATOR_NODE); } } - if (props.useNetworkingHierarchy) { + if (props.useNetworkingHierarchy && isApp(props.app)) { // Network view const hasParents = new Set(); const networkNodes = nodes.filter(node => node.networkingInfo); @@ -1000,7 +1012,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => hiddenNodes.push(child); } } else { - processPodGroup(parent, child, props); + processPodGroup(parent, child, props as ApplicationResourceTreeProps); } }); }); @@ -1028,7 +1040,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => sources.forEach((key, i) => colorsBySource.set(key, TRAFFIC_COLORS[i % TRAFFIC_COLORS.length])); if (externalRoots.length > 0) { - graph.setNode(EXTERNAL_TRAFFIC_NODE, {height: NODE_HEIGHT, width: 30, type: NODE_TYPES.externalTraffic}); + graph.setNode(EXTERNAL_TRAFFIC_NODE, { height: NODE_HEIGHT, width: 30, type: NODE_TYPES.externalTraffic }); externalRoots.sort(compareNodes).forEach(root => { const loadBalancers = root.networkingInfo.ingress.map(ingress => ingress.hostname || ingress.ip); const colorByService = new Map(); @@ -1039,11 +1051,11 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => if (root.podGroup && props.showCompactNodes) { setPodGroupNode(root, root); } else { - graph.setNode(treeNodeKey(root), {...root, width: NODE_WIDTH, height: NODE_HEIGHT, root}); + graph.setNode(treeNodeKey(root), { ...root, width: NODE_WIDTH, height: NODE_HEIGHT, root }); } (childrenByParentKey.get(treeNodeKey(root)) || []).forEach(child => { if (root.namespace === child.namespace) { - graph.setEdge(treeNodeKey(root), treeNodeKey(child), {colors: [colorByService.get(treeNodeKey(child))]}); + graph.setEdge(treeNodeKey(root), treeNodeKey(child), { colors: [colorByService.get(treeNodeKey(child))] }); } }); loadBalancers.forEach(key => { @@ -1055,14 +1067,14 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => label: key, color: colorsBySource.get(key) }); - graph.setEdge(loadBalancerNodeKey, treeNodeKey(root), {colors: [colorsBySource.get(key)]}); - graph.setEdge(EXTERNAL_TRAFFIC_NODE, loadBalancerNodeKey, {colors: [colorsBySource.get(key)]}); + graph.setEdge(loadBalancerNodeKey, treeNodeKey(root), { colors: [colorsBySource.get(key)] }); + graph.setEdge(EXTERNAL_TRAFFIC_NODE, loadBalancerNodeKey, { colors: [colorsBySource.get(key)] }); }); }); } if (internalRoots.length > 0) { - graph.setNode(INTERNAL_TRAFFIC_NODE, {height: NODE_HEIGHT, width: 30, type: NODE_TYPES.internalTraffic}); + graph.setNode(INTERNAL_TRAFFIC_NODE, { height: NODE_HEIGHT, width: 30, type: NODE_TYPES.internalTraffic }); internalRoots.forEach(root => { processNode(root, root, [colorsBySource.get(treeNodeKey(root))]); graph.setEdge(INTERNAL_TRAFFIC_NODE, treeNodeKey(root)); @@ -1074,7 +1086,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => } } else { // Tree view - const managedKeys = new Set(props.app.status.resources.map(nodeKey)); + const managedKeys = isApp(props.app) ? new Set((props.app as models.Application).status.resources.map(nodeKey)) : new Set(); const orphanedKeys = new Set(props.tree.orphanedNodes?.map(nodeKey)); const orphans: ResourceTreeNode[] = []; let allChildNodes: ResourceTreeNode[] = []; @@ -1104,7 +1116,9 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => } } else { const parentTreeNode = nodeByKey.get(parentId); - processPodGroup(parentTreeNode, node, props); + if (isApp(props.app)) { + processPodGroup(parentTreeNode, node, props as ApplicationResourceTreeProps); + } } if (props.showCompactNodes) { if (childrenMap.has(parentId)) { @@ -1124,7 +1138,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => orphans.sort(compareNodes).forEach(node => { processNode(node, node); }); - graph.setNode(appNodeKey(props.app), {...appNode, width: NODE_WIDTH, height: NODE_HEIGHT}); + graph.setNode(appNodeKey(props.app), { ...appNode, width: NODE_WIDTH, height: NODE_HEIGHT }); if (props.nodeFilter) { filterGraph(props.app, appNodeKey(props.app), graph, props.nodeFilter); } @@ -1135,28 +1149,28 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => function setPodGroupNode(node: ResourceTreeNode, root: ResourceTreeNode) { const numberOfRows = Math.ceil(node.podGroup.pods.length / 8); - graph.setNode(treeNodeKey(node), {...node, type: NODE_TYPES.podGroup, width: NODE_WIDTH, height: POD_NODE_HEIGHT + 30 * numberOfRows, root}); + graph.setNode(treeNodeKey(node), { ...node, type: NODE_TYPES.podGroup, width: NODE_WIDTH, height: POD_NODE_HEIGHT + 30 * numberOfRows, root }); } function processNode(node: ResourceTreeNode, root: ResourceTreeNode, colors?: string[]) { if (props.showCompactNodes && node.podGroup) { setPodGroupNode(node, root); } else { - graph.setNode(treeNodeKey(node), {...node, width: NODE_WIDTH, height: NODE_HEIGHT, root}); + graph.setNode(treeNodeKey(node), { ...node, width: NODE_WIDTH, height: NODE_HEIGHT, root }); } (childrenByParentKey.get(treeNodeKey(node)) || []).sort(compareNodes).forEach(child => { if (treeNodeKey(child) === treeNodeKey(root)) { return; } if (node.namespace === child.namespace) { - graph.setEdge(treeNodeKey(node), treeNodeKey(child), {colors}); + graph.setEdge(treeNodeKey(node), treeNodeKey(child), { colors }); } processNode(child, root, colors); }); } dagre.layout(graph); - const edges: {from: string; to: string; lines: Line[]; backgroundImage?: string; color?: string; colors?: string | {[key: string]: any}}[] = []; + const edges: { from: string; to: string; lines: Line[]; backgroundImage?: string; color?: string; colors?: string | { [key: string]: any } }[] = []; const nodeOffset = new Map(); const reverseEdge = new Map(); graph.edges().forEach(edgeInfo => { @@ -1197,7 +1211,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => const endNodeLeft = 140; let spaceForExpansionIcon = 0; if (edgeInfo.v.startsWith(EXTERNAL_TRAFFIC_NODE) && !edgeInfo.v.startsWith(EXTERNAL_TRAFFIC_NODE + ':')) { - lines.push({x1: startNode.x + 10, y1: startNode.y, x2: endNode.x - endNodeLeft, y2: endNode.y}); + lines.push({ x1: startNode.x + 10, y1: startNode.y, x2: endNode.x - endNodeLeft, y2: endNode.y }); } else { if (edgeInfo.v.startsWith(EXTERNAL_TRAFFIC_NODE + ':')) { startNodeRight = 152; @@ -1211,14 +1225,14 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => startNodeRight + (endNode.x - startNode.x - startNodeRight - endNodeLeft) / len + ((endNode.x - startNode.x - startNodeRight - endNodeLeft) / len) * offset; - lines.push({x1: startNode.x + startNodeRight, y1: startNode.y, x2: firstBend, y2: startNode.y}); + lines.push({ x1: startNode.x + startNodeRight, y1: startNode.y, x2: firstBend, y2: startNode.y }); if (startNode.y - yEnd >= 1 || yEnd - startNode.y >= 1) { - lines.push({x1: firstBend, y1: startNode.y, x2: firstBend, y2: yEnd}); + lines.push({ x1: firstBend, y1: startNode.y, x2: firstBend, y2: yEnd }); } - lines.push({x1: firstBend, y1: yEnd, x2: endNode.x - endNodeLeft, y2: yEnd}); + lines.push({ x1: firstBend, y1: yEnd, x2: endNode.x - endNodeLeft, y2: yEnd }); } } - edges.push({from: edgeInfo.v, to: edgeInfo.w, lines, backgroundImage, colors: [{colors}]}); + edges.push({ from: edgeInfo.v, to: edgeInfo.w, lines, backgroundImage, colors: [{ colors }] }); }); const graphNodes = graph.nodes(); const size = getGraphSize(graphNodes.map(id => graph.node(id))); @@ -1230,8 +1244,8 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => )) || (
+ className={classNames('application-resource-tree', { 'application-resource-tree--network': props.useNetworkingHierarchy })} + style={{ width: size.width + 150, height: size.height + 250, transformOrigin: '0% 0%', transform: `scale(${props.zoom})` }}> {graphNodes.map(key => { const node = graph.node(key); const nodeType = node.type; @@ -1245,11 +1259,11 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => case NODE_TYPES.externalLoadBalancer: return {renderLoadBalancerNode(node as any)}; case NODE_TYPES.groupedNodes: - return {renderGroupedNodes(props, node as any)}; + return {renderGroupedNodes(props as ApplicationResourceTreeProps, node as any)}; case NODE_TYPES.podGroup: - return {renderPodGroup(props, key, node as ResourceTreeNode & dagre.Node, childrenMap)}; + return {renderPodGroup(props as ApplicationResourceTreeProps, key, node as ResourceTreeNode & dagre.Node, childrenMap)}; default: - return {renderResourceNode(props, key, node as ResourceTreeNode & dagre.Node, nodesHavingChildren)}; + return {renderResourceNode(props as ApplicationResourceTreeProps, key, node as ResourceTreeNode & dagre.Node, nodesHavingChildren)}; } })} {edges.map(edge => ( 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 c82252144849c..4ccd5b2b998b3 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,18 +1,18 @@ -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 {ApplicationSyncWindowStatusIcon, ComparisonStatusIcon, getAppDefaultSource, getAppOperationState} 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'; interface Props { - application: models.Application; + application: models.AbstractApplication; showDiff?: () => any; showOperation?: () => any; showConditions?: () => any; @@ -25,7 +25,7 @@ interface SectionInfo { } const sectionLabel = (info: SectionInfo) => ( -