summary history files

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

export default defineComponent({
  name: 'IngressesList',
  components: {
    SearchBar
  },
  props: {
    selectedCluster: {
      type: Object as PropType<KubernetesCluster>,
      required: false,
      default: null
    },
    ingresses: {
      type: Array as PropType<KubernetesIngress[]>,
      required: false,
      default: () => []
    }
  },
  emits: ['ingress-selected', 'delete-ingress'],
  data() {
    return {
      selectedIngress: null as KubernetesIngress | null,
      selectedContextIngress: null as KuberneteIngress | null,
      showMenu: false,
      menuPosition: { x: 0, y: 0},
    };
  },
  beforeUnmount() {
    document.removeEventListener('click', this.hideContextMenu);
  },
  setup(props, { emit }) {
    const searchQuery = ref('');
    const filteredIngresses = computed(() => {
      if (!searchQuery.value) {
        return props.ingresses;
      }

      const query = searchQuery.value.toLowerCase();

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

      const namespaceSpecificMatch = query.match(/^namespace:(.+)$/);
      if (namespaceSpecificMatch) {
        const namespaceQuery = namespaceSpecificMatch[1].trim();
        return props.ingresses.filter(ingress => {
          const namespace = ingress.metadata.namespace.toLowerCase();
          return namespace.includes(namespaceQuery);
        });
      }

      const classSpecificMatch = query.match(/^class:(.+)$/);
      if (classSpecificMatch) {
        const classQuery = classSpecificMatch[1].trim();
        return props.ingresses.filter(ingress => {
          if (!ingress.spec.ingressClassName) {
            return false;
          }
          const classname = ingress.spec.ingressClassName.toLowerCase();
          return classname.includes(classQuery);
        });
      }

      return props.ingresses.filter(ingress => {
        const name = ingress.metadata.name.toLowerCase();
        return name.includes(query);
      });
    });

    return {
      searchQuery,
      filteredIngresses,
      formatAge
    };
  },
  methods: {
    hideContextMenu(): void {
      this.showMenu = false;
      document.removeEventListener('click', this.hideContextMenu);
    },
    showContextMenu(event: MouseEvent, ingress: KubernetesIngress): void {
      event.preventDefault();

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

      this.selectedContextIngress = ingress;

      this.selectIngress(ingress);

      this.showMenu = true;

      setTimeout(() => {
        document.addEventListener('click', this.hideContextMenu);
      }, 0);
    },
    isSelected(ingress: KubernetesIngress): boolean {
      if (!this.selectedIngress) return false;
      return this.selectedIngress.metadata.name === ingress.metadata.name &&
        this.selectedIngress.metadata.namespace === ingress.metadata.namespace &&
        this.selectedIngress.metadata.uid === ingress.metadata.uid;
    },
    selectIngress(ingress: KubernetesIngress): void {
      const ingressToEmit = { ...ingress }
      if (!ingressToEmit.kind) {
        ingressToEmit.kind = 'Ingress';
      }
      this.selectedIngress = ingressToEmit;
      this.$emit('ingress-selected', ingressToEmit);
    },
    deleteIngress(): void {
      if (this.selectedContextIngress && this.selectedCluster) {
        this.$emit('delete-ingress', {
          cluster: this.selectedCluster,
          ingress: this.selectedIngress
        });
      }
      this.hideContextMenu();
    },
    getUniqueKey(ingress: KubernetesIngress): string {
        const namespace = ingress.metadata.namespace || 'default';
        let key;
        key = namespace + '-' + ingress.metadata.name + '-' + (ingress.metadata.uid || '');
        return key;
    }
  },
});
</script>

<template>
  <div class="ingresses-container">
    <div class="search-bar-container">
      <SearchBar
        :value="searchQuery"
        @update:value="searchQuery = $event"
        placeholder="Search ingresses..."
      />
    </div>
    <div class="table-scroll-container">
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Namespace</th>
            <th>Class</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody>
          <tr 
            v-for="ingress in filteredIngresses" 
            :key="getUniqueKey(ingress)"
            :class="{ selected: isSelected(ingress) }"
            @click="selectIngress(ingress)"
            @contextmenu="showContextMenu($event, ingress)"
          >
            <td>{{ ingress.metadata.name }}</td>
            <td>{{ ingress.metadata.namespace || 'default' }}</td>
            <td>{{ ingress.spec.ingressClassName }}</td>
            <td>{{ formatAge(ingress.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="deleteIngress">
          Delete
        </div>
      </div>
    </div>
  </div>
</template>

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