Skip to content

Commit 2c3acb3

Browse files
authored
Merge pull request #105 from aali309/newDetailsPage
Show an Argo CD Application Set as Details page in Dev Console
2 parents c63f4f7 + d1273e8 commit 2c3acb3

30 files changed

+1896
-115
lines changed

console-extensions.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,5 +406,49 @@
406406
"$codeRef": "yamlApplicationTemplates.defaultApplicationSetYamlTemplate"
407407
}
408408
}
409+
},
410+
{
411+
"type": "console.page/resource/details",
412+
"flags": {
413+
"required": [
414+
"APPLICATIONSET"
415+
]
416+
},
417+
"properties": {
418+
"model": {
419+
"group": "argoproj.io",
420+
"kind": "ApplicationSet",
421+
"version": "v1alpha1"
422+
},
423+
"component": {
424+
"$codeRef": "ApplicationSetDetailsPage"
425+
}
426+
}
427+
},
428+
{
429+
"type": "console.page/resource/search",
430+
"properties": {
431+
"model": {
432+
"group": "argoproj.io",
433+
"version": "v1alpha1",
434+
"kind": "ApplicationSet"
435+
},
436+
"component": {
437+
"$codeRef": "ApplicationSetList"
438+
}
439+
}
440+
},
441+
{
442+
"type": "console.page/resource/search",
443+
"properties": {
444+
"model": {
445+
"group": "argoproj.io",
446+
"version": "v1alpha1",
447+
"kind": "Application"
448+
},
449+
"component": {
450+
"$codeRef": "ApplicationList"
451+
}
452+
}
409453
}
410454
]

plugin-metadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const metadata: ConsolePluginBuildMetadata = {
1616
ApplicationList: "./gitops/components/application/ApplicationListTab.tsx",
1717
ApplicationDetails: "./gitops/components/application/ApplicationNavPage.tsx",
1818
ApplicationSetList: "./gitops/components/application/ApplicationSetListTab.tsx",
19+
ApplicationSetDetailsPage: "./gitops/components/appset/ApplicationSetDetailsPage.tsx",
1920
yamlApplicationTemplates: "./gitops/components/application/templates/index.ts"
2021
}
2122
};

src/components/topology/console/PodsOverview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { Grid, GridItem } from '@patternfly/react-core';
43
import { Link } from 'react-router-dom-v5-compat';
54
import * as _ from 'lodash';
65

76
import { PodPhase, ResourceLink, StatusComponent } from '@openshift-console/dynamic-plugin-sdk';
87
import { ExtPodKind } from '@openshift-console/dynamic-plugin-sdk-internal/lib/extensions/console-types';
8+
import { Grid, GridItem } from '@patternfly/react-core';
99

1010
import { PodTraffic } from './pod-traffic';
1111
import { ContainerStatus } from './types';

