summary history files

frontend/src/components/PersistentVolumeClaimsCreateGuided.vue
<!-- PersistentVolumeClaimsCreateGuided.vue -->
<template>
  <div v-if="show" class="modal-overlay" @click="closeModal">
    <div class="modal-content" @click.stop>
      <div class="modal-header">
        <h3>Create Persistent Volume Claim</h3>
        <button class="close-button" @click="closeModal">&times;</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-pvc"
            :disabled="isSubmitting"
          />
        </div>

        <div class="form-group">
          <label for="namespace">Namespace:</label>
          <input 
            id="namespace" 
            v-model="namespace" 
            type="text" 
            placeholder="default"
            :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"
          />
          <small class="field-hint">Leave empty to use default storage class</small>
        </div>

        <div class="form-group">
          <label for="storageSize">Storage Size:</label>
          <div class="storage-input-group">
            <input 
              id="storageSize" 
              v-model="storageSize" 
              type="number" 
              min="1"
              placeholder="1"
              :disabled="isSubmitting"
            />
            <select v-model="storageUnit" :disabled="isSubmitting">
              <option value="Gi">Gi</option>
              <option value="Mi">Mi</option>
              <option value="Ti">Ti</option>
            </select>
          </div>
        </div>

        <div class="form-section">
          <h4>Access Modes</h4>
          <div class="checkbox-group">
            <label class="checkbox-label">
              <input 
                type="checkbox" 
                v-model="accessModes" 
                value="ReadWriteOnce"
                :disabled="isSubmitting"
              />
              ReadWriteOnce (RWO)
              <small>Volume can be mounted as read-write by a single node</small>
            </label>
            <label class="checkbox-label">
              <input 
                type="checkbox" 
                v-model="accessModes" 
                value="ReadOnlyMany"
                :disabled="isSubmitting"
              />
              ReadOnlyMany (ROX)
              <small>Volume can be mounted read-only by many nodes</small>
            </label>
            <label class="checkbox-label">
              <input 
                type="checkbox" 
                v-model="accessModes" 
                value="ReadWriteMany"
                :disabled="isSubmitting"
              />
              ReadWriteMany (RWX)
              <small>Volume can be mounted as read-write by many nodes</small>
            </label>
            <label class="checkbox-label">
              <input 
                type="checkbox" 
                v-model="accessModes" 
                value="ReadWriteOncePod"
                :disabled="isSubmitting"
              />
              ReadWriteOncePod (RWOP)
              <small>Volume can be mounted as read-write by a single pod</small>
            </label>
          </div>
        </div>

        <div class="form-section">
          <h4>Volume Mode</h4>
          <div class="radio-group">
            <label class="radio-label">
              <input 
                type="radio" 
                v-model="volumeMode" 
                value="Filesystem"
                :disabled="isSubmitting"
              />
              Filesystem
              <small>Volume is mounted into pods as a directory</small>
            </label>
            <label class="radio-label">
              <input 
                type="radio" 
                v-model="volumeMode" 
                value="Block"
                :disabled="isSubmitting"
              />
              Block
              <small>Volume is used as a raw block device</small>
            </label>
          </div>
        </div>

        <div class="form-section">
          <h4>Labels (Optional)</h4>
          <div 
            v-for="(label, index) in labels" 
            :key="index" 
            class="label-row"
          >
            <div class="form-group key-field">
              <input 
                v-model="label.key" 
                type="text" 
                placeholder="key"
                :disabled="isSubmitting"
              />
            </div>
            <div class="form-group value-field">
              <input 
                v-model="label.value" 
                type="text" 
                placeholder="value"
                :disabled="isSubmitting"
              />
            </div>
            <button 
              class="remove-button" 
              @click="removeLabel(index)"
              :disabled="isSubmitting || labels.length <= 1"
            >
              &times;
            </button>
          </div>

          <button 
            class="add-button" 
            @click="addLabel"
            :disabled="isSubmitting"
          >
            + Add Label
          </button>
        </div>
      </div>

      <div class="modal-footer">
        <button 
          class="cancel-button" 
          @click="closeModal"
          :disabled="isSubmitting"
        >
          Cancel
        </button>
        <button 
          class="create-button" 
          @click="createPvc"
          :disabled="!isFormValid || isSubmitting"
        >
          {{ isSubmitting ? 'Creating...' : 'Create PVC' }}
        </button>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed, PropType } from 'vue';
import { PersistentVolumeClaimCreateOptions } from '../types/custom';
import { KubernetesCluster } from '../types/kubernetes';

