diff --git a/pvc-vue/src/components/general/VMSearchBar.vue b/pvc-vue/src/components/general/VMSearchBar.vue
index a8830b0..fd23a27 100644
--- a/pvc-vue/src/components/general/VMSearchBar.vue
+++ b/pvc-vue/src/components/general/VMSearchBar.vue
@@ -94,7 +94,7 @@
:data-vm-name="vm.name"
class="vm-list-item"
:class="{ 'active': selectedVM === vm.name || vmFromUrl === vm.name }"
- @click="() => { console.log('VM clicked:', vm); handleVMSelect(vm); }"
+ @click="handleVMSelect(vm)"
>
@@ -140,15 +140,6 @@ const props = defineProps({
}
});
-// Direct debug log of props
-console.log('VMSearchBar props:', {
- modelValue: props.modelValue,
- showList: props.showList,
- selectedVM: props.selectedVM,
- vmFromUrl: props.vmFromUrl,
- vmListLength: props.vmList.length
-});
-
const emit = defineEmits([
'update:modelValue',
'search',
@@ -161,71 +152,187 @@ const emit = defineEmits([
'reset-filters'
]);
+// UI state refs
const showFilterMenu = ref(false);
const filterDropdown = ref(null);
const filterMenu = ref(null);
const controlsBar = ref(null);
+const vmListContainer = ref(null);
-// Add local state for filters
+// Search state refs
+const inputValue = ref('');
+const searchText = ref('');
+const isFilterActive = ref(false);
+
+// Filter state
const appliedFilters = ref({
states: {},
nodes: {}
});
-// Add new ref for tracking search active state
-const searchActive = ref(false);
+// Initialize the component
+onMounted(() => {
+ // Set up click outside handler
+ document.addEventListener('click', handleClickOutside);
+
+ // Initialize input value based on selected VM
+ if (!props.showList && (props.selectedVM || props.vmFromUrl)) {
+ inputValue.value = props.selectedVM || props.vmFromUrl;
+ } else if (props.showList && props.modelValue) {
+ inputValue.value = props.modelValue;
+ searchText.value = props.modelValue;
+ }
+});
-// Local ref to track input value
-const inputValue = ref('');
+onUnmounted(() => {
+ document.removeEventListener('click', handleClickOutside);
+});
-// Add a ref to the VM list container
-const vmListContainer = ref(null);
+// Handle search input
+const handleSearch = (event) => {
+ const value = event.target.value;
+ inputValue.value = value;
+ searchText.value = value;
+ isFilterActive.value = true;
+
+ emit('update:modelValue', value);
+ emit('search', value);
+};
-// Add a ref to store the last search query
-const lastSearchQuery = ref('');
-
-// Add a flag to track if filtering should be applied
-const filterActive = ref(false);
-
-// Add a flag to track if a search has been performed
-const hasSearched = ref(false);
-
-// Add a flag to track if we should maintain focus after list refresh
-const maintainFocusAfterRefresh = ref(false);
-
-// Watch both showList and selectedVM to update input value
-watch([() => props.showList, () => props.selectedVM, () => props.vmFromUrl, () => props.modelValue],
- ([showList, selectedVM, vmFromUrl, modelValue]) => {
- console.log('Watch triggered:', { showList, selectedVM, vmFromUrl, modelValue });
+// Handle focus on search input
+const handleFocus = (event) => {
+ // If the list is not shown, show it
+ if (!props.showList) {
+ emit('toggle-list');
+ } else {
+ // If the list is already shown, activate filtering
+ isFilterActive.value = true;
- if (!showList) {
- // When list is closed, show selected VM or VM from URL
- const effectiveVM = selectedVM || vmFromUrl;
- if (effectiveVM) {
- inputValue.value = effectiveVM;
- }
- } else {
- // When list is open, show search query if it exists
- if (modelValue) {
- inputValue.value = modelValue;
- searchActive.value = true;
- } else {
- // If no search query, show placeholder
- inputValue.value = '';
- }
+ // Restore search text if available
+ if (searchText.value) {
+ inputValue.value = searchText.value;
+ emit('update:modelValue', searchText.value);
}
- }, { immediate: true }
-);
-
-// Watch modelValue to update input when searching
-watch(() => props.modelValue, (newVal) => {
- if (newVal) {
- lastSearchQuery.value = newVal;
}
+ emit('focus', event);
+};
+
+// Handle blur on search input
+const handleBlur = (event) => {
+ emit('blur', event);
+};
+
+// Handle click on search input
+const handleSearchClick = () => {
if (props.showList) {
- inputValue.value = newVal || '';
+ // When clicking the search input while list is open, activate filtering
+ isFilterActive.value = true;
+
+ // Restore search text if available
+ if (searchText.value && searchText.value !== inputValue.value) {
+ inputValue.value = searchText.value;
+ emit('update:modelValue', searchText.value);
+ }
}
+};
+
+// Toggle the VM list
+const toggleList = () => {
+ if (props.showList) {
+ // If we're closing the list, save the search text
+ if (props.modelValue) {
+ searchText.value = props.modelValue;
+ }
+ } else {
+ // If we're opening the list, deactivate filtering
+ isFilterActive.value = false;
+
+ // Restore search text in the input, but don't apply filtering
+ if (searchText.value) {
+ inputValue.value = searchText.value;
+ emit('update:modelValue', searchText.value);
+ }
+ }
+
+ emit('toggle-list');
+};
+
+// Clear search
+const clearSearch = () => {
+ inputValue.value = '';
+ searchText.value = '';
+ isFilterActive.value = false;
+ emit('update:modelValue', '');
+ emit('clear');
+};
+
+// Select a VM
+const handleVMSelect = (vm) => {
+ console.log('Selecting VM:', vm);
+ emit('select-vm', vm);
+};
+
+// Handle click outside
+const handleClickOutside = (event) => {
+ if (showFilterMenu.value &&
+ filterMenu.value &&
+ !filterMenu.value.contains(event.target) &&
+ !filterDropdown.value.contains(event.target)) {
+ showFilterMenu.value = false;
+ }
+};
+
+// Toggle filter menu
+const toggleFilterMenu = () => {
+ showFilterMenu.value = !showFilterMenu.value;
+};
+
+// Filter toggle
+const handleFilterToggle = (type, value) => {
+ appliedFilters.value[type][value] = !appliedFilters.value[type][value];
+};
+
+// Reset filters
+const handleResetFilters = () => {
+ Object.keys(appliedFilters.value.states).forEach(state => {
+ appliedFilters.value.states[state] = false;
+ });
+ Object.keys(appliedFilters.value.nodes).forEach(node => {
+ appliedFilters.value.nodes[node] = false;
+ });
+ showFilterMenu.value = false;
+};
+
+// Computed properties
+const filteredVMs = computed(() => {
+ let filtered = [...props.vmList];
+
+ // Apply text filter only if filtering is active
+ if (isFilterActive.value && props.modelValue) {
+ const query = props.modelValue.toLowerCase();
+ filtered = filtered.filter(vm =>
+ vm.name.toLowerCase().includes(query)
+ );
+ }
+
+ // Apply state filters
+ const activeStates = Object.entries(appliedFilters.value.states)
+ .filter(([_, isActive]) => isActive)
+ .map(([state]) => state);
+ if (activeStates.length > 0) {
+ filtered = filtered.filter(vm => activeStates.includes(vm.state));
+ }
+
+ // Apply node filters
+ const activeNodes = Object.entries(appliedFilters.value.nodes)
+ .filter(([_, isActive]) => isActive)
+ .map(([node]) => node);
+ if (activeNodes.length > 0) {
+ filtered = filtered.filter(vm => activeNodes.includes(vm.node));
+ }
+
+ return filtered;
});
// Calculate available states from vmList
@@ -234,23 +341,7 @@ const availableStates = computed(() => {
props.vmList.forEach(vm => {
if (vm.state) states.add(vm.state);
});
- const statesArray = Array.from(states);
-
- // Remove 'start' if it exists
- const startIndex = statesArray.indexOf('start');
- if (startIndex !== -1) {
- statesArray.splice(startIndex, 1);
- }
-
- // Sort remaining states
- statesArray.sort();
-
- // Add 'start' back at beginning if it was present
- if (startIndex !== -1) {
- statesArray.unshift('start');
- }
-
- return statesArray;
+ return Array.from(states).sort();
});
// Calculate available nodes from vmList
@@ -279,24 +370,7 @@ watch([availableStates, availableNodes], ([states, nodes]) => {
});
});
-// Update handleFilterToggle to work with local state
-const handleFilterToggle = (type, value) => {
- searchActive.value = true;
- appliedFilters.value[type][value] = !appliedFilters.value[type][value];
-};
-
-// Update handleResetFilters
-const handleResetFilters = () => {
- Object.keys(appliedFilters.value.states).forEach(state => {
- appliedFilters.value.states[state] = false;
- });
- Object.keys(appliedFilters.value.nodes).forEach(node => {
- appliedFilters.value.nodes[node] = false;
- });
- showFilterMenu.value = false;
-};
-
-// Computed
+// Count active filters
const activeFiltersCount = computed(() => {
let count = 0;
Object.values(appliedFilters.value.states).forEach(isActive => {
@@ -308,307 +382,47 @@ const activeFiltersCount = computed(() => {
return count;
});
-const filteredVMs = computed(() => {
- let filtered = [...props.vmList];
-
- // Apply search filter if filtering is active OR if a search has been performed
- if ((filterActive.value || hasSearched.value) && props.modelValue) {
- const query = props.modelValue.toLowerCase();
- filtered = filtered.filter(vm =>
- vm.name.toLowerCase().includes(query)
- );
- }
-
- // Only apply other filters if search is active
- if (searchActive.value) {
- // Apply state filters
- const activeStates = Object.entries(appliedFilters.value.states)
- .filter(([_, isActive]) => isActive)
- .map(([state]) => state);
- if (activeStates.length > 0) {
- filtered = filtered.filter(vm => activeStates.includes(vm.state));
- }
-
- // Apply node filters
- const activeNodes = Object.entries(appliedFilters.value.nodes)
- .filter(([_, isActive]) => isActive)
- .map(([node]) => node);
- if (activeNodes.length > 0) {
- filtered = filtered.filter(vm => activeNodes.includes(vm.node));
- }
- }
-
- return filtered;
-});
-
-// Methods
-const handleSearch = (event) => {
- // Always update the model value to preserve search
- searchActive.value = true;
- filterActive.value = true; // Activate filtering when typing in search
- lastSearchQuery.value = event.target.value;
- hasSearched.value = true; // Mark that a search has been performed
- maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
- emit('update:modelValue', event.target.value);
- emit('search', event.target.value);
-};
-
-const handleFocus = (event) => {
- searchActive.value = true;
- filterActive.value = true; // Activate filtering when focusing the search bar
- maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
-
- // If there's a saved search, make sure it's applied
- if (props.modelValue) {
- inputValue.value = props.modelValue;
-
- // Force a re-evaluation of the filteredVMs computed property
- nextTick(() => {
- // This is a hack to force Vue to re-evaluate the computed property
- const temp = filteredVMs.value.length;
- console.log('Forcing filter application, filtered count:', temp);
- });
- }
- emit('focus', event);
-};
-
-const handleBlur = (event) => {
- // Only deactivate filtering if no search has been performed
- if (!hasSearched.value) {
- filterActive.value = false;
- }
-
- // Check if the blur was caused by a click outside the component
- const isClickOutside = !controlsBar.value?.contains(event.relatedTarget);
- if (isClickOutside) {
- maintainFocusAfterRefresh.value = false;
- }
-
- emit('blur', event);
-};
-
-const clearSearch = () => {
- emit('update:modelValue', '');
- emit('clear');
-};
-
-const toggleList = () => {
- console.log('toggleList called, showList:', props.showList);
-
- // If we're opening the list (currently closed)
- if (!props.showList) {
- // When opening the list via List VMs button, don't apply filtering initially
- // Only deactivate filtering if no search has been performed
- if (!hasSearched.value) {
- filterActive.value = false;
- }
-
- // But always restore the previous search text
- if (lastSearchQuery.value) {
- // Ensure the model value is preserved
- if (props.modelValue !== lastSearchQuery.value) {
- emit('update:modelValue', lastSearchQuery.value);
- }
- }
- }
- // If we're closing the list (currently open)
- else {
- // When closing, save the current search
- if (props.modelValue) {
- lastSearchQuery.value = props.modelValue;
- // Don't clear the model value when closing
- }
- }
-
- emit('toggle-list');
-};
-
-const handleVMSelect = (vm) => {
- console.log('Selecting VM:', vm);
-
- // Don't clear the search query or change filter state
- // Just emit the select-vm event
- emit('select-vm', vm);
-};
-
-const toggleFilterMenu = () => {
- showFilterMenu.value = !showFilterMenu.value;
-};
-
+// Status class helper
const getStatusClass = (state) => {
- if (!state) return '';
-
+ if (!state) return 'status-unknown';
switch(state.toLowerCase()) {
- case 'start':
- return 'status-running';
- case 'stop':
- return 'status-stopped';
- case 'disable':
- return 'status-disabled';
- default:
- return '';
+ case 'start': return 'status-running';
+ case 'stop': return 'status-stopped';
+ case 'disable': return 'status-paused';
+ default: return 'status-unknown';
}
};
-// Update click outside handling to close both filter menu and list
-const handleClickOutside = (event) => {
- // If click is outside the controls bar
- if (!controlsBar.value?.contains(event.target)) {
- if (props.showList) {
- emit('toggle-list'); // Close the list
- }
- showFilterMenu.value = false; // Close filter menu
- }
- // If click is inside controls bar but outside filter dropdown
- else if (showFilterMenu.value && !filterDropdown.value?.contains(event.target)) {
- showFilterMenu.value = false;
- }
-};
-
-onMounted(() => {
- document.addEventListener('click', handleClickOutside);
-
- // Add click handler for search input
- const searchInput = document.querySelector('.search-input');
- if (searchInput) {
- searchInput.addEventListener('click', () => {
- if (props.showList && props.modelValue) {
- filterActive.value = true;
- applyFilter();
- }
- });
- }
-});
-
-onUnmounted(() => {
- document.removeEventListener('click', handleClickOutside);
-});
-
-// Add a watcher for selectedVM with a debug log
-watch(() => props.selectedVM, (newVal, oldVal) => {
- console.log('VMSearchBar selectedVM changed:', { newVal, oldVal });
- // Update input value when selectedVM changes
- if (!props.showList && newVal) {
- inputValue.value = newVal;
- }
-}, { immediate: true });
-
-// Add a function to scroll to the selected VM
-const scrollToSelectedVM = () => {
- if (!props.showList || (!props.selectedVM && !props.vmFromUrl)) return;
-
- nextTick(() => {
- const selectedVMName = props.selectedVM || props.vmFromUrl;
- const activeElement = document.querySelector(`.vm-list-item[data-vm-name="${selectedVMName}"]`);
- if (activeElement && vmListContainer.value) {
- activeElement.scrollIntoView({ block: 'center', behavior: 'smooth' });
- }
- });
-};
-
-// Update the watch for showList to handle search state
-watch(() => props.showList, (isOpen) => {
- if (isOpen) {
- // When list opens, restore the last search query
- nextTick(() => {
- if (lastSearchQuery.value) {
- inputValue.value = lastSearchQuery.value;
-
- // If the user clicked on the search bar to open it, apply the filter
- if (document.activeElement &&
- document.activeElement.classList.contains('search-input')) {
- filterActive.value = true;
- applyFilter();
- }
- }
- });
-
- // Also scroll to selected VM
- scrollToSelectedVM();
- } else {
- // When list closes, show the selected VM but preserve search
+// Watch for changes in showList
+watch(() => props.showList, (isVisible) => {
+ if (!isVisible) {
+ // When list is closed, show selected VM
const effectiveVM = props.selectedVM || props.vmFromUrl;
if (effectiveVM) {
inputValue.value = effectiveVM;
}
- // Reset filter active state when closing
- filterActive.value = false;
+ } else {
+ // When list is opened, show search text if available
+ if (searchText.value) {
+ inputValue.value = searchText.value;
+ } else {
+ inputValue.value = '';
+ }
+
+ // Only activate filtering if opened via search input, not List VMs button
+ // This is handled in handleFocus and toggleList
}
});
-// Also call it when selectedVM or vmFromUrl changes
-watch([() => props.selectedVM, () => props.vmFromUrl], () => {
- if (props.showList) {
- scrollToSelectedVM();
- }
-});
-
-// Add a method to explicitly apply the filter
-const applyFilter = () => {
- filterActive.value = true;
- // Force computed property re-evaluation
- nextTick(() => {
- const temp = filteredVMs.value.length;
- console.log('Filter applied, filtered count:', temp);
- });
-};
-
-// Update the watch for props.vmList to maintain focus
-watch(() => props.vmList, () => {
- // When VM list refreshes, ensure search is preserved
- if (props.showList && lastSearchQuery.value) {
- // Make sure the model value is preserved
- if (props.modelValue !== lastSearchQuery.value) {
- emit('update:modelValue', lastSearchQuery.value);
- }
-
- // If a search has been performed, keep filtering active
- if (hasSearched.value && props.modelValue) {
- filterActive.value = true;
-
- // If we should maintain focus, refocus the search input
- if (maintainFocusAfterRefresh.value) {
- nextTick(() => {
- const searchInput = document.querySelector('.search-input');
- if (searchInput) {
- searchInput.focus();
- }
- });
- }
-
- nextTick(() => {
- applyFilter();
- });
+// Watch for changes in selectedVM or vmFromUrl
+watch([() => props.selectedVM, () => props.vmFromUrl], ([selectedVM, vmFromUrl]) => {
+ if (!props.showList) {
+ const effectiveVM = selectedVM || vmFromUrl;
+ if (effectiveVM) {
+ inputValue.value = effectiveVM;
}
}
});
-
-// Update handleSearchClick to set maintainFocusAfterRefresh
-const handleSearchClick = () => {
- if (props.showList) {
- // When clicking the search input while list is open, always apply filtering
- filterActive.value = true;
- maintainFocusAfterRefresh.value = true; // Maintain focus after refresh
-
- // If there's a saved search, make sure it's applied to the model
- if (lastSearchQuery.value && !props.modelValue) {
- emit('update:modelValue', lastSearchQuery.value);
- }
-
- // Force filter application
- applyFilter();
- }
-};
-
-// Add a method to force focus on the search input
-const focusSearchInput = () => {
- nextTick(() => {
- const searchInput = document.querySelector('.search-input');
- if (searchInput) {
- searchInput.focus();
- }
- });
-};