diff --git a/cmd/argocd/commands/app_resources.go b/cmd/argocd/commands/app_resources.go index b1aa81947208..d58808f96f24 100644 --- a/cmd/argocd/commands/app_resources.go +++ b/cmd/argocd/commands/app_resources.go @@ -108,8 +108,8 @@ func NewApplicationDeleteResourceCommand(clientOpts *argocdclient.ClientOptions) errors.CheckError(err) command.Flags().StringVar(&group, "group", "", "Group") command.Flags().StringVar(&namespace, "namespace", "", "Namespace") - command.Flags().BoolVar(&force, "force", false, "Indicates whether to orphan the dependents of the deleted resource") - command.Flags().BoolVar(&orphan, "orphan", false, "Indicates whether to force delete the resource") + command.Flags().BoolVar(&force, "force", false, "Indicates whether to force delete the resource") + command.Flags().BoolVar(&orphan, "orphan", false, "Indicates whether to orphan the dependents of the deleted resource") command.Flags().BoolVar(&all, "all", false, "Indicates whether to patch multiple matching of resources") command.Flags().StringVar(&project, "project", "", `The name of the application's project - specifying this allows the command to report "not found" instead of "permission denied" if the app does not exist`) command.Run = func(c *cobra.Command, args []string) { diff --git a/docs/user-guide/commands/argocd_app_delete-resource.md b/docs/user-guide/commands/argocd_app_delete-resource.md index 4a305eb4b448..e397c0c019fa 100644 --- a/docs/user-guide/commands/argocd_app_delete-resource.md +++ b/docs/user-guide/commands/argocd_app_delete-resource.md @@ -12,12 +12,12 @@ argocd app delete-resource APPNAME [flags] ``` --all Indicates whether to patch multiple matching of resources - --force Indicates whether to orphan the dependents of the deleted resource + --force Indicates whether to force delete the resource --group string Group -h, --help help for delete-resource --kind string Kind --namespace string Namespace - --orphan Indicates whether to force delete the resource + --orphan Indicates whether to orphan the dependents of the deleted resource --project string The name of the application's project - specifying this allows the command to report "not found" instead of "permission denied" if the app does not exist --resource-name string Name of resource ``` 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 a3e8175591dd..790919f5c271 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -573,16 +573,12 @@ export class ApplicationDetails extends React.Component {data => ( this.selectNode(fullName)} resources={data} nodeMenu={node => - AppUtils.renderResourceMenu( - {...node, root: node}, - application, - tree, - this.appContext.apis, - this.appChanged, - () => this.getApplicationActionMenu(application, false) + AppUtils.renderResourceMenu(node, application, tree, this.appContext.apis, this.appChanged, () => + this.getApplicationActionMenu(application, false) ) } tree={tree} @@ -608,10 +604,11 @@ export class ApplicationDetails extends React.Component {data => ( this.selectNode(fullName)} resources={data} nodeMenu={node => - AppUtils.renderResourceMenu({...node, root: node}, application, tree, this.appContext.apis, this.appChanged, () => + AppUtils.renderResourceMenu(node, application, tree, this.appContext.apis, this.appChanged, () => this.getApplicationActionMenu(application, false) ) } 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 0d22e1e51686..6fc06abe9a15 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 @@ -4,14 +4,16 @@ import * as classNames from 'classnames'; import * as models from '../../../shared/models'; import {ResourceIcon} from '../resource-icon'; import {ResourceLabel} from '../resource-label'; -import {ComparisonStatusIcon, HealthStatusIcon, nodeKey, createdOrNodeKey} from '../utils'; +import {ComparisonStatusIcon, HealthStatusIcon, nodeKey, createdOrNodeKey, isSameNode} from '../utils'; +import {AppDetailsPreferences} from '../../../shared/services'; import {Consumer} from '../../../shared/context'; import Moment from 'react-moment'; import {format} from 'date-fns'; -import {ResourceNode, ResourceRef} from '../../../shared/models'; +import {ResourceNode} from '../../../shared/models'; import './application-resource-list.scss'; export interface ApplicationResourceListProps { + pref: AppDetailsPreferences; resources: models.ResourceStatus[]; onNodeClick?: (fullName: string) => any; nodeMenu?: (node: models.ResourceNode) => React.ReactNode; @@ -19,29 +21,25 @@ export interface ApplicationResourceListProps { } export const ApplicationResourceList = (props: ApplicationResourceListProps) => { - function getResNode(nodes: ResourceNode[], nodeId: string): models.ResourceNode { - for (const node of nodes) { - if (nodeKey(node) === nodeId) { - return node; - } - } - return null; - } - const parentNode = ((props.resources || []).length > 0 && (getResNode(props.tree.nodes, nodeKey(props.resources[0])) as ResourceNode)?.parentRefs?.[0]) || ({} as ResourceRef); - const searchParams = new URLSearchParams(window.location.search); - const view = searchParams.get('view'); + const nodeByKey = new Map(); + props.tree?.nodes?.forEach(res => nodeByKey.set(nodeKey(res), res)); + + const firstParentNode = props.resources.length > 0 && (nodeByKey.get(nodeKey(props.resources[0])) as ResourceNode)?.parentRefs?.[0]; + const isSameParent = firstParentNode && props.resources?.every(x => (nodeByKey.get(nodeKey(x)) as ResourceNode)?.parentRefs?.every(p => isSameNode(p, firstParentNode))); + const isSameKind = props.resources?.every(x => x.group === props.resources[0].group && x.kind === props.resources[0].kind); + const view = props.pref.view; const ParentRefDetails = () => { - return Object.keys(parentNode).length > 0 ? ( + return isSameParent ? (
Parent Node Info
Name:
-
{parentNode?.name}
+
{firstParentNode.name}
Kind:
-
{parentNode?.kind}
+
{firstParentNode.kind}
) : ( @@ -49,123 +47,114 @@ export const ApplicationResourceList = (props: ApplicationResourceListProps) => ); }; return ( -
- {/* Display only when the view is set to or network */} - {(view === 'tree' || view === 'network') && ( -
- -
- )} -
-
-
-
-
NAME
-
GROUP/KIND
-
SYNC ORDER
-
NAMESPACE
- {(parentNode.kind === 'Rollout' || parentNode.kind === 'Deployment') &&
REVISION
} -
CREATED AT
-
STATUS
+ props.resources.length > 0 && ( +
+ {/* Display only when the view is set to or network */} + {(view === 'tree' || view === 'network') && ( +
+
-
- {props.resources - .sort((first, second) => -createdOrNodeKey(first).localeCompare(createdOrNodeKey(second))) - .map(res => ( -
props.onNodeClick && props.onNodeClick(nodeKey(res))}> -
-
-
- -
-
{ResourceLabel({kind: res.kind})}
+ )} +
+
+
+
+
NAME
+
GROUP/KIND
+
SYNC ORDER
+
NAMESPACE
+ {isSameKind && props.resources[0].kind === 'ReplicaSet' &&
REVISION
} +
CREATED AT
+
STATUS
+
+
+ {props.resources + .sort((first, second) => -createdOrNodeKey(first).localeCompare(createdOrNodeKey(second))) + .map(res => ( +
props.onNodeClick && props.onNodeClick(nodeKey(res))}> +
+
+
+ +
+
{ResourceLabel({kind: res.kind})}
+
-
-
- {res.name} - {res.kind === 'Application' && ( - - {ctx => ( - - e.stopPropagation()} - title='Open application'> - - - - )} - - )} -
-
{[res.group, res.kind].filter(item => !!item).join('/')}
-
{res.syncWave || '-'}
-
{res.namespace}
- {res.kind === 'ReplicaSet' && - ((getResNode(props.tree.nodes, nodeKey(res)) as ResourceNode).info || []) - .filter(tag => !tag.name.includes('Node')) - .slice(0, 4) - .map((tag, i) => { - return ( -
- {tag?.value?.split(':')[1] || '-'} -
- ); - })} +
+ {res.name} + {res.kind === 'Application' && ( + + {ctx => ( + + e.stopPropagation()} + title='Open application'> + + + + )} + + )} +
+
{[res.group, res.kind].filter(item => !!item).join('/')}
+
{res.syncWave || '-'}
+
{res.namespace}
+ {isSameKind && + res.kind === 'ReplicaSet' && + ((nodeByKey.get(nodeKey(res)) as ResourceNode).info || []) + .filter(tag => !tag.name.includes('Node')) + .slice(0, 4) + .map((tag, i) => { + return ( +
+ {tag?.value?.split(':')[1] || '-'} +
+ ); + })} -
- {res.createdAt && ( - - - {res.createdAt} - -  ago   {format(new Date(res.createdAt), 'MM/dd/yy')} - - )} -
-
- {res.health && ( - - {res.health.status}   - - )} - {res.status && } - {res.hook && } - {props.nodeMenu && ( -
- ( - - )}> - {() => - props.nodeMenu({ - name: res.name, - version: res.version, - kind: res.kind, - namespace: res.namespace, - group: res.group, - info: null, - uid: '', - resourceVersion: null, - parentRefs: [] - }) - } - -
- )} +
+ {res.createdAt && ( + + + {res.createdAt} + +  ago   {format(new Date(res.createdAt), 'MM/dd/yy')} + + )} +
+
+ {res.health && ( + + {res.health.status}   + + )} + {res.status && } + {res.hook && } + {props.nodeMenu && ( +
+ ( + + )}> + {() => props.nodeMenu(nodeByKey.get(nodeKey(res)))} + +
+ )} +
-
- ))} + ))} +
-
+ ) ); }; 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 a0d29c2c804f..caba162b82eb 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 @@ -11,7 +11,7 @@ import {PodViewPreferences, services, ViewPreferences} from '../../../shared/ser import {ResourceTreeNode} from '../application-resource-tree/application-resource-tree'; import {ResourceIcon} from '../resource-icon'; import {ResourceLabel} from '../resource-label'; -import {ComparisonStatusIcon, isYoungerThanXMinutes, HealthStatusIcon, nodeKey, PodHealthIcon, deletePodAction} from '../utils'; +import {ComparisonStatusIcon, isYoungerThanXMinutes, HealthStatusIcon, nodeKey, PodHealthIcon} from '../utils'; import './pod-view.scss'; import {PodTooltip} from './pod-tooltip'; @@ -157,83 +157,43 @@ export class PodView extends React.Component { )}
- {group.pods.map(pod => ( - ( - } - popperOptions={{ - modifiers: { - preventOverflow: { - enabled: true - }, - hide: { - enabled: false - }, - flip: { - enabled: false - } - } - }} - key={pod.metadata.name}> -
- {isYoungerThanXMinutes(pod, 30) && ( - - )} -
- -
-
-
- )} - items={[ - { - title: ( - - Info - - ), - action: () => this.props.onItemClick(pod.fullName) - }, - { - title: ( - - Logs - - ), - action: () => { - this.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'logs'}, {replace: true}); - } - }, - { - title: ( - - Exec - - ), - action: () => { - this.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'exec'}, {replace: true}); - } - }, - { - title: ( - - Delete - - ), - action: () => { - deletePodAction( - pod, - this.appContext, - this.props.app.metadata.name, - this.props.app.metadata.namespace - ); - } - } - ]} - /> - ))} + {group.pods.map( + pod => + this.props.nodeMenu && ( + ( + } + popperOptions={{ + modifiers: { + preventOverflow: { + enabled: true + }, + hide: { + enabled: false + }, + flip: { + enabled: false + } + } + }} + key={pod.metadata.name}> +
+ {isYoungerThanXMinutes(pod, 30) && ( + + )} +
+ +
+
+
+ )}> + {() => this.props.nodeMenu(pod)} +
+ ) + )}
PODS
{(podPrefs.sortMode === 'parentResource' || podPrefs.sortMode === 'topLevelResource') && ( 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 cb8fd8f43ecb..0e1cfb9a0078 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, Tooltip} from 'argo-ui'; +import {DropDown, Tooltip} from 'argo-ui'; import * as classNames from 'classnames'; import * as dagre from 'dagre'; import * as React from 'react'; @@ -15,7 +15,6 @@ import {ResourceLabel} from '../resource-label'; import { BASE_COLORS, ComparisonStatusIcon, - deletePodAction, getAppOverridesCount, HealthStatusIcon, isAppNode, @@ -592,83 +591,58 @@ function renderPodGroupByStatus(props: ApplicationResourceTreeProps, node: any,
) : ( - pods.map(pod => ( - ( - - {pod.metadata.name} -
Health: {pod.health}
- {pod.createdAt && ( - - Created: - - {pod.createdAt} - - ago ({{pod.createdAt}}) - - )} -
- } - popperOptions={{ - modifiers: { - preventOverflow: { - enabled: true - }, - hide: { - enabled: false - }, - flip: { - enabled: false + pods.map( + pod => + props.nodeMenu && ( + ( + + {pod.metadata.name} +
Health: {pod.health}
+ {pod.createdAt && ( + + Created: + + {pod.createdAt} + + ago ({{pod.createdAt}}) + + )} +
} - } - }} - key={pod.metadata.name}> -
- {isYoungerThanXMinutes(pod, 30) && ( - - )} -
- -
-
- - )} - items={[ - { - title: ( - - Info - - ), - action: () => props.onNodeClick(pod.fullName) - }, - { - title: ( - - Logs - - ), - action: () => { - props.appContext.apis.navigation.goto('.', {node: pod.fullName, tab: 'logs'}, {replace: true}); - } - }, - { - title: ( - - Delete - - ), - action: () => { - deletePodAction(pod, props.appContext, props.app.metadata.name, props.app.metadata.namespace); - } - } - ]} - /> - )) + popperOptions={{ + modifiers: { + preventOverflow: { + enabled: true + }, + hide: { + enabled: false + }, + flip: { + enabled: false + } + } + }} + key={pod.metadata.name}> +
+ {isYoungerThanXMinutes(pod, 30) && ( + + )} +
+ +
+
+ + )}> + {() => props.nodeMenu(pod)} + + ) + ) )}
); diff --git a/ui/src/app/applications/components/applications-list/applications-table.tsx b/ui/src/app/applications/components/applications-list/applications-table.tsx index a34ea5d4d219..a024059e16e5 100644 --- a/ui/src/app/applications/components/applications-list/applications-table.tsx +++ b/ui/src/app/applications/components/applications-list/applications-table.tsx @@ -140,9 +140,21 @@ export const ApplicationsTable = (props: { )} items={[ - {title: 'Sync', action: () => props.syncApplication(app.metadata.name, app.metadata.namespace)}, - {title: 'Refresh', action: () => props.refreshApplication(app.metadata.name, app.metadata.namespace)}, - {title: 'Delete', action: () => props.deleteApplication(app.metadata.name, app.metadata.namespace)} + { + title: 'Sync', + iconClassName: 'fa fa-fw fa-sync', + action: () => props.syncApplication(app.metadata.name, app.metadata.namespace) + }, + { + title: 'Refresh', + iconClassName: 'fa fa-fw fa-redo', + action: () => props.refreshApplication(app.metadata.name, app.metadata.namespace) + }, + { + title: 'Delete', + iconClassName: 'fa fa-fw fa-times-circle', + action: () => props.deleteApplication(app.metadata.name, app.metadata.namespace) + } ]} />
diff --git a/ui/src/app/applications/components/resource-details/resource-details.tsx b/ui/src/app/applications/components/resource-details/resource-details.tsx index d20285878a53..580bea554344 100644 --- a/ui/src/app/applications/components/resource-details/resource-details.tsx +++ b/ui/src/app/applications/components/resource-details/resource-details.tsx @@ -303,7 +303,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { SYNC