summary history files

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

export default defineComponent({
  name: 'DeploymentsList',

  components: {
    SearchBar
  },

  props: {
    selectedCluster: {
      type: Object as PropType<KubernetesCluster | null>,
      required: false,
      default: null
    },
    deployments: {
      type: Array as PropType<KubernetesDeployment[]>,
      required: false,
      default: () => []
    }
  },

  emits: ['deployment-selected', 'restart-deployment'],

  setup(props) {
    // Search functionality
    const searchQuery = ref('');

    const filteredDeployments = computed(() => {
      if (!searchQuery.value) {
        return props.deployments;
      }

      const query = searchQuery.value.toLowerCase();

      // Check if the query is in the format "name:deployment-name"
      const nameSpecificMatch = query.match(/^name:(.+)$/);
      if (nameSpecificMatch) {
        const nameQuery = nameSpecificMatch[1].trim();
        return props.deployments.filter(deployment => {
          const name = deployment.metadata.name.toLowerCase();
          return name.includes(nameQuery);
        });
      }

      // Check if the query is in the format "namespace:namespace-name"
      const namespaceSpecificMatch = query.match(/^namespace:(.+)$/);
      if (namespaceSpecificMatch) {
        const namespaceQuery = namespaceSpecificMatch[1].trim();
        return props.deployments.filter(deployment => {
          const namespace = deployment.metadata.namespace?.toLowerCase() || '';
          return namespace.includes(namespaceQuery);
        });
      }

      // Check if the query is in the format "label:key=value" or "label:key"
      const labelSpecificMatch = query.match(/^label:(.+)$/);
      if (labelSpecificMatch) {
        const labelQuery = labelSpecificMatch[1].trim();
        return props.deployments.filter(deployment => {
          const labels = deployment.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 deployment names
      return props.deployments.filter(deployment => {
        const name = deployment.metadata.name.toLowerCase();
        return name.includes(query);
      });
    });


    // Context menu state
    const showMenu = ref(false);
    const menuPosition = ref({ x: 0, y: 0 });
    const selectedContextDeployment = ref<KubernetesDeployment | null>(null);

    // Show context menu
    const showContextMenu = (event: MouseEvent, deployment: KubernetesDeployment) => {
      event.preventDefault();

      // Position the menu
      menuPosition.value = {
        x: event.clientX,
        y: event.clientY
      };

      // Store the deployment that was right-clicked
      selectedContextDeployment.value = deployment;

      // Show the menu
      showMenu.value = true;

      // Add a click event listener to hide the menu when clicking outside
      setTimeout(() => {
        document.addEventListener('click', hideContextMenu);
      }, 0);
    };

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

    return {
      searchQuery,
      filteredDeployments,
      showMenu,
      menuPosition,
      selectedContextDeployment,
      showContextMenu,
      hideContextMenu,
			formatAge
    };
  },

  data() {
    return {
      selectedDeployment: null as KubernetesDeployment | null
    };
  },

  methods: {
    selectDeployment(deployment: KubernetesDeployment): void {
      const deploymentToEmit = { ...deployment };
      if (!deploymentToEmit.kind) {
        deploymentToEmit.kind = 'Deployment';
      }
      this.selectedDeployment = deploymentToEmit;
      this.$emit('deployment-selected', deploymentToEmit);
    },

    isSelected(deployment: KubernetesDeployment): boolean {
      return this.selectedDeployment?.metadata.name === deployment.metadata.name;
    },

    // Menu actions
    restartDeployment() {
      if (this.selectedContextDeployment && this.selectedCluster) {
        this.$emit('restart-deployment', {
          cluster: this.selectedCluster,
          deployment: this.selectedContextDeployment
        });
      }
      this.hideContextMenu();
    },

    getUniqueKey(deployment: KubernetesDeployment): string {
      const namespace = deployment.metadata.namespace || 'default';
      return `${namespace}-${deployment.metadata.name}-${deployment.metadata.uid}`;
    },
  }
});
</script>

<template>
  <div class="deployments-container">
    <div class="search-bar-container">
      <SearchBar
        :value="searchQuery"
        @update:value="searchQuery = $event"
        placeholder="Search deployments..."
      />
    </div>
    <div class="table-scroll-container">
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Namespace</th>
            <th>Ready</th>
            <th>Up-to-date</th>
            <th>Available</th>
            <th>Age</th>
          </tr>
        </thead>
        <tbody>
          <tr 
            v-for="deployment in filteredDeployments" 
            :key="getUniqueKey(deployment)"
            :class="{ selected: isSelected(deployment) }"
            @click="selectDeployment(deployment)"
            @contextmenu.prevent="showContextMenu($event, deployment)"
          >
            <td>{{ deployment.metadata.name }}</td>
            <td>{{ deployment.metadata.namespace || 'default' }}</td> <!-- Display namespace -->
            <td>{{ `${deployment.status.readyReplicas || 0}/${deployment.spec.replicas}` }}</td>
            <td>{{ deployment.status.updatedReplicas || 0 }}</td>
            <td>{{ deployment.status.availableReplicas || 0 }}</td>
            <td>{{ formatAge(deployment.metadata.creationTimestamp) }}</td>
          </tr>
        </tbody>
      </table>

    </div>

    <!-- Context Menu -->
    <div 
      v-if="showMenu" 
      class="context-menu" 
      :style="{ top: menuPosition.y + 'px', left: menuPosition.x + 'px' }"
    >
      <div class="menu-item" @click="restartDeployment">
        <span class="menu-text">Restart</span>
      </div>
    </div>
  </div>
</template>


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