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 { +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,25 +143,36 @@ export class ApplicationDetails extends React.Component {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( @@ -187,11 +198,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; } @@ -199,26 +212,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]; @@ -226,20 +239,22 @@ export class ApplicationDetails extends React.Component { 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) { @@ -256,7 +271,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); }); @@ -272,14 +287,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; @@ -288,27 +303,27 @@ 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 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(); @@ -320,7 +335,7 @@ export class ApplicationDetails extends React.Component { @@ -334,7 +349,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 } }); }} /> ))} @@ -412,7 +427,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 })} />
@@ -488,7 +503,7 @@ export class ApplicationDetails extends React.Component openGroupNodeDetails(groupdedNodeIds)} zoom={pref.zoom} - podGroupCount={pref.podGroupCount} + // podGroupCount={pref.podGroupCount} appContext={this.appContext} nameDirection={this.state.truncateNameOnRight} filters={pref.resourceFilter} @@ -532,7 +547,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, @@ -553,11 +568,11 @@ export class ApplicationDetails extends React.Component )) || ( - -

No resources found

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

No resources found

+
Try to change filter criteria
+
+ )}
)} @@ -566,14 +581,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) ) } @@ -588,7 +603,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} /> @@ -614,7 +629,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:
@@ -667,7 +682,7 @@ export class ApplicationDetails extends React.Component {metadata => ( -
+
SHA:
@@ -701,7 +716,7 @@ export class ApplicationDetails extends React.Component
Message:
-
+
{renderCommitMessage(metadata.message)}
@@ -721,12 +736,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: , @@ -736,7 +751,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', @@ -747,7 +762,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', @@ -762,7 +777,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', @@ -770,7 +785,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: ( {' '} @@ -794,6 +809,17 @@ export class ApplicationDetails extends React.Component, + action: () => this.selectNode(fullName) + }, + { + iconClassName: 'fa fa-times-circle', + title: , + action: () => this.deleteApplication() + } ]; } @@ -838,12 +864,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: 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; @@ -853,7 +879,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') { @@ -880,15 +906,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; @@ -900,7 +926,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; } @@ -930,19 +956,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-resource-tree/application-resource-tree.tsx b/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx index b5426ff1de2bf..6fab01b94bd9b 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 @@ -1,4 +1,4 @@ -import {DropDown, DropDownMenu, NotificationType, Tooltip} from 'argo-ui'; +import { DropDown, DropDownMenu, NotificationType, Tooltip } from 'argo-ui'; import * as classNames from 'classnames'; import * as dagre from 'dagre'; import * as React from 'react'; @@ -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,17 +19,18 @@ import { getAppOverridesCount, HealthStatusIcon, isAppNode, + isApp, isYoungerThanXMinutes, NodeId, nodeKey, PodHealthIcon } 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); } @@ -46,8 +47,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; @@ -61,7 +62,7 @@ export interface ApplicationResourceTreeProps { showCompactNodes: boolean; setShowCompactNodes: (showCompactNodes: boolean) => void; zoom: number; - podGroupCount: number; + // podGroupCount: number; filters?: string[]; setTreeFilterGraph?: (filterGraph: any[]) => void; nameDirection: boolean; @@ -69,6 +70,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; @@ -101,14 +109,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) { @@ -122,7 +130,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; } @@ -143,8 +151,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); } @@ -168,12 +176,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) => { @@ -246,11 +254,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) { @@ -258,7 +266,7 @@ function renderFilteredNode(node: {count: number} & dagre.Node, onClearFilter: ( } return ( -
+
@@ -272,14 +280,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) { @@ -287,18 +295,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' ? ( ))} @@ -324,15 +332,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} @@ -384,8 +392,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; @@ -409,7 +417,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') { @@ -468,7 +476,7 @@ function renderPodGroup(props: ApplicationResourceTreeProps, id: string, node: R })}>
- {!rootNode &&
{ResourceLabel({kind: node.kind})}
} + {!rootNode &&
{ResourceLabel({ kind: node.kind })}
}

{ expandCollapse(node, props); @@ -578,7 +586,7 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any, {pods.length !== 0 && showPodGroupByStatus ? (
- +
@@ -629,13 +637,13 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any, } }} key={pod.metadata.name}> -
+
{isYoungerThanXMinutes(pod, 30) && ( )}
- +
@@ -656,7 +664,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 }); } }, { @@ -683,7 +691,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') { @@ -748,7 +756,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 = { @@ -888,29 +896,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; @@ -935,20 +945,22 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => } }, [props.filters]); - const podCount = nodes.filter(node => node.kind === 'Pod').length; - React.useEffect(() => { - const {podGroupCount, setShowCompactNodes, appContext} = props; - if (podCount > podGroupCount) { - setShowCompactNodes(true); - appContext.apis.notifications.show({ - content: `Since the number of pods has surpassed the threshold pod count of ${podGroupCount}, you will now be switched to the group node view. + if (isApp(props.app)) { + const podCount = nodes.filter(node => node.kind === 'Pod').length; + React.useEffect(() => { + const { podGroupCount, setShowCompactNodes, appContext } = props as ApplicationResourceTreeProps; + if (podCount > podGroupCount) { + setShowCompactNodes(true); + appContext.apis.notifications.show({ + content: `Since the number of pods has surpassed the threshold pod count of ${podGroupCount}, you will now be switched to the group node view. If you prefer the tree view, you can simply click on the Group Nodes toolbar button to deselect the current view.`, - type: NotificationType.Success - }); - } else { - props.setShowCompactNodes(false); - } - }, [podCount]); + type: NotificationType.Success + }); + } else { + props.setShowCompactNodes(false); + } + }, [podCount]); + } function filterGraph(app: models.Application, filteredIndicatorParent: string, graphNodesFilter: dagre.graphlib.Graph, predicate: (node: ResourceTreeNode) => boolean) { const appKey = appNodeKey(app); @@ -970,12 +982,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); @@ -999,7 +1011,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => hiddenNodes.push(child); } } else { - processPodGroup(parent, child, props); + processPodGroup(parent, child, props as ApplicationResourceTreeProps); } }); }); @@ -1027,7 +1039,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(); @@ -1038,11 +1050,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 => { @@ -1054,14 +1066,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)); @@ -1073,7 +1085,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[] = []; @@ -1103,7 +1115,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)) { @@ -1123,7 +1137,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); } @@ -1134,28 +1148,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 => { @@ -1196,7 +1210,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; @@ -1210,14 +1224,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))); @@ -1229,8 +1243,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; @@ -1244,11 +1258,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) => ( -