diff --git a/flux/src/actions/index.tsx b/flux/src/actions/index.tsx index 2107323bf..6e2bcb948 100644 --- a/flux/src/actions/index.tsx +++ b/flux/src/actions/index.tsx @@ -2,6 +2,7 @@ import { ActionButton, ConfirmDialog } from '@kinvolk/headlamp-plugin/lib/compon import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; import { useSnackbar } from 'notistack'; import React from 'react'; +import { useSource } from '../sources/Source'; function ForceReconciliationAction(props) { const { enqueueSnackbar } = useSnackbar(); @@ -215,8 +216,12 @@ function SyncAction(props) { } function SyncWithSourceAction(props) { - const { resource, source } = props; + const { resource } = props; const { enqueueSnackbar } = useSnackbar(); + const source = useSource(resource); + + if (!source) return null; + return ( { - const get = source.constructor.apiEndpoint.get; + const get = (source.constructor as any).apiEndpoint.get; let isSourceSynced = false; get(source.metadata.namespace, source.metadata.name, newSource => { if (newSource.status.lastHandledReconcileAt === date && !isSourceSynced) { diff --git a/flux/src/common/Resources.tsx b/flux/src/common/Resources.tsx new file mode 100644 index 000000000..fba2e1735 --- /dev/null +++ b/flux/src/common/Resources.tsx @@ -0,0 +1,123 @@ +import { KubeObject } from '@kinvolk/headlamp-plugin/lib/k8s/cluster'; + +export class Kustomization extends KubeObject { + static kind = 'Kustomization'; + static apiName = 'kustomizations'; + static apiVersion = 'kustomize.toolkit.fluxcd.io/v1'; + static isNamespaced = true; +} + +export class HelmRelease extends KubeObject { + static kind = 'HelmRelease'; + static apiName = 'helmreleases'; + static apiVersion = ['helm.toolkit.fluxcd.io/v2', 'helm.toolkit.fluxcd.io/v2beta1']; + static isNamespaced = true; +} + +export class GitRepository extends KubeObject { + static kind = 'GitRepository'; + static apiName = 'gitrepositories'; + static apiVersion = 'source.toolkit.fluxcd.io/v1'; + static isNamespaced = true; +} + +export class OCIRepository extends KubeObject { + static kind = 'OCIRepository'; + static apiName = 'ocirepositories'; + static apiVersion = 'source.toolkit.fluxcd.io/v1beta2'; + static isNamespaced = true; +} + +export class BucketRepository extends KubeObject { + static kind = 'Bucket'; + static apiName = 'buckets'; + static apiVersion = ['source.toolkit.fluxcd.io/v1', 'source.toolkit.fluxcd.io/v1beta2']; + static isNamespaced = true; +} + +export class HelmRepository extends KubeObject { + static kind = 'HelmRepository'; + static apiName = 'helmrepositories'; + static apiVersion = ['source.toolkit.fluxcd.io/v1', 'source.toolkit.fluxcd.io/v1beta2']; + static isNamespaced = true; +} + +export class HelmChart extends KubeObject { + static kind = 'HelmChart'; + static apiName = 'helmcharts'; + static apiVersion = ['source.toolkit.fluxcd.io/v1', 'source.toolkit.fluxcd.io/v1beta2']; + static isNamespaced = true; +} + +export class ExternalArtifact extends KubeObject { + static kind = 'ExternalArtifact'; + static apiName = 'externalartifacts'; + static apiVersion = 'source.toolkit.fluxcd.io/v1'; + static isNamespaced = true; +} + +export class AlertNotification extends KubeObject { + static kind = 'Alert'; + static apiName = 'alerts'; + static apiVersion = [ + 'notification.toolkit.fluxcd.io/v1beta3', + 'notification.toolkit.fluxcd.io/v1beta2', + ]; + static isNamespaced = true; +} + +export class ProviderNotification extends KubeObject { + static kind = 'Provider'; + static apiName = 'providers'; + static apiVersion = [ + 'notification.toolkit.fluxcd.io/v1beta3', + 'notification.toolkit.fluxcd.io/v1beta2', + ]; + static isNamespaced = true; +} + +export class ReceiverNotification extends KubeObject { + static kind = 'Receiver'; + static apiName = 'receivers'; + static apiVersion = [ + 'notification.toolkit.fluxcd.io/v1beta3', + 'notification.toolkit.fluxcd.io/v1beta2', + 'notification.toolkit.fluxcd.io/v1', + ]; + static isNamespaced = true; +} + +export class ImageUpdateAutomation extends KubeObject { + static kind = 'ImageUpdateAutomation'; + static apiName = 'imageupdateautomations'; + static apiVersion = [ + 'image.toolkit.fluxcd.io/v1', + 'image.toolkit.fluxcd.io/v1beta2', + 'image.toolkit.fluxcd.io/v1beta1', + ]; + static isNamespaced = true; +} + +export class ImagePolicy extends KubeObject { + static kind = 'ImagePolicy'; + static apiName = 'imagepolicies'; + static apiVersion = ['image.toolkit.fluxcd.io/v1', 'image.toolkit.fluxcd.io/v1beta2']; + static isNamespaced = true; +} + +export class ImageRepository extends KubeObject { + static kind = 'ImageRepository'; + static apiName = 'imagerepositories'; + static apiVersion = ['image.toolkit.fluxcd.io/v1', 'image.toolkit.fluxcd.io/v1beta2']; + static isNamespaced = true; +} + +export const getSourceClassByPluralName = (pluralName: string) => + ({ + gitrepositories: GitRepository, + ocirepositories: OCIRepository, + buckets: BucketRepository, + helmrepositories: HelmRepository, + helmcharts: HelmChart, + externalartifacts: ExternalArtifact, + }[pluralName]); diff --git a/flux/src/helm-releases/HelmReleaseList.tsx b/flux/src/helm-releases/HelmReleaseList.tsx index b5a2932d0..d3bad2ad8 100644 --- a/flux/src/helm-releases/HelmReleaseList.tsx +++ b/flux/src/helm-releases/HelmReleaseList.tsx @@ -1,36 +1,14 @@ import { SectionBox, SectionFilterHeader } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd'; import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils'; import React from 'react'; import { NotSupported } from '../checkflux'; +import { HelmRelease } from '../common/Resources'; import Table from '../common/Table'; import { NameLink } from '../helpers'; export function HelmReleases() { - return ; -} - -export function helmReleaseClass() { - const helmreleaseGroup = 'helm.toolkit.fluxcd.io'; - const helmreleaseVersion = 'v2'; - - return makeCustomResourceClass({ - apiInfo: [ - { group: helmreleaseGroup, version: helmreleaseVersion }, - { group: helmreleaseGroup, version: 'v2beta1' }, - ], - isNamespaced: true, - singularName: 'HelmRelease', - pluralName: 'helmreleases', - }); -} - -function HelmReleasesList() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - helmReleaseClass().useApiList(setResources, setError); + const [resources, error] = HelmRelease.useList(); if (error?.status === 404) { return ; @@ -43,7 +21,7 @@ function HelmReleasesList() { // @ts-ignore -- TODO Update the sorting param defaultSortingColumn={2} columns={[ - NameLink(helmReleaseClass()), + NameLink(HelmRelease), 'namespace', 'status', 'source', diff --git a/flux/src/helm-releases/HelmReleaseSingle.tsx b/flux/src/helm-releases/HelmReleaseSingle.tsx index 412713b1c..ff8827b55 100644 --- a/flux/src/helm-releases/HelmReleaseSingle.tsx +++ b/flux/src/helm-releases/HelmReleaseSingle.tsx @@ -1,3 +1,4 @@ +import { registerDetailsViewSection } from '@kinvolk/headlamp-plugin/lib'; import { ConditionsTable, Link, @@ -17,10 +18,9 @@ import { SyncWithSourceAction, } from '../actions/index'; import RemainingTimeDisplay from '../common/RemainingTimeDisplay'; +import { HelmRelease } from '../common/Resources'; import StatusLabel from '../common/StatusLabel'; import { getSourceNameAndPluralKind, ObjectEvents } from '../helpers/index'; -import { GetSource } from '../sources/Source'; -import { helmReleaseClass } from './HelmReleaseList'; import { HelmInventory } from './Inventory'; export function FluxHelmReleaseDetailView(props: { name?: string; namespace?: string }) { @@ -30,17 +30,80 @@ export function FluxHelmReleaseDetailView(props: { name?: string; namespace?: st return ( <> - + ); } +export const registerHelmRelease = () => { + registerDetailsViewSection(({ resource }: { resource: HelmRelease }) => { + console.log('flux', { resource }); + if (resource.kind !== 'HelmRelease') return null; + + const themeName = localStorage.getItem('headlampThemePreference'); + + const cr = resource; + + return ( + <> + {cr && cr?.jsonData?.spec?.values && ( + + + + )} + + + + + + + ( + + {item.name} + + ), + }, + { + header: 'Namespace', + accessorFn: item => ( + + {item.namespace || resource.metadata.namespace} + + ), + }, + ]} + /> + + + + + + ); + }); +}; + function CustomResourceDetails(props) { const { name, namespace } = props; - const [cr, setCr] = React.useState(null); - const [source, setSource] = React.useState(null); - - helmReleaseClass().useApiGet(setCr, name, namespace); + const [cr] = HelmRelease.useGet(name, namespace); function prepareExtraInfo(cr) { if (!cr) { @@ -131,7 +194,7 @@ function CustomResourceDetails(props) { } const actions = []; - actions.push(); + actions.push(); actions.push(); actions.push(); actions.push(); @@ -143,7 +206,6 @@ function CustomResourceDetails(props) { return ( <> - {cr && } {cr && ( { return item.type; }, }, { header: 'Reason', + gridTemplate: 'min-content', accessorFn: item => { return item.reason; }, }, { header: 'From', + gridTemplate: 'min-content', accessorFn: item => { return item.source.component; }, diff --git a/flux/src/image-automation/ImageAutomationList.tsx b/flux/src/image-automation/ImageAutomationList.tsx index b57f3575c..3fcbb0519 100644 --- a/flux/src/image-automation/ImageAutomationList.tsx +++ b/flux/src/image-automation/ImageAutomationList.tsx @@ -4,72 +4,28 @@ import { SectionFilterHeader, ShowHideLabel, } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { KubeObjectClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; -import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd'; import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils'; import React from 'react'; import YAML from 'yaml'; import { NotSupported } from '../checkflux'; import SourceLink from '../common/Link'; +import { ImagePolicy, ImageRepository, ImageUpdateAutomation } from '../common/Resources'; import Table from '../common/Table'; import { NameLink } from '../helpers'; -const imageGroup = 'image.toolkit.fluxcd.io'; - -export function imageRepositoriesClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: imageGroup, version: 'v1' }, - { group: imageGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'ImageRepository', - pluralName: 'imagerepositories', - }); -} - -export function imagePolicyClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: imageGroup, version: 'v1' }, - { group: imageGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'ImagePolicy', - pluralName: 'imagepolicies', - }); -} - -export function imageUpdateAutomationClass(): KubeObjectClass { - return makeCustomResourceClass({ - apiInfo: [ - { group: imageGroup, version: 'v1' }, - { group: imageGroup, version: 'v1beta2' }, - { group: imageGroup, version: 'v1beta1' }, - ], - isNamespaced: true, - singularName: 'ImageUpdateAutomation', - pluralName: 'imageupdateautomations', - }); -} - export function ImageAutomation() { return ( <> - - - + + + ); } -function ImageUpdateAutomationList(props: { resourceClass: KubeObjectClass }) { - const { resourceClass } = props; +function ImageUpdateAutomationList() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - resourceClass.useApiList(setResources, setError); + const [resources, error] = ImageUpdateAutomation.useList(); if (error?.status === 404) { return ; @@ -80,7 +36,7 @@ function ImageUpdateAutomationList(props: { resourceClass: KubeObjectClass }) {
; @@ -133,7 +85,7 @@ function ImagePolicyList(props: { resourceClass: KubeObjectClass }) {
; @@ -170,7 +118,7 @@ function ImageRepositoryList(props: { resourceClass: KubeObjectClass }) {
{ switch (pluralName) { case 'imagerepositories': - return imageRepositoriesClass(); + return ImageRepository; case 'imagepolicies': - return imagePolicyClass(); + return ImagePolicy; case 'imageupdateautomations': - return imageUpdateAutomationClass(); + return ImageUpdateAutomation; default: return null; } @@ -173,7 +169,7 @@ function CustomResourceDetails(props) { resource={resource} extraInfo={prepareExtraInfo()} actions={ - resourceClass.pluralName === imagePolicyClass().pluralName + resourceClass.pluralName === ImagePolicy.pluralName ? [] : [ , @@ -182,10 +178,10 @@ function CustomResourceDetails(props) { ] } /> - {resourceClass.pluralName === imageRepositoriesClass().pluralName && ( + {resourceClass.pluralName === ImageRepository.pluralName && ( )} - {resourceClass.pluralName === imageUpdateAutomationClass().pluralName && ( + {resourceClass.pluralName === ImageUpdateAutomation.pluralName && ( )} diff --git a/flux/src/index.tsx b/flux/src/index.tsx index 8cd0b73ed..f1f57d46d 100644 --- a/flux/src/index.tsx +++ b/flux/src/index.tsx @@ -8,7 +8,7 @@ import { import Canaries from './flagger/canaries'; import CanaryDetails from './flagger/canarydetails'; import { HelmReleases } from './helm-releases/HelmReleaseList'; -import { FluxHelmReleaseDetailView } from './helm-releases/HelmReleaseSingle'; +import { FluxHelmReleaseDetailView, registerHelmRelease } from './helm-releases/HelmReleaseSingle'; import { ImageAutomation } from './image-automation/ImageAutomationList'; import { FluxImageAutomationDetailView } from './image-automation/ImageAutomationSingle'; import { Kustomizations } from './kustomizations/KustomizationList'; @@ -21,6 +21,8 @@ import { FluxRunTime } from './runtime/RuntimeList'; import { FluxSources } from './sources/SourceList'; import { FluxSourceDetailView } from './sources/SourceSingle'; +registerHelmRelease(); + addIcon('simple-icons:flux', { body: '', width: 24, diff --git a/flux/src/kustomizations/KustomizationList.tsx b/flux/src/kustomizations/KustomizationList.tsx index feda14a7b..6dac8322b 100644 --- a/flux/src/kustomizations/KustomizationList.tsx +++ b/flux/src/kustomizations/KustomizationList.tsx @@ -1,37 +1,14 @@ import { SectionBox, SectionFilterHeader } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd'; import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils'; import React from 'react'; import { NotSupported } from '../checkflux'; +import { Kustomization } from '../common/Resources'; import Table from '../common/Table'; import { NameLink } from '../helpers'; export function Kustomizations() { - return ( -
- -
- ); -} - -export function kustomizationClass() { - const kustomizationGroup = 'kustomize.toolkit.fluxcd.io'; - const kustomizationVersion = 'v1'; - - return makeCustomResourceClass({ - apiInfo: [{ group: kustomizationGroup, version: kustomizationVersion }], - isNamespaced: true, - singularName: 'Kustomization', - pluralName: 'kustomizations', - }); -} - -function KustomizationList() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - kustomizationClass().useApiList(setResources, setError); + const [resources, error] = Kustomization.useList(); if (error?.status === 404) { return ; @@ -44,7 +21,7 @@ function KustomizationList() { // @ts-ignore -- TODO Update the sorting param defaultSortingColumn={2} columns={[ - NameLink(kustomizationClass()), + NameLink(Kustomization), 'namespace', 'status', 'source', diff --git a/flux/src/kustomizations/KustomizationSingle.tsx b/flux/src/kustomizations/KustomizationSingle.tsx index 11972dc6e..aa3f51913 100644 --- a/flux/src/kustomizations/KustomizationSingle.tsx +++ b/flux/src/kustomizations/KustomizationSingle.tsx @@ -16,12 +16,11 @@ import { SyncWithSourceAction, } from '../actions/index'; import RemainingTimeDisplay from '../common/RemainingTimeDisplay'; +import { Kustomization } from '../common/Resources'; import StatusLabel from '../common/StatusLabel'; import Table from '../common/Table'; import { getSourceNameAndPluralKind, ObjectEvents } from '../helpers/index'; -import { GetSource } from '../sources/Source'; import { GetResourcesFromInventory } from './Inventory'; -import { kustomizationClass } from './KustomizationList'; export function FluxKustomizationDetailView(props: { name?: string; namespace?: string }) { const params = useParams<{ namespace: string; name: string }>(); @@ -30,17 +29,14 @@ export function FluxKustomizationDetailView(props: { name?: string; namespace?: return ( <> - + ); } function KustomizationDetails(props) { const { name, namespace } = props; - const [cr, setCr] = React.useState(null); - const [source, setSource] = React.useState(null); - - kustomizationClass().useApiGet(setCr, name, namespace); + const [cr] = Kustomization.useGet(name, namespace); function prepareExtraInfo(cr) { if (!cr) { @@ -110,7 +106,7 @@ function KustomizationDetails(props) { } const actions = []; - actions.push(); + actions.push(); actions.push(); actions.push(); actions.push(); @@ -123,7 +119,6 @@ function KustomizationDetails(props) { return ( <> - {cr && } {cr?.jsonData?.spec?.values && ( diff --git a/flux/src/mapView.tsx b/flux/src/mapView.tsx index c4ee002e8..68c3a2422 100644 --- a/flux/src/mapView.tsx +++ b/flux/src/mapView.tsx @@ -1,18 +1,11 @@ import { Icon } from '@iconify/react'; import Deployment from '@kinvolk/headlamp-plugin/lib/K8s/deployment'; import { useMemo } from 'react'; -import { helmReleaseClass } from './helm-releases/HelmReleaseList'; +import { HelmRelease, HelmRepository, Kustomization, OCIRepository } from './common/Resources'; import { FluxHelmReleaseDetailView } from './helm-releases/HelmReleaseSingle'; -import { kustomizationClass } from './kustomizations/KustomizationList'; import { FluxKustomizationDetailView } from './kustomizations/KustomizationSingle'; -import { helmRepositoryClass, ociRepositoryClass } from './sources/SourceList'; import { FluxSourceDetailView } from './sources/SourceSingle'; -const HelmRelease = helmReleaseClass(); -const HelmRepostory = helmRepositoryClass(); -const OciSource = ociRepositoryClass(); -const Kustomization = kustomizationClass(); - export const makeKubeToKubeEdge = (from: any, to: any): any => ({ id: `${from.metadata.uid}-${to.metadata.uid}`, source: from.metadata.uid, @@ -51,7 +44,7 @@ const helmRepositorySource: any = { label: 'Helm Repository', icon: , useData() { - const [repositories] = HelmRepostory.useList(); + const [repositories] = HelmRepository.useList(); const [releases] = HelmRelease.useList(); return useMemo(() => { @@ -61,6 +54,7 @@ const helmRepositorySource: any = { id: it.metadata.uid, kubeObject: it, detailsComponent: HelmRepositoryDetails, + weight: 1100, })); const edges: any[] = []; @@ -100,6 +94,7 @@ const helmReleaseSource = { id: it.metadata.uid, kubeObject: it, detailsComponent: HelmReleaseDetails, + weight: 1050, })); const edges = []; @@ -139,6 +134,7 @@ const kustomizationSource = { id: it.metadata.uid, kubeObject: it, detailsComponent: KustomizationDetails, + weight: 1050, })); const edges = []; @@ -170,7 +166,7 @@ const ociSource = { icon: , useData() { const [kustomizations] = Kustomization.useList(); - const [ocisources] = OciSource.useList(); + const [ocisources] = OCIRepository.useList(); return useMemo(() => { if (!kustomizations || !ocisources) return null; @@ -178,6 +174,7 @@ const ociSource = { id: it.metadata.uid, kubeObject: it, detailsComponent: OCIRepositoryDetails, + weight: 1100, })); const edges = []; diff --git a/flux/src/notifications/NotificationList.tsx b/flux/src/notifications/NotificationList.tsx index 9f19a4643..167d00d2c 100644 --- a/flux/src/notifications/NotificationList.tsx +++ b/flux/src/notifications/NotificationList.tsx @@ -3,53 +3,13 @@ import { SectionBox, SectionFilterHeader, } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd'; import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils'; import { Box } from '@mui/material'; import React from 'react'; import { NotSupported } from '../checkflux'; +import { AlertNotification, ProviderNotification, ReceiverNotification } from '../common/Resources'; import Table from '../common/Table'; -const notificationGroup = 'notification.toolkit.fluxcd.io'; -const notificationVersion = 'v1beta3'; - -export function alertNotificationClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: notificationGroup, version: notificationVersion }, - { group: notificationGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'Alert', - pluralName: 'alerts', - }); -} - -export function providerNotificationClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: notificationGroup, version: notificationVersion }, - { group: notificationGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'Provider', - pluralName: 'providers', - }); -} - -export function receiverNotificationClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: notificationGroup, version: notificationVersion }, - { group: notificationGroup, version: 'v1beta2' }, - { group: notificationGroup, version: 'v1' }, - ], - isNamespaced: true, - singularName: 'Receiver', - pluralName: 'receivers', - }); -} - export function Notifications() { return ( <> @@ -62,10 +22,7 @@ export function Notifications() { function Alerts() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - alertNotificationClass().useApiList(setResources, setError); + const [resources, error] = AlertNotification.useList(); if (error?.status === 404) { return ; @@ -133,10 +90,7 @@ function Alerts() { function Providers() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - providerNotificationClass().useApiList(setResources, setError); + const [resources, error] = ProviderNotification.useList(); if (error?.status === 404) { return ; @@ -220,10 +174,7 @@ function Providers() { function Receivers() { const filterFunction = useFilterFunc(); - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - receiverNotificationClass().useApiList(setResources, setError); + const [resources, error] = ReceiverNotification.useList(); if (error?.status === 404) { return ; diff --git a/flux/src/notifications/NotificationSingle.tsx b/flux/src/notifications/NotificationSingle.tsx index 74b72c78e..9aa18c1af 100644 --- a/flux/src/notifications/NotificationSingle.tsx +++ b/flux/src/notifications/NotificationSingle.tsx @@ -13,12 +13,8 @@ import { } from '../actions/index'; import Flux404 from '../checkflux'; import RemainingTimeDisplay from '../common/RemainingTimeDisplay'; +import { AlertNotification, ProviderNotification, ReceiverNotification } from '../common/Resources'; import { ObjectEvents } from '../helpers/index'; -import { - alertNotificationClass, - providerNotificationClass, - receiverNotificationClass, -} from './NotificationList'; export function Notification() { const { namespace, pluralName, name } = useParams<{ @@ -29,11 +25,11 @@ export function Notification() { const resourceClass = (() => { switch (pluralName) { case 'alerts': - return alertNotificationClass(); + return AlertNotification; case 'providers': - return providerNotificationClass(); + return ProviderNotification; case 'receivers': - return receiverNotificationClass(); + return ReceiverNotification; default: return null; } @@ -53,9 +49,7 @@ export function Notification() { function NotificationDetails(props) { const { name, namespace, resourceClass } = props; - const [resource, setResource] = React.useState(null); - - resourceClass.useApiGet(setResource, name, namespace); + const [resource] = resourceClass.useGet(name, namespace); function prepareExtraInfo() { const extraInfo = []; diff --git a/flux/src/overview/index.tsx b/flux/src/overview/index.tsx index 6c72fb23e..61d868e1e 100644 --- a/flux/src/overview/index.tsx +++ b/flux/src/overview/index.tsx @@ -7,7 +7,7 @@ import { StatusLabel, TileChart, } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; +import type { KubeObject, KubeObjectClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; import { Accordion, AccordionDetails, @@ -21,47 +21,111 @@ import { import { useTheme } from '@mui/material/styles'; import React, { useEffect, useState } from 'react'; import SourceLink from '../common/Link'; +import { + AlertNotification, + BucketRepository, + ExternalArtifact, + GitRepository, + HelmChart, + HelmRelease, + HelmRepository, + ImagePolicy, + ImageRepository, + ImageUpdateAutomation, + Kustomization, + OCIRepository, + ProviderNotification, + ReceiverNotification, +} from '../common/Resources'; import Table from '../common/Table'; -import { helmReleaseClass } from '../helm-releases/HelmReleaseList'; import { useFluxCheck } from '../helpers'; -import { - imagePolicyClass, - imageRepositoriesClass, - imageUpdateAutomationClass, -} from '../image-automation/ImageAutomationList'; -import { kustomizationClass } from '../kustomizations/KustomizationList'; -import { providerNotificationClass } from '../notifications/NotificationList'; -import { alertNotificationClass } from '../notifications/NotificationList'; -import { receiverNotificationClass } from '../notifications/NotificationList'; -import { - bucketRepositoryClass, - externalArtifactClass, - gitRepositoryClass, - helmChartClass, - helmRepositoryClass, - ociRepositoryClass, -} from '../sources/SourceList'; -export function FluxOverview() { - const [sortFilter, setSortFilter] = useState('failed'); +// Helper to get failed count for a resource class +function getFailedCount(items: KubeObject[] | null) { + if (items === null) return 0; + // getStatus is defined inside FluxOverviewChart, so redefine here + let failed = 0; + for (const resource of items) { + if (!resource.jsonData.status) { + continue; + } else if (resource.jsonData.spec?.suspend) { + continue; + } else if (Array.isArray(resource.jsonData.status.conditions)) { + if ( + !resource.jsonData.status.conditions.some( + condition => condition.type === 'Ready' && condition.status === 'True' + ) + ) { + failed++; + } + } + } + return failed; +} + +// Helper to get success count for a resource class +function getSuccessCount(items: KubeObject[] | null) { + if (items === null) return 0; + + let success = 0; + for (const resource of items) { + if (!resource.jsonData.status) { + success++; + } else if (resource.jsonData.spec?.suspend) { + continue; + } else if (Array.isArray(resource.jsonData.status.conditions)) { + if ( + resource.jsonData.status.conditions.some( + condition => condition.type === 'Ready' && condition.status === 'True' + ) + ) { + success++; + } + } + } + return success; +} - const kustomizationResourceClass = kustomizationClass(); - const helmReleaseResourceClass = helmReleaseClass(); - const externalArtifactResourceClass = externalArtifactClass(); - const gitRepoResourceClass = gitRepositoryClass(); - const ociRepoResourceClass = ociRepositoryClass(); - const bucketRepoResourceClass = bucketRepositoryClass(); - const helmRepoResourceClass = helmRepositoryClass(); - const helmChartResourceClass = helmChartClass(); - const alertsResourceClass = alertNotificationClass(); - const providersResourceClass = providerNotificationClass(); - const receiversResourceClass = receiverNotificationClass(); - - const imageUpdateAutomationResourceClass = imageUpdateAutomationClass(); - const imagePolicyResourceClass = imagePolicyClass(); - const imageRepositoryResourceClass = imageRepositoriesClass(); +// Helper to get display name for a resource class +function getDisplayName(resourceClass: KubeObjectClass) { + const nameMap = { + gitrepositories: 'Git Repositories', + ocirepositories: 'OCI Repositories', + buckets: 'Buckets', + helmrepositories: 'Helm Repositories', + helmcharts: 'Helm Charts', + kustomizations: 'Kustomizations', + helmreleases: 'Helm Releases', + alerts: 'Alerts', + providers: 'Providers', + receivers: 'Receivers', + imagerepositories: 'Image Repositories', + imageupdateautomations: 'Image Update Automations', + imagepolicies: 'Image Policies', + externalartifacts: 'External Artifacts', + }; + return nameMap[resourceClass.apiName] || resourceClass.apiName; +} +export function FluxOverview() { + const [sortFilter, setSortFilter] = useState('failed'); const fluxCheck = useFluxCheck(); + const namespace = fluxCheck.namespace; + + const [kustomizations] = Kustomization.useList({ namespace }); + const [helmReleases] = HelmRelease.useList({ namespace }); + const [gitRepos] = GitRepository.useList({ namespace }); + const [ociRepos] = OCIRepository.useList({ namespace }); + const [buckets] = BucketRepository.useList({ namespace }); + const [helmRepos] = HelmRepository.useList({ namespace }); + const [externalArtifacts] = ExternalArtifact.useList({ namespace }); + const [helmCharts] = HelmChart.useList({ namespace }); + const [alerts] = AlertNotification.useList({ namespace }); + const [providerNotifications] = ProviderNotification.useList({ namespace }); + const [receiverNotifications] = ReceiverNotification.useList({ namespace }); + const [imageUpdateAutomations] = ImageUpdateAutomation.useList({ namespace }); + const [imagePolicies] = ImagePolicy.useList({ namespace }); + const [imageRepositories] = ImageRepository.useList({ namespace }); const [pods] = K8s.ResourceClasses.Pod.useList({ namespace: fluxCheck.namespace, @@ -94,106 +158,30 @@ export function FluxOverview() { ); }, [pods]); - // Collect all resource classes in an array with their display condition - const resourceClasses = [ - kustomizationResourceClass, - helmReleaseResourceClass, - externalArtifactResourceClass, - gitRepoResourceClass, - ociRepoResourceClass, - bucketRepoResourceClass, - helmRepoResourceClass, - helmChartResourceClass, - alertsResourceClass, - providersResourceClass, - receiversResourceClass, - imageRepositoryResourceClass, - imagePolicyResourceClass, - imageUpdateAutomationResourceClass, - ].filter(Boolean); - - // Helper to get failed count for a resource class - function getFailedCount(resourceClass) { - // Use the same logic as in FluxOverviewChart - const [crds] = resourceClass.useList ? resourceClass.useList() : []; - if (!crds) return 0; - // getStatus is defined inside FluxOverviewChart, so redefine here - let failed = 0; - for (const resource of crds) { - if (!resource.jsonData.status) { - continue; - } else if (resource.jsonData.spec?.suspend) { - continue; - } else if (Array.isArray(resource.jsonData.status.conditions)) { - if ( - !resource.jsonData.status.conditions.some( - condition => condition.type === 'Ready' && condition.status === 'True' - ) - ) { - failed++; - } - } - } - return failed; - } - - // Helper to get total count for a resource class - function getTotalCount(resourceClass) { - const [crds] = resourceClass.useList ? resourceClass.useList() : []; - return crds ? crds.length : 0; - } - - // Helper to get success count for a resource class - function getSuccessCount(resourceClass) { - const [crds] = resourceClass.useList ? resourceClass.useList() : []; - if (!crds) return 0; - let success = 0; - for (const resource of crds) { - if (!resource.jsonData.status) { - success++; - } else if (resource.jsonData.spec?.suspend) { - continue; - } else if (Array.isArray(resource.jsonData.status.conditions)) { - if ( - resource.jsonData.status.conditions.some( - condition => condition.type === 'Ready' && condition.status === 'True' - ) - ) { - success++; - } - } - } - return success; - } - - // Helper to get display name for a resource class - function getDisplayName(resourceClass) { - const nameMap = { - gitrepositories: 'Git Repositories', - ocirepositories: 'OCI Repositories', - buckets: 'Buckets', - helmrepositories: 'Helm Repositories', - helmcharts: 'Helm Charts', - kustomizations: 'Kustomizations', - helmreleases: 'Helm Releases', - alerts: 'Alerts', - providers: 'Providers', - receivers: 'Receivers', - imagerepositories: 'Image Repositories', - imageupdateautomations: 'Image Update Automations', - imagepolicies: 'Image Policies', - externalartifacts: 'External Artifacts', - }; - return nameMap[resourceClass.apiName] || resourceClass.apiName; - } - // Sort resource classes based on selected filter const sortedResourceClasses = React.useMemo(() => { - const resourceData = resourceClasses.map(rc => ({ + const itemsWithClass = [ + { rc: Kustomization, items: kustomizations }, + { rc: HelmRelease, items: helmReleases }, + { rc: GitRepository, items: gitRepos }, + { rc: OCIRepository, items: ociRepos }, + { rc: BucketRepository, items: buckets }, + { rc: HelmRepository, items: helmRepos }, + { rc: ExternalArtifact, items: externalArtifacts }, + { rc: HelmChart, items: helmCharts }, + { rc: AlertNotification, items: alerts }, + { rc: ProviderNotification, items: providerNotifications }, + { rc: ReceiverNotification, items: receiverNotifications }, + { rc: ImageRepository, items: imageRepositories }, + { rc: ImagePolicy, items: imagePolicies }, + { rc: ImageUpdateAutomation, items: imageUpdateAutomations }, + ]; + + const resourceData = itemsWithClass.map(({ rc, items }) => ({ rc, - failed: getFailedCount(rc), - total: getTotalCount(rc), - success: getSuccessCount(rc), + failed: getFailedCount(items), + total: items?.length ?? 0, + success: getSuccessCount(items), name: getDisplayName(rc), })); @@ -217,7 +205,22 @@ export function FluxOverview() { default: return resourceData.sort((a, b) => b.failed - a.failed).map(obj => obj.rc); } - }, [resourceClasses, sortFilter]); + }, [ + kustomizations, + helmReleases, + gitRepos, + ociRepos, + buckets, + helmRepos, + helmCharts, + alerts, + providerNotifications, + receiverNotifications, + imageRepositories, + imagePolicies, + imageUpdateAutomations, + sortFilter, + ]); const handleSortFilterChange = event => { setSortFilter(event.target.value); diff --git a/flux/src/sources/Source.tsx b/flux/src/sources/Source.tsx index a46f5ff3b..06155673a 100644 --- a/flux/src/sources/Source.tsx +++ b/flux/src/sources/Source.tsx @@ -1,43 +1,15 @@ import { KubeObject } from '@kinvolk/headlamp-plugin/lib/lib/k8s/KubeObject'; +import { getSourceClassByPluralName } from '../common/Resources'; import { getSourceNameAndPluralKind } from '../helpers'; -import { - bucketRepositoryClass, - externalArtifactClass, - gitRepositoryClass, - helmChartClass, - helmRepositoryClass, - ociRepositoryClass, -} from './SourceList'; -export function GetSourceClass(pluralName: string) { - return (() => { - switch (pluralName) { - case 'externalartifacts': - return externalArtifactClass(); - case 'gitrepositories': - return gitRepositoryClass(); - case 'ocirepositories': - return ociRepositoryClass(); - case 'buckets': - return bucketRepositoryClass(); - case 'helmrepositories': - return helmRepositoryClass(); - case 'helmcharts': - return helmChartClass(); - default: - return null; - } - })(); -} - -export function GetSource(props: { item: KubeObject | null; setSource: (...args) => void }) { - const { item, setSource } = props; +/** Returns instance of the source for the given resource */ +export function useSource(item: KubeObject) { const namespace = item.jsonData.metadata.namespace; - const { name, pluralKind, namespace: sourceNamespace } = getSourceNameAndPluralKind(item); - const resourceClass = GetSourceClass(pluralKind); - resourceClass.useApiGet(setSource, name, sourceNamespace ?? namespace); + const resourceClass = getSourceClassByPluralName(pluralKind); + + const [sourceItem] = resourceClass.useGet(name, sourceNamespace ?? namespace); - return <>; + return sourceItem; } diff --git a/flux/src/sources/SourceList.tsx b/flux/src/sources/SourceList.tsx index 294f867ae..b8bab3fe0 100644 --- a/flux/src/sources/SourceList.tsx +++ b/flux/src/sources/SourceList.tsx @@ -1,102 +1,44 @@ import { Link, SectionBox } from '@kinvolk/headlamp-plugin/lib/components/common'; -import { KubeObjectClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; -import { makeCustomResourceClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/crd'; +import type { KubeObjectClass } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; import { useFilterFunc } from '@kinvolk/headlamp-plugin/lib/Utils'; import React from 'react'; import { NotSupported } from '../checkflux'; import SourceLink from '../common/Link'; +import { + BucketRepository, + ExternalArtifact, + GitRepository, + HelmChart, + HelmRepository, + OCIRepository, +} from '../common/Resources'; import Table, { TableProps } from '../common/Table'; -const sourceGroup = 'source.toolkit.fluxcd.io'; - -export function externalArtifactClass() { - return makeCustomResourceClass({ - apiInfo: [{ group: sourceGroup, version: 'v1' }], - isNamespaced: true, - singularName: 'ExternalArtifact', - pluralName: 'externalartifacts', - }); -} - -export function gitRepositoryClass() { - return makeCustomResourceClass({ - apiInfo: [{ group: sourceGroup, version: 'v1' }], - isNamespaced: true, - singularName: 'GitRepository', - pluralName: 'gitrepositories', - }); -} - -export function ociRepositoryClass() { - return makeCustomResourceClass({ - apiInfo: [{ group: sourceGroup, version: 'v1beta2' }], - isNamespaced: true, - singularName: 'OCIRepository', - pluralName: 'ocirepositories', - }); -} - -export function bucketRepositoryClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: sourceGroup, version: 'v1' }, - { group: sourceGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'Bucket', - pluralName: 'buckets', - }); -} - -export function helmRepositoryClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: sourceGroup, version: 'v1' }, - { group: sourceGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'HelmRepository', - pluralName: 'helmrepositories', - }); -} - -export function helmChartClass() { - return makeCustomResourceClass({ - apiInfo: [ - { group: sourceGroup, version: 'v1' }, - { group: sourceGroup, version: 'v1beta2' }, - ], - isNamespaced: true, - singularName: 'HelmChart', - pluralName: 'helmcharts', - }); -} - export function FluxSources() { return ( <> - + - + ); } @@ -110,10 +52,7 @@ interface FluxSourceCustomResourceRendererProps { function FluxSource(props: FluxSourceCustomResourceRendererProps) { const filterFunction = useFilterFunc(); const { resourceClass, title, pluralName } = props; - const [resources, setResources] = React.useState(null); - const [error, setError] = React.useState(null); - - resourceClass.useApiList(setResources, setError); + const [resources, error] = resourceClass.useList(); function prepareColumns() { const columns: TableProps['columns'] = [ diff --git a/flux/src/sources/SourceSingle.tsx b/flux/src/sources/SourceSingle.tsx index 019c39517..14b23faff 100644 --- a/flux/src/sources/SourceSingle.tsx +++ b/flux/src/sources/SourceSingle.tsx @@ -16,16 +16,9 @@ import { import Flux404 from '../checkflux'; import Link from '../common/Link'; import RemainingTimeDisplay from '../common/RemainingTimeDisplay'; +import { getSourceClassByPluralName } from '../common/Resources'; import StatusLabel from '../common/StatusLabel'; import { ObjectEvents } from '../helpers/index'; -import { - bucketRepositoryClass, - externalArtifactClass, - gitRepositoryClass, - helmChartClass, - helmRepositoryClass, - ociRepositoryClass, -} from './SourceList'; export function FluxSourceDetailView(props: { name?: string; @@ -43,24 +36,7 @@ export function FluxSourceDetailView(props: { pluralName = params.pluralName, } = props; - const resourceClass = (() => { - switch (pluralName) { - case 'externalartifacts': - return externalArtifactClass(); - case 'gitrepositories': - return gitRepositoryClass(); - case 'ocirepositories': - return ociRepositoryClass(); - case 'buckets': - return bucketRepositoryClass(); - case 'helmrepositories': - return helmRepositoryClass(); - case 'helmcharts': - return helmChartClass(); - default: - return null; - } - })(); + const resourceClass = getSourceClassByPluralName(pluralName); if (!resourceClass) { return ;