src/components/topology/sidebar/DeploymentSideBarDetails.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ type DeploymentSideBarDetailsProps = {
205205

206206
export const DeploymentSideBarDetails: React.FC<DeploymentSideBarDetailsProps> = ({
207207
rollout: d,
208-
rolloutKind: rolloutKind,
209208
}) => {
210209
const { t } = useTranslation();
211210
const model = getK8sModel(d);

src/components/utils/gitops-utils.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export const getApplicationsListBaseURI = () => {
4141
export class RetryError extends Error {}
4242

4343
export class TimeoutError extends Error {
44-
constructor(url: any, ms: any, ...params: any[]) {
45-
super(`Call to ${url} timed out after ${ms}ms.`); //å ...params);
44+
constructor(url: any, ms: any) {
45+
super(`Call to ${url} timed out after ${ms}ms.`);
4646
// Dumb hack to fix `instanceof TimeoutError`
4747
Object.setPrototypeOf(this, TimeoutError.prototype);
4848
}
@@ -64,7 +64,7 @@ const getCSRFToken = () =>
6464
.map((c) => c.slice(cookiePrefix.length))
6565
.pop();
6666

67-
export const validateStatus = async (response: Response, url, method, retry) => {
67+
export const validateStatus = async (response: Response, url, method) => {
6868
console.log('VALIDATE STATUS - RESPONSE STATUS IS ' + response.status);
6969
console.log('VALIDATE STATUS - RESPONSE TEXT IS ' + response.text);
7070
console.log('VALIDATE STATUS - RESPONSE BODY IS ' + response.body);
@@ -101,7 +101,6 @@ export const validateStatus = async (response: Response, url, method, retry) =>
101101
// retry 409 conflict errors due to ClustResourceQuota / ResourceQuota
102102
// https://bugzilla.redhat.com/show_bug.cgi?id=1920699
103103
if (
104-
retry &&
105104
method === 'POST' &&
106105
response.status === 409 &&
107106
['resourcequotas', 'clusterresourcequotas'].includes(json.details?.kind)
@@ -129,7 +128,7 @@ export const validateStatus = async (response: Response, url, method, retry) =>
129128
});
130129
};
131130

132-
export const coFetchInternal = async (url, options, timeout, retry) => {
131+
export const coFetchInternal = async (url, options, timeout) => {
133132
const allOptions = _.defaultsDeep({}, initDefaults, options);
134133
if (allOptions.method !== 'GET') {
135134
allOptions.headers['X-CSRFToken'] = getCSRFToken();
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
.application-set-details-page {
2+
&__grid {
3+
display: grid;
4+
grid-template-columns: 1fr;
5+
gap: 20px;
6+
}
7+
8+
&__grid-item {
9+
background: #212427;
10+
border: 1px solid #393F44;
11+
border-radius: 8px;
12+
padding: 20px;
13+
}
14+
15+
&__header {
16+
margin-bottom: 20px;
17+
}
18+
19+
&__header-title {
20+
font-size: 18px;
21+
font-weight: 600;
22+
color: #ffffff;
23+
margin: 0;
24+
}
25+
26+
&__content {
27+
color: #ffffff;
28+
}
29+
30+
&__conditions {
31+
margin-top: 20px;
32+
}
33+
34+
&__conditions-title {
35+
font-size: 16px;
36+
font-weight: 600;
37+
color: #ffffff;
38+
margin-bottom: 12px;
39+
}
40+
41+
&__conditions-table {
42+
border: 1px solid #393F44;
43+
border-radius: 6px;
44+
overflow: hidden;
45+
}
46+
47+
&__conditions-table-header {
48+
display: grid;
49+
grid-template-columns: 1fr 1fr 1fr 1fr 2fr;
50+
background: #1a1d21;
51+
border-bottom: 1px solid #393F44;
52+
}
53+
54+
&__conditions-table-header-cell {
55+
padding: 12px;
56+
font-weight: 600;
57+
color: #ffffff;
58+
font-size: 14px;
59+
60+
&--type {
61+
grid-column: 1;
62+
}
63+
64+
&--status {
65+
grid-column: 2;
66+
}
67+
68+
&--updated {
69+
grid-column: 3;
70+
}
71+
72+
&--reason {
73+
grid-column: 4;
74+
}
75+
76+
&--message {
77+
grid-column: 5;
78+
}
79+
}
80+
81+
&__conditions-table-row {
82+
display: grid;
83+
grid-template-columns: 1fr 1fr 1fr 1fr 2fr;
84+
border-bottom: 1px solid #393F44;
85+
86+
&:last-child {
87+
border-bottom: none;
88+
}
89+
}
90+
91+
&__conditions-table-row-cell {
92+
padding: 12px;
93+
color: #ffffff;
94+
font-size: 14px;
95+
96+
&--type {
97+
grid-column: 1;
98+
font-weight: 500;
99+
}
100+
101+
&--status {
102+
grid-column: 2;
103+
}
104+
105+
&--updated {
106+
grid-column: 3;
107+
}
108+
109+
&--reason {
110+
grid-column: 4;
111+
}
112+
113+
&--message {
114+
grid-column: 5;
115+
}
116+
}
117+
}
118+
119+
// Force dashed border styling for labels in ApplicationSet details
120+
.pf-c-description-list__description .co-label-group {
121+
border: 1px dashed var(--pf-v6-global--BorderColor--200) !important;
122+
border-radius: var(--pf-v6-global--BorderRadius--sm) !important;
123+
padding: var(--pf-v6-global--spacer--sm) !important;
124+
min-height: var(--pf-v6-global--spacer--lg) !important;
125+
display: flex !important;
126+
align-items: center !important;
127+
width: fit-content !important;
128+
max-width: 100% !important;
129+
}
130+
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import * as React from 'react';
2+
import { RouteComponentProps } from 'react-router';
3+
4+
import { ResourceLink, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
5+
import { Badge, DescriptionList, Flex, FlexItem, PageSection, Title } from '@patternfly/react-core';
6+
7+
import { ApplicationKind, ApplicationModel } from '../../models/ApplicationModel';
8+
import { ApplicationSetKind, ApplicationSetModel } from '../../models/ApplicationSetModel';
9+
import HealthStatus from '../../Statuses/HealthStatus';
10+
import { Conditions } from '../../utils/components/Conditions/Conditions';
11+
import { getAppSetGeneratorCount, getAppSetStatus } from '../../utils/gitops';
12+
import BaseDetailsSummary, {
13+
DetailsDescriptionGroup,
14+
} from '../shared/BaseDetailsSummary/BaseDetailsSummary';
15+
16+
import './AppSetDetailsTab.scss';
17+
18+
type AppSetDetailsTabProps = RouteComponentProps<{ ns: string; name: string }> & {
19+
obj?: ApplicationSetKind;
20+
};
21+
22+
const AppSetDetailsTab: React.FC<AppSetDetailsTabProps> = ({ obj }) => {
23+
const namespace = obj?.metadata?.namespace;
24+
25+
// Get applications to count generated apps
26+
const [applications] = useK8sWatchResource<ApplicationKind[]>({
27+
groupVersionKind: {
28+
group: ApplicationModel.apiGroup,
29+
version: ApplicationModel.apiVersion,
30+
kind: ApplicationModel.kind,
31+
},
32+
namespace: namespace || obj?.metadata?.namespace,
33+
isList: true,
34+
});
35+
36+
if (!obj) return null;
37+
38+
const status = obj.status || {};
39+
const spec = obj.spec || {};
40+
const totalGenerators = getAppSetGeneratorCount(obj);
41+
const appSetStatus = getAppSetStatus(obj);
42+
43+
// Count applications owned by this ApplicationSet
44+
const generatedAppsCount =
45+
applications?.filter((app) =>
46+
app.metadata?.ownerReferences?.some(
47+
(owner) => owner.kind === obj.kind && owner.name === obj.metadata?.name,
48+
),
49+
).length || 0;
50+
51+
return (
52+
<>
53+
<PageSection>
54+
<Title headingLevel="h2" className="co-section-heading">
55+
Argo CD ApplicationSet details
56+
</Title>
57+
<Flex
58+
justifyContent={{ default: 'justifyContentSpaceEvenly' }}
59+
direction={{ default: 'column', lg: 'row' }}
60+
>
61+
<Flex flex={{ default: 'flex_2' }}>
62+
<FlexItem>
63+
<BaseDetailsSummary obj={obj} model={ApplicationSetModel} />
64+
</FlexItem>
65+
</Flex>
66+
<Flex flex={{ default: 'flex_2' }} direction={{ default: 'column' }}>
67+
<FlexItem>
68+
<DescriptionList className="pf-c-description-list">
69+
<DetailsDescriptionGroup
70+
title="Status"
71+
help="Current health status of the ApplicationSet."
72+
>
73+
<HealthStatus status={appSetStatus} />
74+
</DetailsDescriptionGroup>
75+
76+
<DetailsDescriptionGroup
77+
title="Generated Apps"
78+
help="Number of applications generated by this ApplicationSet."
79+
>
80+
<Badge isRead color="blue">
81+
{generatedAppsCount} application{generatedAppsCount !== 1 ? 's' : ''}
82+
</Badge>
83+
</DetailsDescriptionGroup>
84+
85+
<DetailsDescriptionGroup
86+
title="Generators"
87+
help="Number of generators configured in this ApplicationSet."
88+
>
89+
<Badge isRead color="grey">
90+
{totalGenerators} generator{totalGenerators !== 1 ? 's' : ''}
91+
</Badge>
92+
</DetailsDescriptionGroup>
93+
94+
<DetailsDescriptionGroup
95+
title="App Project"
96+
help="Argo CD project that this ApplicationSet belongs to."
97+
>
98+
<ResourceLink
99+
namespace={obj?.metadata?.namespace}
100+
groupVersionKind={{
101+
group: 'argoproj.io',
102+
version: 'v1alpha1',
103+
kind: 'AppProject',
104+
}}
105+
name={spec.template?.spec?.project || 'default'}
106+
/>
107+
</DetailsDescriptionGroup>
108+
109+
{spec.template?.spec?.source?.repoURL && (
110+
<DetailsDescriptionGroup
111+
title="Repository"
112+
help="Git repository URL where the ApplicationSet configuration is stored."
113+
>
114+
<a
115+
href={spec.template.spec.source.repoURL}
116+
target="_blank"
117+
rel="noopener noreferrer"
118+
>
119+
{spec.template.spec.source.repoURL}
120+
</a>
121+
</DetailsDescriptionGroup>
122+
)}
123+
</DescriptionList>
124+
</FlexItem>
125+
</Flex>
126+
</Flex>
127+
</PageSection>
128+
129+
<PageSection>
130+
<Title headingLevel="h2" className="co-section-heading">
131+
Conditions
132+
</Title>
133+
<Conditions conditions={status.conditions} />
134+
</PageSection>
135+
</>
136+
);
137+
};
138+
139+
export default AppSetDetailsTab;

0 commit comments

Comments
 (0)