diff --git a/public/locales/en.json b/public/locales/en.json index a9f23741..e5d5bf23 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -12,7 +12,11 @@ "FluxList": { "tableNameHeader": "Name", "tableStatusHeader": "Status", - "tableCreatedHeader": "Created" + "tableCreatedHeader": "Created", + "tableVersionHeader": "Revision", + "noFluxError": "Please install flux to view this component", + "gitOpsTitle": "GitOps", + "kustomizationsTitle": "Kustomizations" }, "ProvidersList": { "tableNameHeader": "Name", diff --git a/src/components/ControlPlane/FluxList.tsx b/src/components/ControlPlane/FluxList.tsx index 5a305ea1..91bc0a34 100644 --- a/src/components/ControlPlane/FluxList.tsx +++ b/src/components/ControlPlane/FluxList.tsx @@ -3,19 +3,20 @@ import { AnalyticalTableColumnDefinition, Title, } from '@ui5/webcomponents-react'; -import ReactTimeAgo from 'react-time-ago'; import IllustratedError from '../Shared/IllustratedError.tsx'; import useResource from '../../lib/api/useApiResource'; -import { FluxGitRepo } from '../../lib/api/types/flux/listGitRepo'; +import { FluxRequest } from '../../lib/api/types/flux/listGitRepo'; import { FluxKustomization } from '../../lib/api/types/flux/listKustomization'; import { useTranslation } from 'react-i18next'; +import { timeAgo } from '../../utils/i18n/timeAgo.ts'; +import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx'; export default function FluxList() { const { - data: repoData, + data: gitReposData, error: repoErr, isLoading: repoIsLoading, - } = useResource(FluxGitRepo); //404 if component not enabled + } = useResource(FluxRequest); //404 if component not enabled const { data: kustmizationData, error: kustomizationErr, @@ -24,43 +25,145 @@ export default function FluxList() { const { t } = useTranslation(); - if (repoErr) { - return ; + interface CellData { + cell: { + value: T | null; // null for grouping rows + row: { + original?: FluxRow; // missing for grouping rows + }; + }; } - if (kustomizationErr) { - return ; + + type FluxRow = { + name: string; + created: string; + isReady: boolean; + statusUpdateTime?: string; + }; + + if (repoErr || kustomizationErr) { + return ( + + ); } - const columns: AnalyticalTableColumnDefinition[] = [ + const gitReposColumns: AnalyticalTableColumnDefinition[] = [ { Header: t('FluxList.tableNameHeader'), - accessor: 'metadata.name', + accessor: 'name', }, { Header: t('FluxList.tableStatusHeader'), - accessor: 'status.usages', + accessor: 'status', + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + { + Header: t('FluxList.tableVersionHeader'), + accessor: 'revision', }, { Header: t('FluxList.tableCreatedHeader'), - accessor: 'metadata.creationTimestamp', - Cell: (props: any) => , + accessor: 'created', }, ]; + const kustomizationsColumns: AnalyticalTableColumnDefinition[] = [ + { + Header: t('FluxList.tableNameHeader'), + accessor: 'name', + }, + { + Header: t('FluxList.tableStatusHeader'), + accessor: 'status', + Cell: (cellData: CellData) => + cellData.cell.row.original?.isReady != null ? ( + + ) : null, + }, + { + Header: t('FluxList.tableCreatedHeader'), + accessor: 'created', + }, + ]; + + const gitReposRows: FluxRow[] = + gitReposData?.items?.map((item) => { + return { + name: item.metadata.name, + isReady: + item.status.conditions.find((x) => x.type === 'Ready')?.status === + 'True', + statusUpdateTime: item.status.conditions.find((x) => x.type === 'Ready') + ?.lastTransitionTime, + revision: shortenCommitHash(item.status.artifact.revision), + created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + }; + }) ?? []; + + const kustomizationsRows: FluxRow[] = + kustmizationData?.items?.map((item) => { + return { + name: item.metadata.name, + isReady: + item.status.conditions.find((x) => x.type === 'Ready')?.status === + 'True', + statusUpdateTime: item.status.conditions.find((x) => x.type === 'Ready') + ?.lastTransitionTime, + created: timeAgo.format(new Date(item.metadata.creationTimestamp)), + }; + }) ?? []; + return ( <> - Git Repos - - Kustomizations - + {' '} +
+ {t('FluxList.gitOpsTitle')} + +
+
+ {t('FluxList.kustomizationsTitle')} + +
); } + +function shortenCommitHash(commitHash: string): string { + //example hash: master@sha1:b3396adb98a6a0f5eeedd1a600beaf5e954a1f28 + const match = commitHash.match(/^([a-zA-Z0-9-_]+)@sha1:([a-f0-9]{40})/); + + if (match && match[2]) { + return `${match[1]}@${match[2].slice(0, 7)}`; + } + + //example output : master@b3396ad + return commitHash; +} diff --git a/src/lib/api/types/flux/listGitRepo.ts b/src/lib/api/types/flux/listGitRepo.ts index d989514d..3c2c6f87 100644 --- a/src/lib/api/types/flux/listGitRepo.ts +++ b/src/lib/api/types/flux/listGitRepo.ts @@ -1,6 +1,32 @@ import { Resource } from '../resource'; -export const FluxGitRepo: Resource = { +export type GitReposResponse = { + items: [ + { + spec: { + package: string; + }; + kind: string; + metadata: { + name: string; + creationTimestamp: string; + }; + status: { + artifact: { + revision: string; + }; + conditions: [ + { + status: string; + type: string; + lastTransitionTime: string; + }, + ]; + }; + }, + ]; +}; + +export const FluxRequest: Resource = { path: '/apis/source.toolkit.fluxcd.io/v1/gitrepositories', - jq: '[.items[]]', }; diff --git a/src/lib/api/types/flux/listKustomization.ts b/src/lib/api/types/flux/listKustomization.ts index 490a9507..0efff18e 100644 --- a/src/lib/api/types/flux/listKustomization.ts +++ b/src/lib/api/types/flux/listKustomization.ts @@ -1,6 +1,32 @@ import { Resource } from '../resource'; -export const FluxKustomization: Resource = { +export type KustomizationsResponse = { + items: [ + { + spec: { + package: string; + }; + kind: string; + metadata: { + name: string; + creationTimestamp: string; + }; + status: { + artifact: { + revision: string; + }; + conditions: [ + { + status: string; + type: string; + lastTransitionTime: string; + }, + ]; + }; + }, + ]; +}; + +export const FluxKustomization: Resource = { path: '/apis/kustomize.toolkit.fluxcd.io/v1/kustomizations', - jq: '[.items[]]', };