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:
parent
da010f3d08
commit
ea228c3d6d
276 changed files with 9241 additions and 3361 deletions
|
@ -1,6 +1,9 @@
|
|||
import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { HardDrive } from 'lucide-react';
|
||||
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
import { humanize } from '@/portainer/filters/filters';
|
||||
|
||||
import { TableSettingsMenu } from '@@/datatables';
|
||||
import {
|
||||
BasicTableSettings,
|
||||
|
@ -9,11 +12,12 @@ import {
|
|||
} from '@@/datatables/types';
|
||||
import { useTableStateWithStorage } from '@@/datatables/useTableState';
|
||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
|
||||
import { buildExpandColumn } from '@@/datatables/expand-column';
|
||||
import { Link } from '@@/Link';
|
||||
|
||||
import { useAllStoragesQuery } from '../queries/useVolumesQuery';
|
||||
|
||||
import { StorageClassViewModel } from './types';
|
||||
|
||||
interface TableSettings extends BasicTableSettings, RefreshableTableSettings {}
|
||||
|
@ -27,16 +31,11 @@ const columns = [
|
|||
}),
|
||||
helper.accessor('size', {
|
||||
header: 'Usage',
|
||||
cell: ({ row: { original: item } }) => <>{humanize(item.size)}</>,
|
||||
}),
|
||||
];
|
||||
|
||||
export function StorageDatatable({
|
||||
dataset,
|
||||
onRefresh,
|
||||
}: {
|
||||
dataset: Array<StorageClassViewModel>;
|
||||
onRefresh: () => void;
|
||||
}) {
|
||||
export function StorageDatatable() {
|
||||
const tableState = useTableStateWithStorage<TableSettings>(
|
||||
'kubernetes.volumes.storages',
|
||||
'Name',
|
||||
|
@ -45,17 +44,21 @@ export function StorageDatatable({
|
|||
})
|
||||
);
|
||||
|
||||
useRepeater(tableState.autoRefreshRate, onRefresh);
|
||||
const envId = useEnvironmentId();
|
||||
const storagesQuery = useAllStoragesQuery(envId, {
|
||||
refetchInterval: tableState.autoRefreshRate * 1000,
|
||||
});
|
||||
const storages = storagesQuery.data ?? [];
|
||||
|
||||
return (
|
||||
<ExpandableDatatable
|
||||
noWidget
|
||||
disableSelect
|
||||
dataset={dataset}
|
||||
dataset={storages}
|
||||
columns={columns}
|
||||
title="Storage"
|
||||
titleIcon={HardDrive}
|
||||
settingsManager={tableState}
|
||||
isLoading={storagesQuery.isLoading}
|
||||
renderTableSettings={() => (
|
||||
<TableSettingsMenu>
|
||||
<TableSettingsMenuAutoRefresh
|
||||
|
@ -89,7 +92,7 @@ function SubRow({ item }: { item: StorageClassViewModel }) {
|
|||
{vol.PersistentVolumeClaim.Name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>{vol.PersistentVolumeClaim.Storage}</td>
|
||||
<td>{humanize(vol.PersistentVolumeClaim.Storage)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Database } from 'lucide-react';
|
||||
|
||||
import { useAuthorizations } from '@/react/hooks/useUser';
|
||||
import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
|
||||
import KubernetesVolumeHelper from '@/kubernetes/helpers/volumeHelper';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
|
@ -8,7 +8,6 @@ import { refreshableSettings } from '@@/datatables/types';
|
|||
import { Datatable, TableSettingsMenu } from '@@/datatables';
|
||||
import { useTableStateWithStorage } from '@@/datatables/useTableState';
|
||||
import { DeleteButton } from '@@/buttons/DeleteButton';
|
||||
import { useRepeater } from '@@/datatables/useRepeater';
|
||||
|
||||
import { systemResourcesSettings } from '../../datatables/SystemResourcesSettings';
|
||||
import { CreateFromManifestButton } from '../../components/CreateFromManifestButton';
|
||||
|
@ -18,19 +17,13 @@ import {
|
|||
} from '../../datatables/DefaultDatatableSettings';
|
||||
import { SystemResourceDescription } from '../../datatables/SystemResourceDescription';
|
||||
import { useNamespacesQuery } from '../../namespaces/queries/useNamespacesQuery';
|
||||
import { useAllVolumesQuery } from '../queries/useVolumesQuery';
|
||||
import { isSystemNamespace } from '../../namespaces/queries/useIsSystemNamespace';
|
||||
import { useDeleteVolumes } from '../queries/useDeleteVolumes';
|
||||
|
||||
import { VolumeViewModel } from './types';
|
||||
import { columns } from './columns';
|
||||
|
||||
export function VolumesDatatable({
|
||||
dataset,
|
||||
onRemove,
|
||||
onRefresh,
|
||||
}: {
|
||||
dataset: Array<VolumeViewModel>;
|
||||
onRemove(items: Array<VolumeViewModel>): void;
|
||||
onRefresh(): void;
|
||||
}) {
|
||||
export function VolumesDatatable() {
|
||||
const tableState = useTableStateWithStorage<TableSettings>(
|
||||
'kube-volumes',
|
||||
'Name',
|
||||
|
@ -40,40 +33,54 @@ export function VolumesDatatable({
|
|||
})
|
||||
);
|
||||
|
||||
const hasWriteAuth = useAuthorizations('K8sVolumesW', undefined, true);
|
||||
|
||||
useRepeater(tableState.autoRefreshRate, onRefresh);
|
||||
const { authorized: hasWriteAuth } = useAuthorizations(
|
||||
'K8sVolumesW',
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
|
||||
const envId = useEnvironmentId();
|
||||
const deleteVolumesMutation = useDeleteVolumes(envId);
|
||||
const namespaceListQuery = useNamespacesQuery(envId);
|
||||
const namespaces = namespaceListQuery.data ?? [];
|
||||
const volumesQuery = useAllVolumesQuery(envId, {
|
||||
refetchInterval: tableState.autoRefreshRate * 1000,
|
||||
});
|
||||
const volumes = volumesQuery.data ?? [];
|
||||
|
||||
const filteredDataset = tableState.showSystemResources
|
||||
? dataset
|
||||
: dataset.filter((item) => !isSystem(item));
|
||||
const filteredVolumes = tableState.showSystemResources
|
||||
? volumes
|
||||
: volumes.filter(
|
||||
(volume) =>
|
||||
!isSystemNamespace(volume.ResourcePool.Namespace.Name, namespaces)
|
||||
);
|
||||
|
||||
return (
|
||||
<Datatable
|
||||
noWidget
|
||||
data-cy="k8s-volumes-datatable"
|
||||
dataset={filteredDataset}
|
||||
isLoading={volumesQuery.isLoading || namespaceListQuery.isLoading}
|
||||
dataset={filteredVolumes}
|
||||
columns={columns}
|
||||
settingsManager={tableState}
|
||||
title="Volumes"
|
||||
titleIcon={Database}
|
||||
isRowSelectable={({ original: item }) =>
|
||||
hasWriteAuth &&
|
||||
!(isSystem(item) && !KubernetesVolumeHelper.isUsed(item))
|
||||
getRowId={(row) => row.PersistentVolumeClaim.Name}
|
||||
disableSelect={!hasWriteAuth}
|
||||
isRowSelectable={({ original: volume }) =>
|
||||
!isSystemNamespace(volume.ResourcePool.Namespace.Name, namespaces) &&
|
||||
!KubernetesVolumeHelper.isUsed(volume)
|
||||
}
|
||||
renderTableActions={(selectedItems) => (
|
||||
<>
|
||||
<Authorized authorizations="K8sVolumesW">
|
||||
<DeleteButton
|
||||
confirmMessage="Do you want to remove the selected volume(s)?"
|
||||
onConfirmed={() => onRemove(selectedItems)}
|
||||
onConfirmed={() => deleteVolumesMutation.mutate(selectedItems)}
|
||||
disabled={selectedItems.length === 0}
|
||||
isLoading={deleteVolumesMutation.isLoading}
|
||||
data-cy="k8s-volumes-delete-button"
|
||||
/>
|
||||
<CreateFromManifestButton data-cy="k8s-volumes-deploy-button" />
|
||||
</>
|
||||
</Authorized>
|
||||
)}
|
||||
renderTableSettings={() => (
|
||||
<TableSettingsMenu>
|
||||
|
@ -87,9 +94,4 @@ export function VolumesDatatable({
|
|||
}
|
||||
/>
|
||||
);
|
||||
|
||||
function isSystem(item: VolumeViewModel) {
|
||||
return !!namespaceListQuery.data?.[item.ResourcePool.Namespace.Name]
|
||||
.IsSystem;
|
||||
}
|
||||
}
|
||||
|
|
40
app/react/kubernetes/volumes/ListView/VolumesView.tsx
Normal file
40
app/react/kubernetes/volumes/ListView/VolumesView.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
import { Database, HardDrive } from 'lucide-react';
|
||||
|
||||
import { PageHeader } from '@@/PageHeader';
|
||||
import { WidgetTabs, Tab, findSelectedTabIndex } from '@@/Widget/WidgetTabs';
|
||||
|
||||
import { VolumesDatatable } from './VolumesDatatable';
|
||||
import { StorageDatatable } from './StorageDatatable';
|
||||
|
||||
export function VolumesView() {
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
name: 'Volumes',
|
||||
icon: Database,
|
||||
widget: <VolumesDatatable />,
|
||||
selectedTabParam: 'volumes',
|
||||
},
|
||||
{
|
||||
name: 'Storage',
|
||||
icon: HardDrive,
|
||||
widget: <StorageDatatable />,
|
||||
selectedTabParam: 'storage',
|
||||
},
|
||||
];
|
||||
|
||||
const currentTabIndex = findSelectedTabIndex(
|
||||
useCurrentStateAndParams(),
|
||||
tabs
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Volume list" breadcrumbs="Volumes" reload />
|
||||
<>
|
||||
<WidgetTabs tabs={tabs} currentTabIndex={currentTabIndex} />
|
||||
<div className="content">{tabs[currentTabIndex].widget}</div>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -15,6 +15,7 @@ import { helper } from './columns.helper';
|
|||
|
||||
export const name = helper.accessor('PersistentVolumeClaim.Name', {
|
||||
header: 'Name',
|
||||
id: 'Name',
|
||||
cell: NameCell,
|
||||
});
|
||||
|
||||
|
@ -23,10 +24,12 @@ export function NameCell({
|
|||
}: CellContext<VolumeViewModel, string>) {
|
||||
const envId = useEnvironmentId();
|
||||
const namespaceListQuery = useNamespacesQuery(envId);
|
||||
const isSystem =
|
||||
namespaceListQuery.data?.[item.ResourcePool.Namespace.Name].IsSystem;
|
||||
const isSystem = namespaceListQuery.data?.some(
|
||||
(namespace) =>
|
||||
namespace.Name === item.ResourcePool.Namespace.Name && namespace.IsSystem
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-x-1">
|
||||
<Link
|
||||
to="kubernetes.volumes.volume"
|
||||
params={{
|
||||
|
@ -45,6 +48,6 @@ export function NameCell({
|
|||
{!KubernetesVolumeHelper.isUsed(item) && <UnusedBadge />}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { StorageClass } from '@/kubernetes/models/storage-class/StorageClass';
|
||||
import { Volume } from '@/kubernetes/models/volume/Volume';
|
||||
|
||||
import { K8sVolOwningApplication } from '../types';
|
||||
|
||||
export interface VolumeViewModel {
|
||||
Applications: Array<{
|
||||
Name: string;
|
||||
}>;
|
||||
Applications: K8sVolOwningApplication[];
|
||||
PersistentVolumeClaim: {
|
||||
Name: string;
|
||||
storageClass: {
|
||||
Name: string;
|
||||
};
|
||||
Storage?: unknown;
|
||||
CreationDate: number;
|
||||
CreationDate?: string;
|
||||
ApplicationOwner?: string;
|
||||
};
|
||||
ResourcePool: {
|
||||
|
@ -22,6 +22,6 @@ export interface VolumeViewModel {
|
|||
}
|
||||
|
||||
export type StorageClassViewModel = StorageClass & {
|
||||
size: 0;
|
||||
size: number;
|
||||
Volumes: Array<Volume>;
|
||||
};
|
||||
|
|
16
app/react/kubernetes/volumes/queries/query-keys.ts
Normal file
16
app/react/kubernetes/volumes/queries/query-keys.ts
Normal 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',
|
||||
],
|
||||
};
|
60
app/react/kubernetes/volumes/queries/useDeleteVolumes.ts
Normal file
60
app/react/kubernetes/volumes/queries/useDeleteVolumes.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
160
app/react/kubernetes/volumes/queries/useVolumesQuery.ts
Normal file
160
app/react/kubernetes/volumes/queries/useVolumesQuery.ts
Normal 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: [],
|
||||
}));
|
||||
}
|
57
app/react/kubernetes/volumes/types.ts
Normal file
57
app/react/kubernetes/volumes/types.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import {
|
||||
PersistentVolumeSpec,
|
||||
PersistentVolumeClaimSpec,
|
||||
PersistentVolumeClaimStatus,
|
||||
ObjectReference,
|
||||
CSIPersistentVolumeSource,
|
||||
} from 'kubernetes-types/core/v1';
|
||||
|
||||
export interface K8sVolumeInfo {
|
||||
persistentVolume: K8sPersistentVolume;
|
||||
persistentVolumeClaim: K8sPersistentVolumeClaim;
|
||||
storageClass: K8sStorageClass;
|
||||
}
|
||||
|
||||
interface K8sPersistentVolume {
|
||||
name?: string;
|
||||
annotations?: { [key: string]: string };
|
||||
accessModes?: PersistentVolumeSpec['accessModes'];
|
||||
capacity: PersistentVolumeSpec['capacity'];
|
||||
claimRef?: ObjectReference;
|
||||
storageClassName?: string;
|
||||
persistentVolumeReclaimPolicy: PersistentVolumeSpec['persistentVolumeReclaimPolicy'];
|
||||
volumeMode?: PersistentVolumeSpec['volumeMode'];
|
||||
csi?: CSIPersistentVolumeSource;
|
||||
}
|
||||
|
||||
interface K8sPersistentVolumeClaim {
|
||||
id: string;
|
||||
name: string;
|
||||
namespace: string;
|
||||
storage: number;
|
||||
creationDate: string;
|
||||
accessModes?: PersistentVolumeClaimSpec['accessModes'];
|
||||
volumeName: string;
|
||||
resourcesRequests?: PersistentVolumeClaimSpec['resources'];
|
||||
storageClass?: string;
|
||||
volumeMode?: PersistentVolumeClaimSpec['volumeMode'];
|
||||
owningApplications?: K8sVolOwningApplication[];
|
||||
phase: PersistentVolumeClaimStatus['phase'];
|
||||
}
|
||||
|
||||
interface K8sStorageClass {
|
||||
name: string;
|
||||
provisioner: string;
|
||||
reclaimPolicy?: PersistentVolumeSpec['persistentVolumeReclaimPolicy'];
|
||||
allowVolumeExpansion?: boolean;
|
||||
}
|
||||
|
||||
export interface K8sVolOwningApplication {
|
||||
Uid?: string;
|
||||
Name: string;
|
||||
Namespace?: string;
|
||||
Kind?: string;
|
||||
Labels?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export type Volumes = Record<string, K8sVolumeInfo>;
|
|
@ -1,46 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { PersistentVolumeClaimList } from 'kubernetes-types/core/v1';
|
||||
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { withError } from '@/react-tools/react-query';
|
||||
import axios from '@/portainer/services/axios';
|
||||
|
||||
import { parseKubernetesAxiosError } from '../axiosError';
|
||||
|
||||
// useQuery to get a list of all persistent volume claims from an array of namespaces
|
||||
export function usePVCsQuery(
|
||||
environmentId: EnvironmentId,
|
||||
namespaces?: string[]
|
||||
) {
|
||||
return useQuery(
|
||||
['environments', environmentId, 'kubernetes', 'pvcs'],
|
||||
async () => {
|
||||
if (!namespaces) {
|
||||
return [];
|
||||
}
|
||||
const pvcs = await Promise.all(
|
||||
namespaces?.map((namespace) => getPVCs(environmentId, namespace))
|
||||
);
|
||||
return pvcs.flat();
|
||||
},
|
||||
{
|
||||
...withError('Unable to retrieve perrsistent volume claims'),
|
||||
enabled: !!namespaces,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// get all persistent volume claims for a namespace
|
||||
export async function getPVCs(environmentId: EnvironmentId, namespace: string) {
|
||||
try {
|
||||
const { data } = await axios.get<PersistentVolumeClaimList>(
|
||||
`/endpoints/${environmentId}/kubernetes/api/v1/namespaces/${namespace}/persistentvolumeclaims`
|
||||
);
|
||||
return data.items;
|
||||
} catch (e) {
|
||||
throw parseKubernetesAxiosError(
|
||||
e,
|
||||
'Unable to retrieve persistent volume claims'
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue