Compare commits
No commits in common. "41d9ef40575b0ddbba6167b6df96cde00ef51a99" and "70639194ea5e3ab5c3349f1ab2dafa550aea1a22" have entirely different histories.
41d9ef4057
...
70639194ea
@ -21,11 +21,10 @@
|
|||||||
:value="showVMList ? searchQuery : (selectedVMData?.name || '')"
|
:value="showVMList ? searchQuery : (selectedVMData?.name || '')"
|
||||||
@input="handleSearch"
|
@input="handleSearch"
|
||||||
@focus="handleSearchFocus"
|
@focus="handleSearchFocus"
|
||||||
@blur="handleSearchBlur"
|
|
||||||
class="form-control search-input"
|
class="form-control search-input"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="searchQuery && showVMList"
|
v-if="searchQuery"
|
||||||
class="btn-clear"
|
class="btn-clear"
|
||||||
@click="clearSearch"
|
@click="clearSearch"
|
||||||
>
|
>
|
||||||
@ -36,7 +35,6 @@
|
|||||||
<div class="filter-dropdown">
|
<div class="filter-dropdown">
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-secondary dropdown-toggle"
|
class="btn btn-outline-secondary dropdown-toggle"
|
||||||
:disabled="!showVMList"
|
|
||||||
@click="toggleFilterMenu"
|
@click="toggleFilterMenu"
|
||||||
>
|
>
|
||||||
<i class="fas fa-filter"></i> Filters
|
<i class="fas fa-filter"></i> Filters
|
||||||
@ -98,7 +96,6 @@
|
|||||||
class="vm-list-item"
|
class="vm-list-item"
|
||||||
:class="{ 'active': selectedVM === vm.name }"
|
:class="{ 'active': selectedVM === vm.name }"
|
||||||
@click="selectVM(vm.name)"
|
@click="selectVM(vm.name)"
|
||||||
:ref="el => { if (vm.name === selectedVM) selectedVMRef = el; }"
|
|
||||||
>
|
>
|
||||||
<div class="vm-item-content">
|
<div class="vm-item-content">
|
||||||
<div class="vm-name">{{ vm.name }}</div>
|
<div class="vm-name">{{ vm.name }}</div>
|
||||||
@ -249,7 +246,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import PageTitle from '../components/PageTitle.vue';
|
import PageTitle from '../components/PageTitle.vue';
|
||||||
import { useApiStore } from '../stores/api';
|
import { useApiStore } from '../stores/api';
|
||||||
@ -264,12 +261,10 @@ const selectedVM = ref('');
|
|||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
const showVMList = ref(true);
|
const showVMList = ref(true);
|
||||||
const showFilterMenu = ref(false);
|
const showFilterMenu = ref(false);
|
||||||
const searchActive = ref(false);
|
|
||||||
const appliedFilters = ref({
|
const appliedFilters = ref({
|
||||||
states: {},
|
states: {},
|
||||||
nodes: {}
|
nodes: {}
|
||||||
});
|
});
|
||||||
const selectedVMRef = ref(null);
|
|
||||||
|
|
||||||
// Section visibility state
|
// Section visibility state
|
||||||
const sections = ref({
|
const sections = ref({
|
||||||
@ -281,20 +276,7 @@ const sections = ref({
|
|||||||
|
|
||||||
// Toggle VM list visibility
|
// Toggle VM list visibility
|
||||||
const toggleVMList = () => {
|
const toggleVMList = () => {
|
||||||
if (showVMList.value) {
|
showVMList.value = !showVMList.value;
|
||||||
showVMList.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showVMList.value = true;
|
|
||||||
searchActive.value = false;
|
|
||||||
|
|
||||||
// Scroll to selected VM after the list is shown
|
|
||||||
if (selectedVM.value) {
|
|
||||||
nextTick(() => {
|
|
||||||
scrollToSelectedVM();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle filter menu
|
// Toggle filter menu
|
||||||
@ -313,31 +295,13 @@ const closeFilterMenuOnClickOutside = (event) => {
|
|||||||
// Add event listener for clicks outside filter menu
|
// Add event listener for clicks outside filter menu
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', closeFilterMenuOnClickOutside);
|
document.addEventListener('click', closeFilterMenuOnClickOutside);
|
||||||
// Add event listener for clicks outside the VM list area
|
|
||||||
document.addEventListener('click', handleClickOutside);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove event listener when component is unmounted
|
// Remove event listener when component is unmounted
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', closeFilterMenuOnClickOutside);
|
document.removeEventListener('click', closeFilterMenuOnClickOutside);
|
||||||
document.removeEventListener('click', handleClickOutside);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle clicks outside the VM list area
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
// Only handle this if a VM is selected and the list is showing
|
|
||||||
if (selectedVM.value && showVMList.value) {
|
|
||||||
const vmControls = document.querySelector('.vm-controls-container');
|
|
||||||
const vmList = document.querySelector('.vm-list-fullpage');
|
|
||||||
|
|
||||||
// If click is outside both the controls and list, close the list
|
|
||||||
if ((!vmControls || !vmControls.contains(event.target)) &&
|
|
||||||
(!vmList || !vmList.contains(event.target))) {
|
|
||||||
showVMList.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Toggle section visibility
|
// Toggle section visibility
|
||||||
const toggleSection = (section) => {
|
const toggleSection = (section) => {
|
||||||
sections.value[section] = !sections.value[section];
|
sections.value[section] = !sections.value[section];
|
||||||
@ -346,19 +310,18 @@ const toggleSection = (section) => {
|
|||||||
// Clear search
|
// Clear search
|
||||||
const clearSearch = () => {
|
const clearSearch = () => {
|
||||||
searchQuery.value = '';
|
searchQuery.value = '';
|
||||||
searchActive.value = false;
|
|
||||||
filterVMs();
|
filterVMs();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle a filter on/off
|
// Toggle a filter on/off
|
||||||
const toggleFilter = (type, value) => {
|
const toggleFilter = (type, value) => {
|
||||||
appliedFilters.value[type][value] = !appliedFilters.value[type][value];
|
appliedFilters.value[type][value] = !appliedFilters.value[type][value];
|
||||||
searchActive.value = true;
|
|
||||||
filterVMs();
|
filterVMs();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset all filters
|
// Reset filters
|
||||||
const resetFilters = () => {
|
const resetFilters = () => {
|
||||||
|
// Reset all filters and apply immediately
|
||||||
Object.keys(appliedFilters.value.states).forEach(state => {
|
Object.keys(appliedFilters.value.states).forEach(state => {
|
||||||
appliedFilters.value.states[state] = false;
|
appliedFilters.value.states[state] = false;
|
||||||
});
|
});
|
||||||
@ -367,23 +330,14 @@ const resetFilters = () => {
|
|||||||
appliedFilters.value.nodes[node] = false;
|
appliedFilters.value.nodes[node] = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
searchActive.value = false;
|
|
||||||
filterVMs();
|
filterVMs();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Count active filters
|
// Count active filters
|
||||||
const activeFiltersCount = computed(() => {
|
const activeFiltersCount = computed(() => {
|
||||||
let count = 0;
|
const stateFiltersCount = Object.values(appliedFilters.value.states).filter(v => v).length;
|
||||||
|
const nodeFiltersCount = Object.values(appliedFilters.value.nodes).filter(v => v).length;
|
||||||
Object.values(appliedFilters.value.states).forEach(isActive => {
|
return stateFiltersCount + nodeFiltersCount;
|
||||||
if (isActive) count++;
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.values(appliedFilters.value.nodes).forEach(isActive => {
|
|
||||||
if (isActive) count++;
|
|
||||||
});
|
|
||||||
|
|
||||||
return count;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get available states from VM data
|
// Get available states from VM data
|
||||||
@ -392,24 +346,7 @@ const availableStates = computed(() => {
|
|||||||
vmData.value.forEach(vm => {
|
vmData.value.forEach(vm => {
|
||||||
if (vm.state) states.add(vm.state);
|
if (vm.state) states.add(vm.state);
|
||||||
});
|
});
|
||||||
// Get all states as an array
|
return Array.from(states).sort();
|
||||||
const statesArray = Array.from(states);
|
|
||||||
|
|
||||||
// Remove 'start' if it exists
|
|
||||||
const startIndex = statesArray.indexOf('start');
|
|
||||||
if (startIndex !== -1) {
|
|
||||||
statesArray.splice(startIndex, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the remaining states
|
|
||||||
statesArray.sort();
|
|
||||||
|
|
||||||
// Add 'start' at the beginning if it was present
|
|
||||||
if (startIndex !== -1) {
|
|
||||||
statesArray.unshift('start');
|
|
||||||
}
|
|
||||||
|
|
||||||
return statesArray;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get available nodes from VM data
|
// Get available nodes from VM data
|
||||||
@ -427,31 +364,28 @@ const filteredVMs = computed(() => {
|
|||||||
|
|
||||||
let filtered = [...vmData.value];
|
let filtered = [...vmData.value];
|
||||||
|
|
||||||
if (searchActive.value && searchQuery.value) {
|
// Apply search filter
|
||||||
|
if (searchQuery.value.trim()) {
|
||||||
const query = searchQuery.value.toLowerCase();
|
const query = searchQuery.value.toLowerCase();
|
||||||
filtered = filtered.filter(vm =>
|
filtered = filtered.filter(vm =>
|
||||||
vm.name.toLowerCase().includes(query)
|
vm.name.toLowerCase().includes(query)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchActive.value) {
|
// Apply state filters
|
||||||
// Apply state filters if any are active
|
const hasStateFilters = Object.values(appliedFilters.value.states).some(v => v);
|
||||||
const activeStates = Object.entries(appliedFilters.value.states)
|
if (hasStateFilters) {
|
||||||
.filter(([_, isActive]) => isActive)
|
filtered = filtered.filter(vm => {
|
||||||
.map(([state]) => state);
|
return appliedFilters.value.states[vm.state] === true;
|
||||||
|
});
|
||||||
if (activeStates.length > 0) {
|
|
||||||
filtered = filtered.filter(vm => activeStates.includes(vm.state));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply node filters if any are active
|
// Apply node filters
|
||||||
const activeNodes = Object.entries(appliedFilters.value.nodes)
|
const hasNodeFilters = Object.values(appliedFilters.value.nodes).some(v => v);
|
||||||
.filter(([_, isActive]) => isActive)
|
if (hasNodeFilters) {
|
||||||
.map(([node]) => node);
|
filtered = filtered.filter(vm => {
|
||||||
|
return appliedFilters.value.nodes[vm.node] === true;
|
||||||
if (activeNodes.length > 0) {
|
});
|
||||||
filtered = filtered.filter(vm => activeNodes.includes(vm.node));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered;
|
return filtered;
|
||||||
@ -465,17 +399,19 @@ const selectedVMData = computed(() => {
|
|||||||
|
|
||||||
// Get status class based on VM state
|
// Get status class based on VM state
|
||||||
const getStatusClass = (state) => {
|
const getStatusClass = (state) => {
|
||||||
if (!state) return '';
|
if (!state) return 'status-unknown';
|
||||||
|
|
||||||
switch(state.toLowerCase()) {
|
switch (state) {
|
||||||
case 'start':
|
case 'start':
|
||||||
return 'status-running';
|
return 'status-running';
|
||||||
case 'stop':
|
case 'shutdown':
|
||||||
return 'status-stopped';
|
return 'status-stopped';
|
||||||
case 'disable':
|
case 'pause':
|
||||||
return 'status-disabled';
|
return 'status-paused';
|
||||||
|
case 'crash':
|
||||||
|
return 'status-error';
|
||||||
default:
|
default:
|
||||||
return '';
|
return 'status-unknown';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -515,7 +451,6 @@ const fetchVMData = async () => {
|
|||||||
// Handle search input
|
// Handle search input
|
||||||
const handleSearch = (event) => {
|
const handleSearch = (event) => {
|
||||||
searchQuery.value = event.target.value;
|
searchQuery.value = event.target.value;
|
||||||
searchActive.value = true;
|
|
||||||
filterVMs();
|
filterVMs();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -524,34 +459,6 @@ const handleSearchFocus = () => {
|
|||||||
// Show VM list when search is focused
|
// Show VM list when search is focused
|
||||||
if (!showVMList.value) {
|
if (!showVMList.value) {
|
||||||
showVMList.value = true;
|
showVMList.value = true;
|
||||||
// Scroll to selected VM after the list is shown
|
|
||||||
if (selectedVM.value) {
|
|
||||||
nextTick(() => {
|
|
||||||
scrollToSelectedVM();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
searchActive.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle search blur
|
|
||||||
const handleSearchBlur = (event) => {
|
|
||||||
// Don't close the list if clicking on another element within the list
|
|
||||||
const vmList = document.querySelector('.vm-list-fullpage');
|
|
||||||
if (vmList && vmList.contains(event.relatedTarget)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a VM is selected and user clicks away from search, close the list
|
|
||||||
if (selectedVM.value && !event.relatedTarget) {
|
|
||||||
// Use setTimeout to allow click events to process first
|
|
||||||
setTimeout(() => {
|
|
||||||
// Only close if we're not clicking on a VM in the list
|
|
||||||
if (!document.activeElement || document.activeElement.tagName !== 'BUTTON' ||
|
|
||||||
!document.activeElement.classList.contains('vm-list-item')) {
|
|
||||||
showVMList.value = false;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -568,25 +475,6 @@ const selectVM = (vmName) => {
|
|||||||
showVMList.value = false;
|
showVMList.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scroll to the selected VM in the list
|
|
||||||
const scrollToSelectedVM = () => {
|
|
||||||
if (selectedVMRef.value) {
|
|
||||||
// Get the VM list container
|
|
||||||
const vmList = document.querySelector('.vm-list');
|
|
||||||
if (!vmList) return;
|
|
||||||
|
|
||||||
// Calculate the scroll position
|
|
||||||
const vmElement = selectedVMRef.value;
|
|
||||||
const vmPosition = vmElement.offsetTop;
|
|
||||||
const listHeight = vmList.clientHeight;
|
|
||||||
const vmHeight = vmElement.clientHeight;
|
|
||||||
|
|
||||||
// Scroll to position the selected VM in the middle of the list if possible
|
|
||||||
const scrollPosition = vmPosition - (listHeight / 2) + (vmHeight / 2);
|
|
||||||
vmList.scrollTop = Math.max(0, scrollPosition);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchVMData();
|
fetchVMData();
|
||||||
});
|
});
|
||||||
@ -603,21 +491,12 @@ watch(() => route.query.vm, (newVm) => {
|
|||||||
showVMList.value = true;
|
showVMList.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch for changes in the VM list visibility
|
|
||||||
watch(() => showVMList.value, (isVisible) => {
|
|
||||||
if (isVisible && selectedVM.value) {
|
|
||||||
// Scroll to selected VM when the list becomes visible
|
|
||||||
nextTick(() => {
|
|
||||||
scrollToSelectedVM();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* VM Controls */
|
/* VM Controls Styles */
|
||||||
.vm-controls-container {
|
.vm-controls-container {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@ -798,15 +677,12 @@ watch(() => showVMList.value, (isVisible) => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-list {
|
.vm-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-y: auto;
|
|
||||||
max-height: calc(100vh - 200px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-list-item {
|
.vm-list-item {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user