diff --git a/frontend/public/components/daemon-set.jsx b/frontend/public/components/daemon-set.jsx
index 3c28546152a..b693c7c62c3 100644
--- a/frontend/public/components/daemon-set.jsx
+++ b/frontend/public/components/daemon-set.jsx
@@ -120,7 +120,7 @@ const Details = ({obj: daemonset}) =>
-
+
;
diff --git a/frontend/public/components/deployment-config.tsx b/frontend/public/components/deployment-config.tsx
index a63ccf8bcff..7b50d7d1383 100644
--- a/frontend/public/components/deployment-config.tsx
+++ b/frontend/public/components/deployment-config.tsx
@@ -155,7 +155,7 @@ export const DeploymentConfigsDetails: React.FC<{obj: K8sResourceKind}> = ({obj:
-
+
diff --git a/frontend/public/components/deployment.tsx b/frontend/public/components/deployment.tsx
index bd20250cf32..6e228e67b79 100644
--- a/frontend/public/components/deployment.tsx
+++ b/frontend/public/components/deployment.tsx
@@ -114,7 +114,7 @@ const DeploymentDetails: React.FC = ({obj: deployment})
-
+
diff --git a/frontend/public/components/modals/index.ts b/frontend/public/components/modals/index.ts
index 7c1352c5746..55cd9664bdf 100644
--- a/frontend/public/components/modals/index.ts
+++ b/frontend/public/components/modals/index.ts
@@ -63,5 +63,8 @@ export const installPlanPreviewModal = (props) => import('./installplan-preview-
export const expandPVCModal = (props) => import('./expand-pvc-modal' /* webpackChunkName: "expand-pvc-modal" */)
.then(m => m.expandPVCModal(props));
+export const removeVolumeModal = (props) => import('./remove-volume-modal' /* webpackChunkName: "remove-volume-modal" */)
+ .then(m => m.removeVolumeModal(props));
+
export const configureMachineAutoscalerModal = (props) => import('./configure-machine-autoscaler-modal' /* webpackChunkName: "configure-machine-autoscaler-modal" */)
.then(m => m.configureMachineAutoscalerModal(props));
diff --git a/frontend/public/components/modals/remove-volume-modal.tsx b/frontend/public/components/modals/remove-volume-modal.tsx
new file mode 100644
index 00000000000..6566f19ad4c
--- /dev/null
+++ b/frontend/public/components/modals/remove-volume-modal.tsx
@@ -0,0 +1,86 @@
+import * as _ from 'lodash-es';
+import * as React from 'react';
+
+import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory';
+import { ContainerSpec, getVolumeType, K8sKind, k8sPatch, K8sResourceKind, Volume, VolumeMount } from '../../module/k8s/';
+import { RowVolumeData } from '../volumes-table';
+
+export const RemoveVolumeModal: React.FC
= (props) => {
+ const [inProgress, setInProgress] = React.useState(false);
+ const [errorMessage, setErrorMessage] = React.useState('');
+
+ const getRemoveVolumePatch = (resource: K8sResourceKind, rowVolumeData: RowVolumeData) => {
+ const containers: ContainerSpec[] = _.get(resource, 'spec.template.spec.containers', []);
+ const patches = [];
+ let allowRemoveVolume = true;
+ containers.forEach((container: ContainerSpec, i: number) => {
+ const mounts: VolumeMount[] = _.get(container, 'volumeMounts', []);
+ mounts.forEach((mount: VolumeMount, j: number) => {
+ if (mount.name !== rowVolumeData.name) {
+ return;
+ }
+ if (mount.mountPath === rowVolumeData.mountPath) {
+ patches.push({op: 'remove', path: `/spec/template/spec/containers/${i}/volumeMounts/${j}`});
+ } else {
+ allowRemoveVolume = false;
+ }
+ });
+ });
+
+ // if the mountCount is greater than zero, then the volume is still being used at a different mount point or in a different container
+ // Either way, we cannot give the cmd to remove it
+ if (allowRemoveVolume) {
+ const volumes: Volume[] = _.get(resource, 'spec.template.spec.volumes', []);
+ const volumeIndex = volumes.findIndex((v: Volume) => v.name === rowVolumeData.volumeDetail.name);
+ patches.push({op: 'remove', path: `/spec/template/spec/volumes/${volumeIndex}`});
+ }
+ return patches;
+ };
+
+ const submit = (event: React.FormEvent) => {
+ event.preventDefault();
+ setErrorMessage('');
+ setInProgress(true);
+ const { kind, resource, volume } = props;
+ k8sPatch(kind, resource, getRemoveVolumePatch(resource, volume)).then(() => {
+ setInProgress(false);
+ props.close();
+ }).catch(({message: errMessage}) => {
+ setErrorMessage(errMessage);
+ setInProgress(false);
+ });
+ };
+
+ const {kind, resource, volume} = props;
+ const type: string = _.get(getVolumeType(volume.volumeDetail), 'id', '');
+ return ;
+};
+
+export const removeVolumeModal = createModalLauncher(RemoveVolumeModal);
+
+export type RemoveVolumeModalProps = {
+ cancel: (e: Event) => void;
+ close: () => void;
+ volume: RowVolumeData;
+ kind: K8sKind;
+ resource: K8sResourceKind;
+};
diff --git a/frontend/public/components/pod.tsx b/frontend/public/components/pod.tsx
index 31e1d1a2460..30585d55c55 100644
--- a/frontend/public/components/pod.tsx
+++ b/frontend/public/components/pod.tsx
@@ -276,7 +276,7 @@ const Details: React.FC = ({obj: pod}) => {
-
+
;
};
diff --git a/frontend/public/components/replicaset.jsx b/frontend/public/components/replicaset.jsx
index fda16fe0bb7..21ddc3d903a 100644
--- a/frontend/public/components/replicaset.jsx
+++ b/frontend/public/components/replicaset.jsx
@@ -50,7 +50,7 @@ const Details = ({obj: replicaSet}) => {
-
+
;
};
diff --git a/frontend/public/components/replication-controller.jsx b/frontend/public/components/replication-controller.jsx
index ed771b54d45..50d2dfab326 100644
--- a/frontend/public/components/replication-controller.jsx
+++ b/frontend/public/components/replication-controller.jsx
@@ -44,7 +44,7 @@ const Details = ({obj: replicationController}) => {
-
+
;
};
diff --git a/frontend/public/components/stateful-set.jsx b/frontend/public/components/stateful-set.jsx
index c74afd724f0..a5e919c8d25 100644
--- a/frontend/public/components/stateful-set.jsx
+++ b/frontend/public/components/stateful-set.jsx
@@ -50,7 +50,7 @@ const Details = ({obj: ss}) =>
-
+
;
diff --git a/frontend/public/components/volumes-table.tsx b/frontend/public/components/volumes-table.tsx
index cd201fc1ad7..bb0564ef98b 100644
--- a/frontend/public/components/volumes-table.tsx
+++ b/frontend/public/components/volumes-table.tsx
@@ -5,28 +5,47 @@ import * as classNames from 'classnames';
import {
ContainerSpec,
+ getVolumeType,
+ getVolumeLocation,
+ K8sKind,
+ K8sResourceKind,
+ K8sResourceKindReference,
PodKind,
PodTemplate,
Volume,
VolumeMount,
} from '../module/k8s';
-import {
- getVolumeType,
- getVolumeLocation,
-} from '../module/k8s/pods';
-import {
- VolumeIcon,
- ResourceIcon,
- EmptyBox,
- SectionHeading,
-} from './utils';
+import { asAccessReview, EmptyBox, Kebab, KebabOption, VolumeIcon, ResourceIcon, SectionHeading } from './utils';
+import { Table, TableData, TableRow } from './factory';
+import { sortable } from '@patternfly/react-table';
+import { removeVolumeModal } from './modals';
+import {connectToModel} from '../kinds';
+
+const removeVolume = (kind: K8sKind, obj: K8sResourceKind, volume: RowVolumeData): KebabOption => {
+ return {
+ label: 'Remove Volume',
+ callback: () => removeVolumeModal({
+ kind,
+ resource: obj,
+ volume,
+ }),
+ accessReview: asAccessReview(kind, obj, 'patch'),
+ };
+};
+
+const menuActions = [removeVolume];
+
+const getPodTemplate = (resource: K8sResourceKind): PodTemplate => {
+ return resource.kind === 'Pod' ? resource as PodKind : resource.spec.template;
+};
-const getVolumeMountsByPermissions = (pod: PodTemplate): VolumeData[] => {
+const getRowVolumeData = (resource: K8sResourceKind): RowVolumeData[] => {
+ const pod: PodTemplate = getPodTemplate(resource);
if (!pod || !pod.spec || !pod.spec.volumes) {
return [];
}
- const m = {};
+ const m = {};
const volumes = (pod.spec.volumes || []).reduce((p, v: Volume) => {
p[v.name] = v;
return p;
@@ -34,15 +53,12 @@ const getVolumeMountsByPermissions = (pod: PodTemplate): VolumeData[] => {
_.forEach(pod.spec.containers, (c: ContainerSpec) => {
_.forEach(c.volumeMounts, (v: VolumeMount) => {
- const k = `${v.name}_${v.readOnly ? 'ro' : 'rw'}`;
+ const k = `${v.name}_${v.readOnly ? 'ro' : 'rw'}_${v.mountPath}`;
const mount = {container: c.name, mountPath: v.mountPath, subPath: v.subPath};
- if (k in m) {
- return m[k].mounts.push(mount);
- }
- m[k] = {mounts: [mount], name: v.name, readOnly: !!v.readOnly, volume: volumes[v.name]};
+ m[k] = {name: v.name, readOnly: !!v.readOnly, volumeDetail: volumes[v.name],
+ container: mount.container, mountPath: mount.mountPath, subPath: mount.subPath,resource};
});
});
-
return _.values(m);
};
@@ -52,70 +68,125 @@ const ContainerLink: React.FC = ({name, pod}) => ;
ContainerLink.displayName = 'ContainerLink';
-const VolumeRow: React.FC = ({value, pod}) => {
- const kind = _.get(getVolumeType(value.volume), 'id', '');
- const loc = getVolumeLocation(value.volume);
- const name = value.name;
- const permission = value.readOnly ? 'Read-only' : 'Read/Write';
-
- return
- {value.mounts.map((m: Mount, i: number) =>
-
-
{name}
-
{_.get(m, 'mountPath', '-')}
-
- {_.get(m, 'subPath', '-')}
-
-
-
- {loc && ` (${loc})`}
-
-
{permission}
-
- { _.get(pod, 'kind') === 'Pod'
- ?
- :
{m.container}
- }
-
-
- )}
-
;
+const volumeRoColumnClasses = [
+ classNames('col-lg-2', 'col-md-3', 'col-sm-4', 'col-xs-5'),
+ classNames('col-lg-2', 'col-md-3', 'col-sm-4', 'col-xs-7'),
+ classNames('col-lg-2', 'col-md-2', 'col-sm-4', 'hidden-xs'),
+ classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'),
+ classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'),
+ classNames('col-lg-2', 'hidden-md', 'hidden-sm', 'hidden-xs'),
+ Kebab.columnClass,
+];
+
+const VolumesTableHeader = () => {
+ return [
+ {
+ title: 'Name', sortField: 'name', transforms: [sortable],
+ props: { className: volumeRoColumnClasses[0]},
+ },
+ {
+ title: 'Mount Path', sortField: 'mountPath', transforms: [sortable],
+ props: { className: volumeRoColumnClasses[1]},
+ },
+ {
+ title: 'SubPath', sortField: 'subPath', transforms: [sortable],
+ props: { className: volumeRoColumnClasses[2]},
+ },
+ {
+ title: 'Type',
+ props: { className: volumeRoColumnClasses[3]},
+ },
+ {
+ title: 'Permissions', sortField: 'readOnly', transforms: [sortable],
+ props: { className: volumeRoColumnClasses[4]},
+ },
+ {
+ title: 'Utilized By', sortField: 'container', transforms: [sortable],
+ props: { className: volumeRoColumnClasses[5]},
+ },
+ {
+ title: '',
+ props: { className: volumeRoColumnClasses[6]},
+ },
+ ];
+};
+VolumesTableHeader.displayName = 'VolumesTableHeader';
+
+const VolumesTableRow = ({obj: volume, index, key, style}) => {
+ const type = _.get(getVolumeType(volume.volumeDetail), 'id', '');
+ const loc = getVolumeLocation(volume.volumeDetail);
+ const name = volume.name;
+ const permission = volume.readOnly ? 'Read-only' : 'Read/Write';
+ const { resource } = volume;
+ const pod: PodTemplate = getPodTemplate(resource);
+
+ return (
+
+ {name}
+ {volume.mountPath}
+ {volume.subPath}
+
+
+ {loc && ` (${loc})`}
+
+ {permission}
+
+ {_.get(pod, 'kind') === 'Pod' ? : {volume.container}
}
+
+
+
+
+
+ );
};
-VolumeRow.displayName = 'VolumeRow';
+VolumesTableRow.displayName = 'VolumesTableRow';
-export const VolumesTable: React.FC = ({podTemplate, heading}) => (
-
- {heading && }
- {_.isEmpty(podTemplate.spec.volumes)
+export const VolumesTable = props => {
+ const { resource, ...tableProps } = props;
+ const data: RowVolumeData[] = getRowVolumeData(resource);
+ const pod: PodTemplate = getPodTemplate(resource);
+ return
+ {props.heading && }
+ {_.isEmpty(pod.spec.volumes)
?
: (
-
-
-
Name
-
Mount Path
-
SubPath
-
Type
-
Permissions
-
Utilized By
-
-
- {getVolumeMountsByPermissions(podTemplate).map((v, i) => )}
-
-
+
)}
-
-);
+ ;
+};
+
VolumesTable.displayName = 'VolumesTable';
-type Mount = {
- container: string;
-} & VolumeMount;
+const VolumeKebab = connectToModel((props: VolumeKebabProps) => {
+ const {actions, kindObj, resource, isDisabled, rowVolumeData} = props;
+ if (!kindObj || kindObj.kind === 'Pod') {
+ return null;
+ }
+ const options = actions.map(b => b(kindObj, resource, rowVolumeData));
+ return ;
+});
+
+type VolumeKebabAction = (kind: K8sKind, obj: K8sResourceKind, rowVolumeData: RowVolumeData) => KebabOption;
+type VolumeKebabProps = {
+ kindObj: K8sKind;
+ actions: VolumeKebabAction[];
+ kind: K8sResourceKindReference;
+ resource: K8sResourceKind;
+ isDisabled?: boolean;
+ rowVolumeData: RowVolumeData;
+};
-type VolumeData = {
+export type RowVolumeData = {
name: string;
readOnly: boolean;
- volume: Volume;
- mounts: Mount[];
+ volumeDetail: Volume;
+ container: string;
+ mountPath: string;
+ subPath?: string;
+ resource: K8sResourceKind;
};
type ContainerLinkProps = {
@@ -123,12 +194,3 @@ type ContainerLinkProps = {
name: string;
};
-type VolumeRowProps = {
- pod: PodKind | PodTemplate;
- value: VolumeData;
-};
-
-type VolumesTableProps = {
- podTemplate: PodKind | PodTemplate;
- heading?: string;
-};