Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions console-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -406,5 +406,49 @@
"$codeRef": "yamlApplicationTemplates.defaultApplicationSetYamlTemplate"
}
}
},
{
"type": "console.page/resource/details",
"flags": {
"required": [
"APPLICATIONSET"
]
},
"properties": {
"model": {
"group": "argoproj.io",
"kind": "ApplicationSet",
"version": "v1alpha1"
},
"component": {
"$codeRef": "ApplicationSetDetailsPage"
}
}
},
{
"type": "console.page/resource/search",
"properties": {
"model": {
"group": "argoproj.io",
"version": "v1alpha1",
"kind": "ApplicationSet"
},
"component": {
"$codeRef": "ApplicationSetList"
}
}
},
{
"type": "console.page/resource/search",
"properties": {
"model": {
"group": "argoproj.io",
"version": "v1alpha1",
"kind": "Application"
},
"component": {
"$codeRef": "ApplicationList"
}
}
}
]
1 change: 1 addition & 0 deletions plugin-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const metadata: ConsolePluginBuildMetadata = {
ApplicationList: "./gitops/components/application/ApplicationListTab.tsx",
ApplicationDetails: "./gitops/components/application/ApplicationNavPage.tsx",
ApplicationSetList: "./gitops/components/application/ApplicationSetListTab.tsx",
ApplicationSetDetailsPage: "./gitops/components/appset/ApplicationSetDetailsPage.tsx",
yamlApplicationTemplates: "./gitops/components/application/templates/index.ts"
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/topology/console/PodsOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Grid, GridItem } from '@patternfly/react-core';
import { Link } from 'react-router-dom-v5-compat';
import * as _ from 'lodash';

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

