mirror of https://github.com/portainer/portainer
fix(apps): group helm apps together [r8s-102] (#24)
parent
1110f745e1
commit
57e10dc911
|
@ -31,9 +31,7 @@ import {
|
|||
KubernetesPodNodeAffinityPayload,
|
||||
KubernetesPreferredSchedulingTermPayload,
|
||||
} from 'Kubernetes/pod/payloads/affinities';
|
||||
|
||||
export const PodKubernetesInstanceLabel = 'app.kubernetes.io/instance';
|
||||
export const PodManagedByLabel = 'app.kubernetes.io/managed-by';
|
||||
import { PodKubernetesInstanceLabel, PodManagedByLabel } from '@/react/kubernetes/applications/constants';
|
||||
|
||||
class KubernetesApplicationHelper {
|
||||
/* #region UTILITY FUNCTIONS */
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { BoxIcon } from 'lucide-react';
|
||||
import { groupBy, partition } from 'lodash';
|
||||
|
||||
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
|
||||
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings';
|
||||
|
@ -10,6 +11,7 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|||
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
|
||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
import { isSystemNamespace } from '@/react/kubernetes/namespaces/queries/useIsSystemNamespace';
|
||||
import { KubernetesApplicationTypes } from '@/kubernetes/models/application/models/appConstants';
|
||||
|
||||
import { TableSettingsMenu } from '@@/datatables';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
|
@ -20,8 +22,9 @@ import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
|
|||
import { NamespaceFilter } from '../ApplicationsStacksDatatable/NamespaceFilter';
|
||||
import { Namespace } from '../ApplicationsStacksDatatable/types';
|
||||
import { useApplications } from '../../application.queries';
|
||||
import { PodKubernetesInstanceLabel, PodManagedByLabel } from '../../constants';
|
||||
|
||||
import { Application, ConfigKind } from './types';
|
||||
import { Application, ApplicationRowData, ConfigKind } from './types';
|
||||
import { useColumns } from './useColumns';
|
||||
import { getPublishedUrls } from './PublishedPorts';
|
||||
import { SubRow } from './SubRow';
|
||||
|
@ -70,7 +73,7 @@ export function ApplicationsDatatable({
|
|||
namespace,
|
||||
withDependencies: true,
|
||||
});
|
||||
const applications = applicationsQuery.data ?? [];
|
||||
const applications = useApplicationsRowData(applicationsQuery.data);
|
||||
const filteredApplications = showSystem
|
||||
? applications
|
||||
: applications.filter(
|
||||
|
@ -156,7 +159,74 @@ export function ApplicationsDatatable({
|
|||
);
|
||||
}
|
||||
|
||||
function isExpandable(item: Application) {
|
||||
function useApplicationsRowData(
|
||||
applications?: Application[]
|
||||
): ApplicationRowData[] {
|
||||
return useMemo(() => separateHelmApps(applications ?? []), [applications]);
|
||||
}
|
||||
|
||||
function separateHelmApps(applications: Application[]): ApplicationRowData[] {
|
||||
const [helmApps, nonHelmApps] = partition(
|
||||
applications,
|
||||
(app) =>
|
||||
app.Metadata?.labels &&
|
||||
app.Metadata.labels[PodKubernetesInstanceLabel] &&
|
||||
app.Metadata.labels[PodManagedByLabel] === 'Helm'
|
||||
);
|
||||
|
||||
const groupedHelmApps: Record<string, Application[]> = groupBy(
|
||||
helmApps,
|
||||
(app) => app.Metadata?.labels[PodKubernetesInstanceLabel] ?? ''
|
||||
);
|
||||
|
||||
// build the helm apps row data from the grouped helm apps
|
||||
const helmAppsRowData = Object.entries(groupedHelmApps).reduce<
|
||||
ApplicationRowData[]
|
||||
>((helmApps, [appName, apps]) => {
|
||||
const helmApp = buildHelmAppRowData(appName, apps);
|
||||
return [...helmApps, helmApp];
|
||||
}, []);
|
||||
|
||||
return [...helmAppsRowData, ...nonHelmApps];
|
||||
}
|
||||
|
||||
function buildHelmAppRowData(
|
||||
appName: string,
|
||||
apps: Application[]
|
||||
): ApplicationRowData {
|
||||
const id = `${apps[0].ResourcePool}-${appName
|
||||
.toLowerCase()
|
||||
.replaceAll(' ', '-')}`;
|
||||
const { earliestCreationDate, runningPods, totalPods } = apps.reduce(
|
||||
(acc, app) => ({
|
||||
earliestCreationDate:
|
||||
new Date(app.CreationDate) < new Date(acc.earliestCreationDate)
|
||||
? app.CreationDate
|
||||
: acc.earliestCreationDate,
|
||||
runningPods: acc.runningPods + app.RunningPodsCount,
|
||||
totalPods: acc.totalPods + app.TotalPodsCount,
|
||||
}),
|
||||
{
|
||||
earliestCreationDate: apps[0].CreationDate,
|
||||
runningPods: 0,
|
||||
totalPods: 0,
|
||||
}
|
||||
);
|
||||
const helmApp: ApplicationRowData = {
|
||||
...apps[0],
|
||||
Name: appName,
|
||||
Id: id,
|
||||
KubernetesApplications: apps,
|
||||
ApplicationType: KubernetesApplicationTypes.Helm,
|
||||
Status: runningPods < totalPods ? 'Not ready' : 'Ready',
|
||||
CreationDate: earliestCreationDate,
|
||||
RunningPodsCount: runningPods,
|
||||
TotalPodsCount: totalPods,
|
||||
};
|
||||
return helmApp;
|
||||
}
|
||||
|
||||
function isExpandable(item: ApplicationRowData) {
|
||||
return (
|
||||
!!item.KubernetesApplications ||
|
||||
!!getPublishedUrls(item).length ||
|
||||
|
|
|
@ -5,25 +5,26 @@ import { useCurrentUser } from '@/react/hooks/useUser';
|
|||
import { ConfigurationDetails } from './ConfigurationDetails';
|
||||
import { InnerTable } from './InnerTable';
|
||||
import { PublishedPorts } from './PublishedPorts';
|
||||
import { Application } from './types';
|
||||
import { ApplicationRowData } from './types';
|
||||
|
||||
export function SubRow({
|
||||
item,
|
||||
hideStacks,
|
||||
areSecretsRestricted,
|
||||
}: {
|
||||
item: Application;
|
||||
item: ApplicationRowData;
|
||||
hideStacks: boolean;
|
||||
areSecretsRestricted: boolean;
|
||||
}) {
|
||||
const {
|
||||
user: { Username: username },
|
||||
} = useCurrentUser();
|
||||
const colSpan = hideStacks ? 8 : 9;
|
||||
|
||||
return (
|
||||
<tr className={clsx({ 'secondary-body': !item.KubernetesApplications })}>
|
||||
<td />
|
||||
<td colSpan={8} className="datatable-padding-vertical">
|
||||
<td colSpan={colSpan} className="datatable-padding-vertical">
|
||||
{item.KubernetesApplications ? (
|
||||
<InnerTable
|
||||
dataset={item.KubernetesApplications}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
|
||||
import { Application } from './types';
|
||||
import { ApplicationRowData } from './types';
|
||||
|
||||
export const helper = createColumnHelper<Application>();
|
||||
export const helper = createColumnHelper<ApplicationRowData>();
|
||||
|
|
|
@ -7,14 +7,16 @@ import { SystemBadge } from '@@/Badge/SystemBadge';
|
|||
import { ExternalBadge } from '@@/Badge/ExternalBadge';
|
||||
|
||||
import { helper } from './columns.helper';
|
||||
import { Application } from './types';
|
||||
import { ApplicationRowData } from './types';
|
||||
|
||||
export const name = helper.accessor('Name', {
|
||||
header: 'Name',
|
||||
cell: Cell,
|
||||
});
|
||||
|
||||
function Cell({ row: { original: item } }: CellContext<Application, string>) {
|
||||
function Cell({
|
||||
row: { original: item },
|
||||
}: CellContext<ApplicationRowData, string>) {
|
||||
const isSystem = useIsSystemNamespace(item.ResourcePool);
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
|
||||
import styles from './columns.status.module.css';
|
||||
import { helper } from './columns.helper';
|
||||
import { Application } from './types';
|
||||
import { ApplicationRowData } from './types';
|
||||
|
||||
export const status = helper.accessor('Status', {
|
||||
header: 'Status',
|
||||
|
@ -16,7 +16,9 @@ export const status = helper.accessor('Status', {
|
|||
enableSorting: false,
|
||||
});
|
||||
|
||||
function Cell({ row: { original: item } }: CellContext<Application, string>) {
|
||||
function Cell({
|
||||
row: { original: item },
|
||||
}: CellContext<ApplicationRowData, string>) {
|
||||
if (
|
||||
item.ApplicationType === KubernetesApplicationTypes.Pod &&
|
||||
item.Pods &&
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { AppType, DeploymentType } from '../../types';
|
||||
|
||||
export interface ApplicationRowData extends Application {
|
||||
KubernetesApplications?: Array<Application>;
|
||||
}
|
||||
|
||||
export interface Application {
|
||||
Id: string;
|
||||
Name: string;
|
||||
|
@ -11,7 +15,6 @@ export interface Application {
|
|||
StackName?: string;
|
||||
ResourcePool: string;
|
||||
ApplicationType: AppType;
|
||||
KubernetesApplications?: Array<Application>;
|
||||
Metadata?: {
|
||||
labels: Record<string, string>;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,8 @@ export const appNoteAnnotation = 'io.portainer.kubernetes.application.note';
|
|||
export const appDeployMethodLabel = 'io.portainer.kubernetes.application.kind';
|
||||
export const defaultDeploymentUniqueLabel = 'pod-template-hash';
|
||||
export const appNameLabel = 'io.portainer.kubernetes.application.name';
|
||||
export const PodKubernetesInstanceLabel = 'app.kubernetes.io/instance';
|
||||
export const PodManagedByLabel = 'app.kubernetes.io/managed-by';
|
||||
|
||||
export const appRevisionAnnotation = 'deployment.kubernetes.io/revision';
|
||||
|
||||
|
|
Loading…
Reference in New Issue