summary history files

frontend/src/components/PersistentVolumesList.vue
<!-- PersistentVolumeList.vue -->
<script lang="ts">
import { defineComponent, ref, computed, PropType, onBeforeUnmount } from 'vue';
import { KubernetesCluster, KubernetesPersistentVolume, KubernetesPersistentVolumeClaim } from '../types/kubernetes';
import { formatAge } from '../lib/format'; 
import SearchBar from './SearchBar.vue';

export default defineComponent({
  name: 'PersistentVolumeList',

  components: {
    SearchBar
  },

  props: {
    selectedCluster: {
      type: Object as PropType<KubernetesCluster>,
      required: false,
      default: null
    },
    persistentVolumes: {
      type: Array as PropType<KubernetesPersistentVolume[]>,
      required: false,
      default: () => []
    },
    persistentVolumeClaims: {
      type: Array as PropType<KubernetesPersistentVolumeClaim[]>,
      required: false,
      default: () => []
    }
  },

  emits: ['persistentVolume-selected', 'delete-persistentvolume'],

  setup(props, { emit }) {
    // State
    const searchQuery = ref('');
    const selectedPersistentVolume = ref<KubernetesPersistentVolume | null>(null);
    const selectedContextPersistentVolume = ref<KubernetesPersistentVolume | null>(null);
    const showMenu = ref(false);
    const menuPosition = ref({ x: 0, y: 0 });

    // Computed properties
    const filteredPersistentVolumes = computed(() => {
      if (!searchQuery.value) {
        return props.persistentVolumes;
      }

      const query = searchQuery.value.toLowerCase();

      const nameSpecificMatch = query.match(/^name:(.+)$/);
      if (nameSpecificMatch) {
        const nameQuery = nameSpecificMatch[1].trim();
        return props.persistentVolumes.filter(persistentVolume => {
          const name = persistentVolume.metadata.name.toLowerCase();
          return name.includes(nameQuery);
        });
      }

      const statusSpecificMatch = query.match(/^status:(.+)$/);
      if (statusSpecificMatch) {
        const statusQuery = statusSpecificMatch[1].trim();
        return props.persistentVolumes.filter(persistentVolume => {
          const status = persistentVolume.status?.phase?.toLowerCase() || '';
          return status.includes(statusQuery);
        });
      }

      const claimSpecificMatch = query.match(/^claim:(.+)$/);
      if (claimSpecificMatch) {
        const claimQuery = claimSpecificMatch[1].trim();
        return props.persistentVolumes.filter(persistentVolume => {
          const claimName = persistentVolume.spec?.claimRef?.name?.toLowerCase() || '';
          return claimName.includes(claimQuery);
        });
      }

      const storageClassSpecificMatch = query.match(/^storageclass:(.+)$/);
      if (storageClassSpecificMatch) {
        const storageClassQuery = storageClassSpecificMatch[1].trim();
        return props.persistentVolumes.filter(persistentVolume => {
          const storageClass = persistentVolume.spec?.storageClassName?.toLowerCase() || '';
          return storageClass.includes(storageClassQuery);
        });
      }

      const labelSpecificMatch = query.match(/^label:(.+)$/);
      if (labelSpecificMatch) {
        const labelQuery = labelSpecificMatch[1].trim();
        return props.persistentVolumes.filter(persistentVolume => {
          const labels = persistentVolume.metadata.labels || {};
          for (const key in labels) {
            const value = labels[key].toLowerCase();
            const keyLower = key.toLowerCase();

            // Check for key=value format
            if (labelQuery.includes('=')) {
              const [queryKey, queryValue] = labelQuery.split('=');
              if (keyLower === queryKey.trim().toLowerCase() && 
                  value.includes(queryValue.trim().toLowerCase())) {
                return true;
              }
            } 
            // Check for just key
            else if (keyLower.includes(labelQuery)) {
              return true;
            }
          }
          return false;
        });
      }

      // Default behavior: search only in persistent volume names
      return props.persistentVolumes.filter(persistentVolume => {
        const name = persistentVolume.metadata.name.toLowerCase();
        return name.includes(query);
      });
    });

    // Methods
    const hideContextMenu = () => {
      showMenu.value = false;
      document.removeEventListener('click', hideContextMenu);
    };

    const showContextMenu = (event: MouseEvent, persistentVolume: KubernetesPersistentVolume) => {
      event.preventDefault();

      menuPosition.value = {
        x: event.clientX,
        y: event.clientY
      };

      selectedContextPersistentVolume.value = persistentVolume;
      selectPersistentVolume(persistentVolume);
      showMenu.value = true;

      setTimeout(() => {
        document.addEventListener('click', hideContextMenu);
      }, 0);
    };

    const isSelected = (persistentVolume: KubernetesPersistentVolume): boolean => {
      if (!selectedPersistentVolume.value) return false;
      return selectedPersistentVolume.value.metadata.name === persistentVolume.metadata.name &&
        selectedPersistentVolume.value.metadata.uid === persistentVolume.metadata.uid;
    };

    const selectPersistentVolume = (persistentVolume: KubernetesPersistentVolume): void => {
      try {
        const payload = { ...persistentVolume };
        if (!payload.kind) {
          payload.kind = 'PersistentVolume';
        }
        selectedPersistentVolume.value = payload;
        emit('persistentVolume-selected', payload);
      } catch (error) {
        alert("Failed to select Persistent Volume:", error);
      }
    };

    const deletePersistentVolume = (): void => {
      if (selectedContextPersistentVolume.value && props.selectedCluster) {
        try {
          emit('delete-persistentvolume', {
            cluster: props.selectedCluster,
            definition: selectedContextPersistentVolume.value
          });
        } catch (error) {
          alert(error);
        }
      }
      hideContextMenu();
    };

    const getUniqueKey = (persistentVolume: KubernetesPersistentVolume): string => {
      return `${persistentVolume.metadata.name}-${persistentVolume.metadata.uid || ''}`;
    };

    const getPersistentVolumeStorageClass = (persistentVolume: KubernetesPersistentVolume): string => {
      return persistentVolume.spec?.storageClassName || '-';
    };

    const getPersistentVolumeAccessModes = (persistentVolume: KubernetesPersistentVolume): string => {
      const accessModes = persistentVolume.spec?.accessModes?.join(', ') || 'Unknown';
      switch (accessModes) {
        case "ReadWriteOnce":
          return "RWO";
        case "ReadOnlyMany":
          return "ROX";
        case "ReadWriteMany":
          return "RWX";
        case "ReadWriteOncePod":
          return "RWOP";
        default:
          return accessModes;
      }
    };

    const getPersistentVolumeStatus = (persistentVolume: KubernetesPersistentVolume): string => {
      return persistentVolume.status?.phase || 'Unknown';
    };

    const getPersistentVolumeStatusClass = (persistentVolume: KubernetesPersistentVolume): string => {
      const status = getPersistentVolumeStatus(persistentVolume);
      switch (status) {
        case 'Available':
          return 'status-available';
        case 'Bound':
          return 'status-active';
        case 'Released':
          return 'status-warning';
        case 'Failed':
          return 'status-error';
        default:
          return 'status-unknown';
      }
    };

    const getPersistentVolumeCapacity = (persistentVolume: KubernetesPersistentVolume): string => {
      return persistentVolume.spec?.capacity?.storage || 'Unknown';
    };

    const getPersistentVolumeClaim = (persistentVolume: KubernetesPersistentVolume): string => {
      const claimRef = persistentVolume.spec?.claimRef;
      if (claimRef) {
        return `${claimRef.namespace}/${claimRef.name}`;
      }
      return '-';
    };

    const getPersistentVolumeReclaimPolicy = (persistentVolume: KubernetesPersistentVolume): string => {
      return persistentVolume.spec?.persistentVolumeReclaimPolicy || 'Retain';
    };

    onBeforeUnmount(() => {
      document.removeEventListener('click', hideContextMenu);
    });

    // Return all refs, computed properties, and methods
    return {
      // State
      searchQuery,
      selectedPersistentVolume,
      selectedContextPersistentVolume,
      showMenu,
      menuPosition,

      // Props
      persistentVolumeClaims: props.persistentVolumeClaims,

      // Computed
      filteredPersistentVolumes,

      // Methods
      hideContextMenu,
      showContextMenu,
      isSelected,
      selectPersistentVolume,
      deletePersistentVolume,
      getUniqueKey,
      getPersistentVolumeStorageClass,
      getPersistentVolumeAccessModes,
      getPersistentVolumeStatus,
      getPersistentVolumeStatusClass,
      getPersistentVolumeCapacity,
      getPersistentVolumeClaim,
      getPersistentVolumeReclaimPolicy,
      formatAge
    };
  }
});
</script>

<template>
  <div class="persistentvolumes-container">
    <div class="search-bar-container">
      <SearchBar
        :value="searchQuery"
        @update:value="searchQuery = $event"
        placeholder="Search persistent volumes..."
      />
    </div>

    <div v-if="filteredPersistentVolumes.length === 0" class="no-persistentvolumes">
      <p v-if="searchQuery">No persistent volumes found matching "{{ searchQuery }}"</p>
      <p v-else>No persistent volumes found.</p>
    </div>

    <div v-else class="table-scroll-container"> 
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Status</th>
            <th>Claim</th>
            <th>Capacity</th>
            <th>Access Modes</th>
            <th>Reclaim Policy</th>
            <th>Storage Class</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody>
          <tr 
            v-for="persistentVolume in filteredPersistentVolumes" 
            :key="getUniqueKey(persistentVolume)"
            :class="{ selected: isSelected(persistentVolume) }"
            @click="selectPersistentVolume(persistentVolume)"
            @contextmenu="showContextMenu($event, persistentVolume)"
          >
            <td>{{ persistentVolume.metadata.name }}</td>
            <td :class="getPersistentVolumeStatusClass(persistentVolume)">{{ getPersistentVolumeStatus(persistentVolume) }}</td>
            <td>{{ getPersistentVolumeClaim(persistentVolume) }}</td>
            <td>{{ getPersistentVolumeCapacity(persistentVolume) }}</td>
            <td>{{ getPersistentVolumeAccessModes(persistentVolume) }}</td>
            <td>{{ getPersistentVolumeReclaimPolicy(persistentVolume) }}</td>
            <td>{{ getPersistentVolumeStorageClass(persistentVolume) }}</td>
            <td>{{ formatAge(persistentVolume.metadata.creationTimestamp) }}</td>
          </tr>
        </tbody>
      </table>
      <div 
        v-if="showMenu" 
        class="context-menu" 
        :style="{ top: menuPosition.y + 'px', left: menuPosition.x + 'px' }"
        @click.stop
      >
        <div class="menu-item" @click="deletePersistentVolume">
          Delete
        </div>
      </div>
    </div>
  </div>
</template>

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