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>