1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

refactor(k8s): namespace core logic (#12142)

Co-authored-by: testA113 <aliharriss1995@gmail.com>
Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
Co-authored-by: James Carppe <85850129+jamescarppe@users.noreply.github.com>
Co-authored-by: Ali <83188384+testA113@users.noreply.github.com>
This commit is contained in:
Steven Kang 2024-10-01 14:15:51 +13:00 committed by GitHub
parent da010f3d08
commit ea228c3d6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
276 changed files with 9241 additions and 3361 deletions

View file

@ -0,0 +1,16 @@
import { EnvironmentId } from '@/react/portainer/environments/types';
export const queryKeys = {
volumes: (environmentId: EnvironmentId) => [
'environments',
environmentId,
'kubernetes',
'volumes',
],
storages: (environmentId: EnvironmentId) => [
'environments',
environmentId,
'kubernetes',
'storages',
],
};

View file

@ -0,0 +1,60 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import axios from '@/portainer/services/axios';
import { getAllSettledItems } from '@/portainer/helpers/promise-utils';
import { withGlobalError } from '@/react-tools/react-query';
import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { pluralize } from '@/portainer/helpers/strings';
import { parseKubernetesAxiosError } from '../../axiosError';
import { VolumeViewModel } from '../ListView/types';
import { queryKeys } from './query-keys';
export function useDeleteVolumes(environmentId: EnvironmentId) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (volumes: VolumeViewModel[]) =>
deleteVolumes(volumes, environmentId),
onSuccess: ({ fulfilledItems, rejectedItems }) => {
// one error notification per rejected item
rejectedItems.forEach(({ item, reason }) => {
notifyError(
`Failed to remove volume '${item.PersistentVolumeClaim.Name}'`,
new Error(reason)
);
});
// one success notification for all fulfilled items
if (fulfilledItems.length) {
notifySuccess(
`${pluralize(fulfilledItems.length, 'Volume')} successfully removed`,
fulfilledItems
.map((item) => item.PersistentVolumeClaim.Name)
.join(', ')
);
}
queryClient.invalidateQueries(queryKeys.storages(environmentId));
queryClient.invalidateQueries(queryKeys.volumes(environmentId));
},
...withGlobalError('Unable to remove volumes'),
});
}
function deleteVolumes(
volumes: VolumeViewModel[],
environmentId: EnvironmentId
) {
return getAllSettledItems(volumes, deleteVolume);
async function deleteVolume(volume: VolumeViewModel) {
try {
await axios.delete(
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${volume.ResourcePool.Namespace.Name}/persistentvolumeclaims/${volume.PersistentVolumeClaim.Name}`
);
} catch (error) {
throw parseKubernetesAxiosError(error, 'Unable to remove volume');
}
}
}

View file

@ -0,0 +1,160 @@
import { useQuery } from '@tanstack/react-query';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { humanize } from '@/portainer/filters/filters';
import { withGlobalError } from '@/react-tools/react-query';
import axios from '@/portainer/services/axios';
import { Volume } from '@/kubernetes/models/volume/Volume';
import { parseKubernetesAxiosError } from '../../axiosError';
import { K8sVolumeInfo } from '../types';
import { VolumeViewModel, StorageClassViewModel } from '../ListView/types';
import { queryKeys } from './query-keys';
// useQuery to get a list of all volumes in a cluster
export function useAllVolumesQuery(
environmentId: EnvironmentId,
queryOptions?: {
refetchInterval?: number;
}
) {
return useQuery(
queryKeys.volumes(environmentId),
() => getAllVolumes(environmentId, { withApplications: true }),
{
refetchInterval: queryOptions?.refetchInterval,
select: convertToVolumeViewModels,
...withGlobalError('Unable to retrieve volumes'),
}
);
}
// useQuery to get a list of all volumes in a cluster
export function useAllStoragesQuery(
environmentId: EnvironmentId,
queryOptions?: {
refetchInterval?: number;
}
) {
return useQuery(
queryKeys.storages(environmentId),
() => getAllVolumes(environmentId),
{
refetchInterval: queryOptions?.refetchInterval,
select: convertToStorageClassViewModels,
...withGlobalError('Unable to retrieve volumes'),
}
);
}
// get all volumes from a namespace
export async function getAllVolumes(
environmentId: EnvironmentId,
params?: { withApplications: boolean }
) {
try {
const { data } = await axios.get<K8sVolumeInfo[]>(
`/kubernetes/${environmentId}/volumes`,
{ params }
);
return data;
} catch (e) {
throw parseKubernetesAxiosError(e, 'Unable to retrieve volumes');
}
}
function convertToVolumeViewModels(
volumes: K8sVolumeInfo[]
): VolumeViewModel[] {
return volumes.map((volume) => {
const owningApplications =
volume.persistentVolumeClaim.owningApplications ?? [];
return {
Applications: owningApplications.map((app) => ({
Name: app.Name,
Namespace: app.Namespace,
Kind: app.Kind,
})),
PersistentVolumeClaim: {
Namespace: volume.persistentVolumeClaim.namespace,
Name: volume.persistentVolumeClaim.name,
storageClass: {
Name: volume.persistentVolumeClaim.storageClass || '',
},
Storage: humanize(volume.persistentVolumeClaim.storage),
CreationDate: volume.persistentVolumeClaim.creationDate,
ApplicationOwner:
volume.persistentVolumeClaim.owningApplications?.[0]?.Name,
},
ResourcePool: {
Namespace: {
Name: volume.persistentVolumeClaim.namespace,
},
},
};
});
}
function convertToStorageClassViewModels(
volumes: K8sVolumeInfo[]
): StorageClassViewModel[] {
const volumesModels = convertToVolumeModel(volumes);
// Use reduce to create a new Map
const storageClassMap = volumesModels.reduce((acc, volume) => {
const pvcStorageClass = volume.PersistentVolumeClaim.storageClass;
const storageClassName = pvcStorageClass?.Name || 'none';
const defaultStorageClass: StorageClassViewModel = {
Name: pvcStorageClass?.Name || 'none',
Provisioner: pvcStorageClass?.Provisioner ?? '',
ReclaimPolicy: pvcStorageClass?.ReclaimPolicy ?? '',
AllowVolumeExpansion: pvcStorageClass?.AllowVolumeExpansion || false,
size: 0,
Volumes: [],
};
const existingStorageClass =
acc.get(storageClassName) ?? defaultStorageClass;
// Create a new StorageClassViewModel with updated values
const updatedStorageClass = {
...existingStorageClass,
size:
existingStorageClass.size + (volume.PersistentVolumeClaim.Storage || 0),
Volumes: [...existingStorageClass.Volumes, volume],
};
// Return a new Map with the updated StorageClassViewModel
return new Map(acc).set(storageClassName, updatedStorageClass);
}, new Map<string, StorageClassViewModel>());
// Convert the Map values to an array
return Array.from(storageClassMap.values());
}
function convertToVolumeModel(volumes: K8sVolumeInfo[]): Volume[] {
return volumes.map((volume) => ({
PersistentVolumeClaim: {
Id: volume.persistentVolumeClaim.id,
Name: volume.persistentVolumeClaim.name,
PreviousName: '',
Namespace: volume.persistentVolumeClaim.namespace,
storageClass: {
Name: volume.persistentVolumeClaim.storageClass || '',
Provisioner: volume.storageClass.provisioner,
ReclaimPolicy: volume.storageClass.reclaimPolicy ?? '',
AllowVolumeExpansion: volume.storageClass.allowVolumeExpansion || false,
},
Storage: volume.persistentVolumeClaim.storage,
CreationDate: volume.persistentVolumeClaim.creationDate,
ApplicationOwner:
volume.persistentVolumeClaim.owningApplications?.[0]?.Name ?? '',
AccessModes: volume.persistentVolumeClaim.accessModes ?? [],
ApplicationName: '',
MountPath: '',
Yaml: '',
},
Applications: [],
}));
}