import { PodTraffic } from './pod-traffic';
import { ContainerStatus } from './types';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ type DeploymentSideBarDetailsProps = {

export const DeploymentSideBarDetails: React.FC<DeploymentSideBarDetailsProps> = ({
rollout: d,
rolloutKind: rolloutKind,
}) => {
const { t } = useTranslation();
const model = getK8sModel(d);
Expand Down
9 changes: 4 additions & 5 deletions src/components/utils/gitops-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export const getApplicationsListBaseURI = () => {
export class RetryError extends Error {}

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

export const validateStatus = async (response: Response, url, method, retry) => {
export const validateStatus = async (response: Response, url, method) => {
console.log('VALIDATE STATUS - RESPONSE STATUS IS ' + response.status);
console.log('VALIDATE STATUS - RESPONSE TEXT IS ' + response.text);
console.log('VALIDATE STATUS - RESPONSE BODY IS ' + response.body);
Expand Down Expand Up @@ -101,7 +101,6 @@ export const validateStatus = async (response: Response, url, method, retry) =>
// retry 409 conflict errors due to ClustResourceQuota / ResourceQuota
// https://bugzilla.redhat.com/show_bug.cgi?id=1920699
if (
retry &&
method === 'POST' &&
response.status === 409 &&
['resourcequotas', 'clusterresourcequotas'].includes(json.details?.kind)
Expand Down Expand Up @@ -129,7 +128,7 @@ export const validateStatus = async (response: Response, url, method, retry) =>
});
};

export const coFetchInternal = async (url, options, timeout, retry) => {
export const coFetchInternal = async (url, options, timeout) => {
const allOptions = _.defaultsDeep({}, initDefaults, options);
if (allOptions.method !== 'GET') {
allOptions.headers['X-CSRFToken'] = getCSRFToken();
Expand Down
130 changes: 130 additions & 0 deletions src/gitops/components/appset/AppSetDetailsTab.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
.application-set-details-page {
&__grid {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}

&__grid-item {
background: #212427;
border: 1px solid #393F44;
border-radius: 8px;
padding: 20px;
}

&__header {
margin-bottom: 20px;
}

&__header-title {
font-size: 18px;
font-weight: 600;
color: #ffffff;
margin: 0;
}

&__content {
color: #ffffff;
}

&__conditions {
margin-top: 20px;
}

&__conditions-title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
margin-bottom: 12px;
}

&__conditions-table {
border: 1px solid #393F44;
border-radius: 6px;
overflow: hidden;
}

&__conditions-table-header {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 2fr;
background: #1a1d21;
border-bottom: 1px solid #393F44;
}

&__conditions-table-header-cell {
padding: 12px;
font-weight: 600;
color: #ffffff;
font-size: 14px;

&--type {
grid-column: 1;
}

&--status {
grid-column: 2;
}

&--updated {
grid-column: 3;
}

&--reason {
grid-column: 4;
}

&--message {
grid-column: 5;
}
}

&__conditions-table-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 2fr;
border-bottom: 1px solid #393F44;

&:last-child {
border-bottom: none;
}
}

&__conditions-table-row-cell {
padding: 12px;
color: #ffffff;
font-size: 14px;

&--type {
grid-column: 1;
font-weight: 500;
}

&--status {
grid-column: 2;
}

&--updated {
grid-column: 3;
}

&--reason {
grid-column: 4;
}

&--message {
grid-column: 5;
}
}
}

// Force dashed border styling for labels in ApplicationSet details
.pf-c-description-list__description .co-label-group {
border: 1px dashed var(--pf-v6-global--BorderColor--200) !important;
border-radius: var(--pf-v6-global--BorderRadius--sm) !important;
padding: var(--pf-v6-global--spacer--sm) !important;
min-height: var(--pf-v6-global--spacer--lg) !important;
display: flex !important;
align-items: center !important;
width: fit-content !important;
max-width: 100% !important;
}

139 changes: 139 additions & 0 deletions src/gitops/components/appset/AppSetDetailsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as React from 'react';
import { RouteComponentProps } from 'react-router';

import { ResourceLink, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { Badge, DescriptionList, Flex, FlexItem, PageSection, Title } from '@patternfly/react-core';

import { ApplicationKind, ApplicationModel } from '../../models/ApplicationModel';
import { ApplicationSetKind, ApplicationSetModel } from '../../models/ApplicationSetModel';
import HealthStatus from '../../Statuses/HealthStatus';
import { Conditions } from '../../utils/components/Conditions/Conditions';
import { getAppSetGeneratorCount, getAppSetStatus } from '../../utils/gitops';
import BaseDetailsSummary, {
DetailsDescriptionGroup,
} from '../shared/BaseDetailsSummary/BaseDetailsSummary';

import './AppSetDetailsTab.scss';

type AppSetDetailsTabProps = RouteComponentProps<{ ns: string; name: string }> & {
obj?: ApplicationSetKind;
};

const AppSetDetailsTab: React.FC<AppSetDetailsTabProps> = ({ obj }) => {
const namespace = obj?.metadata?.namespace;

// Get applications to count generated apps
const [applications] = useK8sWatchResource<ApplicationKind[]>({
groupVersionKind: {
group: ApplicationModel.apiGroup,
version: ApplicationModel.apiVersion,
kind: ApplicationModel.kind,
},
namespace: namespace || obj?.metadata?.namespace,
isList: true,
});

if (!obj) return null;

const status = obj.status || {};
const spec = obj.spec || {};
const totalGenerators = getAppSetGeneratorCount(obj);
const appSetStatus = getAppSetStatus(obj);

// Count applications owned by this ApplicationSet
const generatedAppsCount =
applications?.filter((app) =>
app.metadata?.ownerReferences?.some(
(owner) => owner.kind === obj.kind && owner.name === obj.metadata?.name,
),
).length || 0;

return (
<>
<PageSection>
<Title headingLevel="h2" className="co-section-heading">
Argo CD ApplicationSet details
</Title>
<Flex
justifyContent={{ default: 'justifyContentSpaceEvenly' }}
direction={{ default: 'column', lg: 'row' }}
>
<Flex flex={{ default: 'flex_2' }}>
<FlexItem>
<BaseDetailsSummary obj={obj} model={ApplicationSetModel} />
</FlexItem>
</Flex>
<Flex flex={{ default: 'flex_2' }} direction={{ default: 'column' }}>
<FlexItem>
<DescriptionList className="pf-c-description-list">
<DetailsDescriptionGroup
title="Status"
help="Current health status of the ApplicationSet."
>
<HealthStatus status={appSetStatus} />
</DetailsDescriptionGroup>

<DetailsDescriptionGroup
title="Generated Apps"
help="Number of applications generated by this ApplicationSet."
>
<Badge isRead color="blue">
{generatedAppsCount} application{generatedAppsCount !== 1 ? 's' : ''}
</Badge>
</DetailsDescriptionGroup>

<DetailsDescriptionGroup
title="Generators"
help="Number of generators configured in this ApplicationSet."
>
<Badge isRead color="grey">
{totalGenerators} generator{totalGenerators !== 1 ? 's' : ''}
</Badge>
</DetailsDescriptionGroup>

<DetailsDescriptionGroup
title="App Project"
help="Argo CD project that this ApplicationSet belongs to."
>
<ResourceLink
namespace={obj?.metadata?.namespace}
groupVersionKind={{
group: 'argoproj.io',
version: 'v1alpha1',
kind: 'AppProject',
}}
name={spec.template?.spec?.project || 'default'}
/>
</DetailsDescriptionGroup>

{spec.template?.spec?.source?.repoURL && (
<DetailsDescriptionGroup
title="Repository"
help="Git repository URL where the ApplicationSet configuration is stored."
>
<a
href={spec.template.spec.source.repoURL}
target="_blank"
rel="noopener noreferrer"
>
{spec.template.spec.source.repoURL}
</a>
</DetailsDescriptionGroup>
)}
</DescriptionList>
</FlexItem>
</Flex>
</Flex>
</PageSection>

<PageSection>
<Title headingLevel="h2" className="co-section-heading">
Conditions
</Title>
<Conditions conditions={status.conditions} />
</PageSection>
</>
);
};

export default AppSetDetailsTab;
Loading