export default defineComponent({
  name: 'PersistentVolumeClaimsCreateGuided',

  props: {
    show: {
      type: Boolean,
      required: true
    },
    cluster: {
      type: Object as PropType<KubernetesCluster | null>,
      required: false,
      default: null
    }
  },

  emits: ['close', 'create-pvc'],

  setup(props, { emit }) {
    const name = ref('');
    const namespace = ref('default');
    const storageClass = ref('');
    const storageSize = ref(1);
    const storageUnit = ref('Gi');
    const accessModes = ref(['ReadWriteOnce']);
    const volumeMode = ref('Filesystem');
    const isSubmitting = ref(false);
    const error = ref('');
    const labels = ref([{ key: '', value: '' }]);

    const isFormValid = computed(() => {
      if (!name.value.trim()) return false;
      if (!namespace.value.trim()) return false;
      if (!storageSize.value || storageSize.value <= 0) return false;
      if (accessModes.value.length === 0) return false;
      return true;
    });

    const addLabel = () => {
      labels.value.push({ key: '', value: '' });
    };

    const removeLabel = (index: number) => {
      if (labels.value.length > 1) {
        labels.value.splice(index, 1);
      }
    };

    const resetForm = () => {
      name.value = '';
      namespace.value = 'default';
      storageClass.value = '';
      storageSize.value = 1;
      storageUnit.value = 'Gi';
      accessModes.value = ['ReadWriteOnce'];
      volumeMode.value = 'Filesystem';
      labels.value = [{ key: '', value: '' }];
      error.value = '';
      isSubmitting.value = false;
    };

    const closeModal = () => {
      resetForm();
      emit('close');
    };

    const createPvc = () => {
      if (!name.value.trim()) {
        error.value = 'Name is required';
        return;
      }

      if (!namespace.value.trim()) {
        error.value = 'Namespace is required';
        return;
      }

      if (!props.cluster) {
        error.value = 'No cluster selected';
        return;
      }

      if (!storageSize.value || storageSize.value <= 0) {
        error.value = 'Storage size must be greater than 0';
        return;
      }

      if (accessModes.value.length === 0) {
        error.value = 'At least one access mode must be selected';
        return;
      }

      isSubmitting.value = true;

      // Build labels object
      const labelsObj = {};
      labels.value.forEach(label => {
        if (label.key.trim() && label.value.trim()) {
          labelsObj[label.key.trim()] = label.value.trim();
        }
      });

      // Build PVC spec
      const spec: any = {
        accessModes: accessModes.value,
        resources: {
          requests: {
            storage: `${storageSize.value}${storageUnit.value}`
          }
        },
        volumeMode: volumeMode.value
      };

      // Add storage class if specified
      if (storageClass.value.trim()) {
        spec.storageClassName = storageClass.value.trim();
      }

      // Build metadata
      const metadata: any = {
        name: name.value.trim(),
        namespace: namespace.value.trim()
      };

      // Add labels if any
      if (Object.keys(labelsObj).length > 0) {
        metadata.labels = labelsObj;
      }

      const opts: PersistentVolumeClaimCreateOptions = {
        context: props.cluster.contextName,
        opts: {
          definition: {
            apiVersion: 'v1',
            kind: 'PersistentVolumeClaim',
            metadata: metadata,
            spec: spec
          },
          isYaml: false
        }
      };

      emit('create-pvc', opts);
      resetForm();
      closeModal();
    };

    return {
      name,
      namespace,
      storageClass,
      storageSize,
      storageUnit,
      accessModes,
      volumeMode,
      labels,
      isSubmitting,
      error,
      isFormValid,
      addLabel,
      removeLabel,
      closeModal,
      createPvc
    };
  }
});
</script>

<style src="@/assets/css/CreateResource.css" scoped></style>

<style scoped>
.storage-input-group {
  display: flex;
  gap: 0.5rem;
}

.storage-input-group input {
  flex: 1;
}

.storage-input-group select {
  width: 80px;
  padding: 0.5rem;
  border: 1px solid #444;
  border-radius: 4px;
  background-color: #2d2d2d;
  color: #ffffff;
}

.checkbox-group {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.checkbox-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
  width: auto;
  margin-right: 0.5rem;
}

.radio-group {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.radio-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  cursor: pointer;
}

.radio-label input[type="radio"] {
  width: auto;
  margin-right: 0.5rem;
}

.field-hint {
  color: #999;
  font-size: 0.8rem;
  margin-top: 0.25rem;
  display: block;
}

.label-row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  margin-bottom: 0.5rem;
}

.key-field,
.value-field {
  flex: 1;
  margin-bottom: 0;
}

.remove-button {
  background-color: #dc3545;
  border: 1px solid #dc3545;
  color: white;
  border-radius: 4px;
  width: 30px;
  height: 30px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1.5rem;
}

.remove-button:hover:not(:disabled) {
  background-color: #c82333;
}

.remove-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.add-button {
  background-color: #28a745;
  border: 1px solid #28a745;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 0.5rem;
}

.add-button:hover:not(:disabled) {
  background-color: #218838;
}

.add-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>