frontend/src/components/PersistentVolumesCreateGuided.vue
<!-- VolumesCreateGuided.vue -->
<script lang="ts">
import { defineComponent, ref, computed } from 'vue';
import { SecretCreateOptions } from '../types/custom'
export default defineComponent({
name: 'VolumesCreateGuided',
props: {
show: {
type: Boolean,
required: true
},
cluster: {
type: Object as PropType<KubernetesCluster | null>,
required: false,
default: null
}
},
emits: ['close', 'create-volume'],
setup(props, { emit }) {
const name = ref('');
const namespace = ref('default');
const isSubmitting = ref(false);
const error = ref('');
const volumeData = ref([{ key: '', value: '', showValue: false }]);
const accessModes = ref(['ReadWriteConce']);
const hostPath = ref({ path: '', type: ''});
const nfs = ref({ server: '', path: '', readOnly: false });
const volumeType = ref('');
const volumeTypes = [
{
value: 'hostPath',
label: 'HostPath',
category: 'local',
description: 'Mount a file or directory from the host name (development only)',
},
{
value: 'nfs',
label: 'NFS',
category: 'network',
description: 'Network File System for shared storage across multiple nodes',
},
];
const isFormValid = computed(() => {
if (!name.value.trim()) return false;
if (!capacity.value || capacity.value <= 0) return false;
if (accessModes.value.length === 0)return false;
if (!volumeType.value) return false;
if (volumeType.value === 'hostPath') {
if (!hostPath.value.path.trim()) {
return false;
}
}
return true;
});
const getVolumeTypeLabel = () => {
const type = volumeTypes.find(t => t.value === volumeType.value);
return type ? type.label : '';
};
const selectVolumeType = (type: string) => {
volumeType.value = type;
error.value = '';
};
const addSecretData = () => {
volumeData.value.push({ key: '', value: '', showValue: false });
};
const removeSecretData = (index: number) => {
if (volumeData.value.length > 1) {
volumeData.value.splice(index, 1);
}
};
const resetForm = () => {
name.value = '';
namespace.value = 'default';
volumeData.value = [{ key: '', value: '' }];
error.value = '';
isSubmitting.value = false;
};
const closeModal = () => {
resetForm();
emit('close');
};
const toggleValueVisibility = (index: number) => {
volumeData.value[index].showValue = !volumeData.value[index].showValue;
};
const createPersistentVolume = () => {
if (!props.cluster) {
error.value = 'No cluster selected';
return;
}
if (!isFormValid.value) {
error.value = 'Please fill in all required fields';
return;
}
isSubmitting.value = true;
let volumeSource = {};
switch (volumeType.value) {
case 'hostPath':
volumeSource = {
hostPath: {
path: hostPath.value.path.trim(),
...(hostPath.value.type && { type: hostPath.value.type })
}
};
break;
}
const spec: any = {
capacity: {
storage: `${capacity.value}${capacityUnit.value}`
},
accessModes: accessModes.value,
persistentVolumeReclaimPolicy: reclaimPolicy.value,
...volumeSource
};
const stringData = {};
validData.forEach(item => {
stringData[item.key.trim()] = item.value;
});
const opts: SecretCreateOptions = {
context: props.cluster.contextName,
opts: {
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: name.value.trim(),
namespace: namespace.value.trim()
},
type: 'Opaque',
stringData: stringData
}
}
emit('create-volume', opts);
resetForm();
closeModal();
};
return {
name,
namespace,
volumeData,
isSubmitting,
error,
isFormValid,
addSecretData,
removeSecretData,
closeModal,
createSecret,
toggleValueVisibility,
getVolumeTypeLabel,
selectVolumeType,
volumeType,
volumeTypes,
hostPath,
nfs,
accessModes,
};
}
});
</script>
<template>
<div v-if="show" class="modal-overlay" @click="closeModal">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>Create Persistent Volume</h3>
<button class="close-button" @click="closeModal">×</button>
</div>
<div class="modal-body">
<div v-if="error" class="error-message">{{ error }}</div>
<div class="form-group">
<label for="name">Name:</label>
<input
id="name"
v-model="name"
type="text"
placeholder="my-persistent-volume"
:disabled="isSubmitting"
/>
</div>
<div class="form-group">
<label for="storageClass">Storage Class:</label>
<input
id="storageClass"
v-model="storageClass"
type="text"
placeholder="standard"
:disabled="isSubmitting"
/>
</div>
<div class="form-group">
<label for="capacity">Capacity (Gi):</label>
<div class="storage-input-group">
<input
id="capacity"
v-model="capacity"
type="number"
min="1"
placeholder="10"
:disabled="isSubmitting"
/>
</div>
</div>
<div class="form-section">
<h4>Volume Type</h4>
<div class="volume-type-selector">
<div
v-for="type in volumeTypes"
:key="type.value"
class="volume-type-option"
:class="{ selected: volumeType === type.value }"
@click="selectVolumeType(type.value)"
>
<div class="type-header">
<span class="type-name">{{ type.label }}</span>
<span class="type-badge" :class="type.category">{{ type.category }}</span>
</div>
<p class="type-description">{{ type.description }}</p>
</div>
</div>
</div>
<div v-if="volumeType" class="form-section">
<h4>{{ getVolumeTypeLabel() }} Configuration</h4>
<div v-if="volumeType == 'hostPath'" class="volume-config">
<div class="form-group">
<label for="hostPath">Host Path:</label>
<input
id="hostPath"
v-model="hostPath.path"
type="text"
placeholder="/mnt/data"
:disabled="isSubmitting"
/>
<small class="field-hint">Path on the host node</small>
</div>
<div class="form-group">
<label for="hostPathType">Path Type:</label>
<select id="hostPathType" v-model="hostPath.type" :disabled="isSubmitting">
<option value="">Default</option>
<option value="DirectoryOrCreate">DirectoryOrCreate</option>
<option value="Directory">Directory</option>
<option value="FileOrCreate">FileOrCreate</option>
<option value="File">File</option>
<option value="Socket">Socket</option>
<option value="CharDevice">CharDevice</option>
<option value="BlockDevice">BlockDevice</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button
class="cancel-button"
@click="closeModal"
:disabled="isSubmitting"
>
Cancel
</button>
<button
class="create-button"
@click="createPersistentVolume"
:disabled="!isFormValid || isSubmitting"
>
{{ isSubmitting ? 'Creating...' : 'Create' }}
</button>
</div>
</div>
</div>
</div>
</template>
<style src="@/assets/css/CreateResource.css" scoped></style>
<style src="@/assets/css/PersistentVolumes.css" scoped></style>