1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-19 13:29:41 +02:00

feat(helm): helm actions [r8s-259] (#715)

Co-authored-by: James Player <james.player@portainer.io>
Co-authored-by: Cara Ryan <cara.ryan@portainer.io>
Co-authored-by: stevensbkang <skan070@gmail.com>
This commit is contained in:
Ali 2025-05-13 22:15:04 +12:00 committed by GitHub
parent dfa32b6755
commit 4ee349bd6b
117 changed files with 4161 additions and 696 deletions

View file

@ -1,32 +1,184 @@
import { useState } from 'react';
import { compact } from 'lodash';
import { useCurrentStateAndParams, useRouter } from '@uirouter/react';
import { AlertTriangle } from 'lucide-react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useEvents } from '@/react/kubernetes/queries/useEvents';
import { NavTabs, Option } from '@@/NavTabs';
import { Badge } from '@@/Badge';
import { Icon } from '@@/Icon';
import { HelmRelease } from '../../types';
import { useHelmHistory } from '../queries/useHelmHistory';
import { ManifestDetails } from './ManifestDetails';
import { NotesDetails } from './NotesDetails';
import { ValuesDetails } from './ValuesDetails';
import { ResourcesTable } from './ResourcesTable/ResourcesTable';
import { DiffControl, DiffViewMode } from './DiffControl';
import { useHelmReleaseToCompare } from './useHelmReleaseToCompare';
import {
filterRelatedEvents,
HelmEventsDatatable,
useHelmEventsTableState,
} from './HelmEventsDatatable';
type Props = {
release: HelmRelease;
selectedRevision: number;
};
type Tab = 'values' | 'notes' | 'manifest' | 'resources';
type Tab = 'values' | 'notes' | 'manifest' | 'resources' | 'events';
export function ReleaseTabs({ release, selectedRevision }: Props) {
const {
params: { tab },
} = useCurrentStateAndParams();
const router = useRouter();
const environmentId = useEnvironmentId();
// state is here so that the state isn't lost when the tab changes
const [isUserSupplied, setIsUserSupplied] = useState(true);
// start with NaN so that the input is empty (see <Input /> for more details)
const [selectedCompareRevisionNumber, setSelectedCompareRevisionNumber] =
useState(NaN);
const [diffViewMode, setDiffViewMode] = useState<DiffViewMode>('view');
const historyQuery = useHelmHistory(
environmentId,
release.name,
release.namespace ?? ''
);
const earliestRevisionNumber =
historyQuery.data?.[historyQuery.data.length - 1]?.version ??
release.version ??
1;
const latestRevisionNumber =
historyQuery.data?.[0]?.version ?? release.version ?? 1;
const { compareRelease, isCompareReleaseLoading, isCompareReleaseError } =
useHelmReleaseToCompare(
release,
earliestRevisionNumber,
latestRevisionNumber,
diffViewMode,
selectedRevision,
selectedCompareRevisionNumber
);
const { autoRefreshRate } = useHelmEventsTableState();
const { data: eventWarningCount } = useEvents(environmentId, {
namespace: release.namespace ?? '',
queryOptions: {
autoRefreshRate: autoRefreshRate * 1000,
select: (data) => {
const relatedEvents = filterRelatedEvents(
data,
release.info?.resources ?? []
);
return relatedEvents.filter((e) => e.type === 'Warning').length;
},
},
});
return (
<NavTabs<Tab>
onSelect={setTab}
selectedId={parseValidTab(tab, !!release.info?.notes)}
type="pills"
justified
options={helmTabs(
release,
isUserSupplied,
setIsUserSupplied,
earliestRevisionNumber,
latestRevisionNumber,
selectedRevision,
selectedCompareRevisionNumber,
setSelectedCompareRevisionNumber,
diffViewMode,
handleDiffViewChange,
isCompareReleaseLoading,
isCompareReleaseError,
eventWarningCount ?? 0,
compareRelease
)}
/>
);
function handleDiffViewChange(diffViewMode: DiffViewMode) {
setDiffViewMode(diffViewMode);
if (latestRevisionNumber === earliestRevisionNumber) {
return;
}
// if the input for compare revision number is NaN, set it to the previous revision number
if (
Number.isNaN(selectedCompareRevisionNumber) &&
diffViewMode === 'specific'
) {
if (selectedRevision > earliestRevisionNumber) {
setSelectedCompareRevisionNumber(selectedRevision - 1);
return;
}
// it could be useful to compare to the latest revision number if the selected revision number is the earliest revision number
setSelectedCompareRevisionNumber(latestRevisionNumber);
}
}
function setTab(tab: Tab) {
router.stateService.go('kubernetes.helm', {
tab,
});
}
}
function helmTabs(
release: HelmRelease,
isUserSupplied: boolean,
setIsUserSupplied: (isUserSupplied: boolean) => void
setIsUserSupplied: (isUserSupplied: boolean) => void,
earliestRevisionNumber: number,
latestRevisionNumber: number,
selectedRevisionNumber: number,
compareRevisionNumber: number,
setCompareRevisionNumber: (compareRevisionNumber: number) => void,
diffViewMode: DiffViewMode,
setDiffViewMode: (diffViewMode: DiffViewMode) => void,
isCompareReleaseLoading: boolean,
isCompareReleaseError: boolean,
eventWarningCount: number,
compareRelease?: HelmRelease
): Option<Tab>[] {
// as long as the latest revision number is greater than the earliest revision number, there are changes to compare
const showDiffControl = latestRevisionNumber > earliestRevisionNumber;
return compact([
{
label: 'Resources',
id: 'resources',
children: <ResourcesTable />,
},
{
label: (
<>
Events
{eventWarningCount >= 1 && (
<Badge type="warnSecondary">
<Icon icon={AlertTriangle} className="!mr-1" />
{eventWarningCount}
</Badge>
)}
</>
),
id: 'events',
children: (
<HelmEventsDatatable
namespace={release.namespace ?? ''}
releaseResources={release.info?.resources ?? []}
/>
),
},
{
label: 'Values',
id: 'values',
@ -34,35 +186,97 @@ function helmTabs(
<ValuesDetails
values={release.values}
isUserSupplied={isUserSupplied}
setIsUserSupplied={setIsUserSupplied}
selectedRevisionNumber={selectedRevisionNumber}
diffViewMode={diffViewMode}
compareValues={compareRelease?.values}
compareRevisionNumberFetched={compareRelease?.version}
isCompareReleaseLoading={isCompareReleaseLoading}
isCompareReleaseError={isCompareReleaseError}
diffControl={
<DiffControl
selectedRevisionNumber={selectedRevisionNumber}
latestRevisionNumber={latestRevisionNumber}
earliestRevisionNumber={earliestRevisionNumber}
compareRevisionNumber={compareRevisionNumber}
setCompareRevisionNumber={setCompareRevisionNumber}
diffViewMode={diffViewMode}
setDiffViewMode={setDiffViewMode}
isUserSupplied={isUserSupplied}
setIsUserSupplied={setIsUserSupplied}
showUserSuppliedCheckbox
/>
}
/>
),
},
{
label: 'Manifest',
id: 'manifest',
children: <ManifestDetails manifest={release.manifest} />,
children: (
<ManifestDetails
manifest={release.manifest}
selectedRevisionNumber={selectedRevisionNumber}
diffViewMode={diffViewMode}
compareManifest={compareRelease?.manifest}
compareRevisionNumberFetched={compareRelease?.version}
isCompareReleaseLoading={isCompareReleaseLoading}
isCompareReleaseError={isCompareReleaseError}
diffControl={
showDiffControl && (
<DiffControl
selectedRevisionNumber={selectedRevisionNumber}
latestRevisionNumber={latestRevisionNumber}
earliestRevisionNumber={earliestRevisionNumber}
compareRevisionNumber={compareRevisionNumber}
setCompareRevisionNumber={setCompareRevisionNumber}
diffViewMode={diffViewMode}
setDiffViewMode={setDiffViewMode}
/>
)
}
/>
),
},
!!release.info?.notes && {
label: 'Notes',
id: 'notes',
children: <NotesDetails notes={release.info.notes} />,
children: (
<NotesDetails
notes={release.info.notes}
selectedRevisionNumber={selectedRevisionNumber}
diffViewMode={diffViewMode}
compareNotes={compareRelease?.info?.notes}
compareRevisionNumberFetched={compareRelease?.version}
isCompareReleaseLoading={isCompareReleaseLoading}
isCompareReleaseError={isCompareReleaseError}
diffControl={
showDiffControl && (
<DiffControl
selectedRevisionNumber={selectedRevisionNumber}
latestRevisionNumber={latestRevisionNumber}
earliestRevisionNumber={earliestRevisionNumber}
compareRevisionNumber={compareRevisionNumber}
setCompareRevisionNumber={setCompareRevisionNumber}
diffViewMode={diffViewMode}
setDiffViewMode={setDiffViewMode}
/>
)
}
/>
),
},
]);
}
export function ReleaseTabs({ release }: Props) {
const [tab, setTab] = useState<Tab>('resources');
// state is here so that the state isn't lost when the tab changes
const [isUserSupplied, setIsUserSupplied] = useState(true);
return (
<NavTabs<Tab>
onSelect={setTab}
selectedId={tab}
type="pills"
justified
options={helmTabs(release, isUserSupplied, setIsUserSupplied)}
/>
);
function parseValidTab(tab: string, hasNotes: boolean): Tab {
if (
tab === 'values' ||
(tab === 'notes' && hasNotes) ||
tab === 'manifest' ||
tab === 'resources' ||
tab === 'events'
) {
return tab;
}
return 'resources';
}