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 1131d7a8c1..5b9a9df2c6 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 @@ -98,8 +98,8 @@ function filterGraph(app: models.Application, filteredIndicatorParent: string, g } } -function compareNodes(first: models.ResourceNode, second: models.ResourceNode) { - return nodeKey(first).localeCompare(nodeKey(second)); +function compareNodes(first: ResourceTreeNode, second: ResourceTreeNode) { + return `${first.orphaned && '1' || '0'}/${nodeKey(first)}`.localeCompare(`${second.orphaned && '1' || '0'}/${nodeKey(second)}`); } function appNodeKey(app: models.Application) { diff --git a/ui/src/app/settings/components/project-details/project-details.tsx b/ui/src/app/settings/components/project-details/project-details.tsx index 0aa912992c..df92671e4a 100644 --- a/ui/src/app/settings/components/project-details/project-details.tsx +++ b/ui/src/app/settings/components/project-details/project-details.tsx @@ -1,4 +1,4 @@ -import { NotificationsApi, NotificationType, SlidingPanel, Tabs } from 'argo-ui'; +import { NotificationsApi, NotificationType, SlidingPanel, Tabs, Tooltip } from 'argo-ui'; import * as React from 'react'; import { FormApi } from 'react-form'; import { RouteComponentProps } from 'react-router'; @@ -16,6 +16,14 @@ interface ProjectDetailsState { token: string; } +function helpTip(text: string) { + return ( + + + + ); +} + export class ProjectDetails extends React.Component, ProjectDetailsState> { private projectFormApi: FormApi; private projectRoleFormApi: FormApi; @@ -87,6 +95,8 @@ export class ProjectDetails extends React.Component this.projectFormApi = api} submit={async (projParams) => { try { await services.projects.update(projParams); @@ -255,7 +265,7 @@ export class ProjectDetails extends React.Component -

Source repositories

+

Source repositories {helpTip('Git repositories where application manifests are permitted to be retrieved from')}

{(proj.spec.sourceRepos || []).length > 0 && (
@@ -274,7 +284,7 @@ export class ProjectDetails extends React.Component) ||

Project has no source repositories

} -

Destinations

+

Destinations {helpTip('Cluster and namespaces where applications are permitted to be deployed to')}

{(proj.spec.destinations || []).length > 0 && (
@@ -297,7 +307,7 @@ export class ProjectDetails extends React.Component) ||

Project has no destinations

} -

Whitelisted cluster resources

+

Whitelisted cluster resources {helpTip('Cluster-scoped K8s API Groups and Kinds which are permitted to be deployed')}

{(proj.spec.clusterResourceWhitelist || []).length > 0 && (
@@ -320,7 +330,7 @@ export class ProjectDetails extends React.Component) ||

No cluster-scoped resources are permitted to deploy

} -

Blacklisted namespaced resources

+

Blacklisted namespaced resources {helpTip('Namespace-scoped K8s API Groups and Kinds which are prohibited from being deployed')}

{(proj.spec.namespaceResourceBlacklist || []).length > 0 && (
@@ -342,6 +352,25 @@ export class ProjectDetails extends React.Component ))}
) ||

All namespaced-scoped resources are permitted to deploy

} + +

Orphaned Resource Monitoring {helpTip('Enables monitoring of top level resources in the application target namespace')}

+ +
+
+ {proj.spec.orphanedResources && ( +
+
+ WARN +
+
+ {(proj.spec.orphanedResources.warn === undefined || proj.spec.orphanedResources.warn) && 'enabled' || 'disabled'} +
+
+ ) || ( +

Orphan resources monitoring is disabled

+ )} +
+
); } diff --git a/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx b/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx index 3f6720e360..e1c4cc9ec3 100644 --- a/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx +++ b/ui/src/app/settings/components/project-edit-panel/project-edit-panel.tsx @@ -2,7 +2,7 @@ import {FormField, FormSelect} from 'argo-ui'; import * as React from 'react'; import {Form, FormApi, Text} from 'react-form'; -import {AutocompleteField, clusterTitle, DataLoader} from '../../../shared/components'; +import {AutocompleteField, CheckboxField, clusterTitle, DataLoader} from '../../../shared/components'; import * as models from '../../../shared/models'; import {ProjectParams, services} from '../../../shared/services'; @@ -156,6 +156,15 @@ export const ProjectEditPanel = (props: { blacklist new namespaced resource + + +

Orphaned Resource Monitoring

+
Enables monitoring of top level resources in the application target namespace
+ + {api.values.orphanedResourcesEnabled && ( + + )} +
)} diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts index 7fa748c455..6bdbf601df 100644 --- a/ui/src/app/shared/models.ts +++ b/ui/src/app/shared/models.ts @@ -499,6 +499,7 @@ export interface ProjectSpec { roles: ProjectRole[]; clusterResourceWhitelist: GroupKind[]; namespaceResourceBlacklist: GroupKind[]; + orphanedResources?: { warn?: boolean }; } export interface Project { diff --git a/ui/src/app/shared/services/projects-service.ts b/ui/src/app/shared/services/projects-service.ts index 6a3b95c810..189b51b3b7 100644 --- a/ui/src/app/shared/services/projects-service.ts +++ b/ui/src/app/shared/services/projects-service.ts @@ -10,6 +10,8 @@ export interface ProjectParams { roles: models.ProjectRole[]; clusterResourceWhitelist: models.GroupKind[]; namespaceResourceBlacklist: models.GroupKind[]; + orphanedResourcesEnabled: boolean; + orphanedResourcesWarn: boolean; } export interface CreateJWTTokenParams { @@ -67,6 +69,7 @@ function paramsToProj(params: ProjectParams) { roles: params.roles, clusterResourceWhitelist: params.clusterResourceWhitelist, namespaceResourceBlacklist: params.namespaceResourceBlacklist, + orphanedResources: params.orphanedResourcesEnabled && { warn: !!params.orphanedResourcesWarn } || null, }, }; }