URL-less VM view and persistence

This commit is contained in:
Joshua Boniface 2025-03-02 17:42:12 -05:00
parent 61dbe8eed1
commit e26c5defa4
6 changed files with 163 additions and 63 deletions

View File

@ -184,6 +184,9 @@ onMounted(() => {
// Set up click outside handler // Set up click outside handler
document.addEventListener('click', handleClickOutside); document.addEventListener('click', handleClickOutside);
// Get the selected VM from localStorage
const savedVMId = localStorage.getItem('selectedVMId');
// Restore search text from localStorage if available // Restore search text from localStorage if available
const savedSearchText = localStorage.getItem('vmSearchText'); const savedSearchText = localStorage.getItem('vmSearchText');
if (savedSearchText) { if (savedSearchText) {
@ -194,9 +197,20 @@ onMounted(() => {
loadFiltersFromLocalStorage(); loadFiltersFromLocalStorage();
// Initialize input value based on selected VM // Initialize input value based on selected VM
if (!props.showList && (props.selectedVM || props.vmFromUrl)) { if (!props.showList) {
inputValue.value = props.selectedVM || props.vmFromUrl; // If list is closed, show the selected VM name in the input
const effectiveVM = props.selectedVM || props.vmFromUrl || savedVMId;
if (effectiveVM) {
// Find the VM in the list to get its full name
const vm = props.vmList.find(v => v.name === effectiveVM);
if (vm) {
inputValue.value = vm.name;
} else {
inputValue.value = effectiveVM;
}
}
} else if (props.showList && props.modelValue) { } else if (props.showList && props.modelValue) {
// If list is open and there's a search query, show that
inputValue.value = props.modelValue; inputValue.value = props.modelValue;
searchText.value = props.modelValue; searchText.value = props.modelValue;
// Save to localStorage // Save to localStorage
@ -204,7 +218,7 @@ onMounted(() => {
} }
// If the list is visible on mount, scroll to selected VM // If the list is visible on mount, scroll to selected VM
if (props.showList && (props.selectedVM || props.vmFromUrl)) { if (props.showList && (props.selectedVM || props.vmFromUrl || savedVMId)) {
scrollToSelectedVM(); scrollToSelectedVM();
} }
}); });
@ -351,6 +365,9 @@ const clearSearch = () => {
const handleVMSelect = (vm) => { const handleVMSelect = (vm) => {
console.log('Selecting VM:', vm); console.log('Selecting VM:', vm);
// Update the input value to show the selected VM
inputValue.value = vm.name;
// Save the current search text before selecting a VM // Save the current search text before selecting a VM
if (props.modelValue) { if (props.modelValue) {
searchText.value = props.modelValue; searchText.value = props.modelValue;
@ -358,6 +375,9 @@ const handleVMSelect = (vm) => {
localStorage.setItem('vmSearchText', props.modelValue); localStorage.setItem('vmSearchText', props.modelValue);
} }
// Save selected VM to localStorage
localStorage.setItem('selectedVMId', vm.name);
emit('select-vm', vm); emit('select-vm', vm);
}; };
@ -539,7 +559,7 @@ const getStateClass = (state) => {
watch(() => props.showList, (isVisible) => { watch(() => props.showList, (isVisible) => {
if (!isVisible) { if (!isVisible) {
// When list is closed, show selected VM // When list is closed, show selected VM
const effectiveVM = props.selectedVM || props.vmFromUrl; const effectiveVM = props.selectedVM || props.vmFromUrl || localStorage.getItem('selectedVMId');
if (effectiveVM) { if (effectiveVM) {
inputValue.value = effectiveVM; inputValue.value = effectiveVM;
@ -569,7 +589,7 @@ watch(() => props.showList, (isVisible) => {
// Watch for changes in selectedVM or vmFromUrl // Watch for changes in selectedVM or vmFromUrl
watch([() => props.selectedVM, () => props.vmFromUrl], ([selectedVM, vmFromUrl]) => { watch([() => props.selectedVM, () => props.vmFromUrl], ([selectedVM, vmFromUrl]) => {
if (!props.showList) { if (!props.showList) {
const effectiveVM = selectedVM || vmFromUrl; const effectiveVM = selectedVM || vmFromUrl || localStorage.getItem('selectedVMId');
if (effectiveVM) { if (effectiveVM) {
inputValue.value = effectiveVM; inputValue.value = effectiveVM;
} }
@ -579,7 +599,7 @@ watch([() => props.selectedVM, () => props.vmFromUrl], ([selectedVM, vmFromUrl])
// Add a function to scroll to the selected VM // Add a function to scroll to the selected VM
const scrollToSelectedVM = () => { const scrollToSelectedVM = () => {
nextTick(() => { nextTick(() => {
const selectedVMName = props.selectedVM || props.vmFromUrl; const selectedVMName = props.selectedVM || props.vmFromUrl || localStorage.getItem('selectedVMId');
if (selectedVMName && vmListContainer.value) { if (selectedVMName && vmListContainer.value) {
const activeElement = vmListContainer.value.querySelector(`[data-vm-name="${selectedVMName}"]`); const activeElement = vmListContainer.value.querySelector(`[data-vm-name="${selectedVMName}"]`);
if (activeElement) { if (activeElement) {

View File

@ -15,7 +15,7 @@
v-for="vmName in runningDomains" v-for="vmName in runningDomains"
:key="vmName" :key="vmName"
class="vm-item" class="vm-item"
@click="selectVM(vmName)" @click="selectVMAndNavigate(vmName)"
> >
{{ vmName }} {{ vmName }}
</div> </div>
@ -26,6 +26,7 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { navigateToVM } from '../../../services/navigation';
const props = defineProps({ const props = defineProps({
vmData: { vmData: {
@ -69,10 +70,9 @@ const runningDomains = computed(() => {
return nodeData.value.running_domains; return nodeData.value.running_domains;
}); });
// Navigate to VM details // Navigate to VM details using the navigation service
const selectVM = (vmName) => { const selectVMAndNavigate = (vmName) => {
// Store the VM ID in localStorage so it will be selected when the VMs page loads navigateToVM(vmName, router);
localStorage.setItem('selectedVMId', vmName);
}; };
</script> </script>

View File

@ -147,7 +147,7 @@ const props = defineProps({
}); });
// Use ref and sync with route // Use ref and sync with route
const selectedVM = ref(route.query.vm || ''); const selectedVM = ref('');
const searchQuery = ref(''); const searchQuery = ref('');
const showVMList = ref(true); const showVMList = ref(true);
const searchActive = ref(false); const searchActive = ref(false);
@ -211,8 +211,10 @@ const selectVM = (vm) => {
// Close the VM list // Close the VM list
showVMList.value = false; showVMList.value = false;
// Update the URL // Save to localStorage
router.push({ query: { vm: vm.name } }); localStorage.setItem('selectedVMId', vm.name);
// No URL parameter update
}; };
// Clear search // Clear search
@ -224,7 +226,13 @@ const clearSearch = () => {
// Lifecycle hooks // Lifecycle hooks
onMounted(() => { onMounted(() => {
console.log('VMOverview mounted, route.query.vm:', route.query.vm); console.log('VMOverview mounted');
isLoading.value = true;
// Check sources in priority order:
// 1. localStorage (for returning to the page or navigating from other pages)
// 2. URL query parameter (for backward compatibility)
const savedVMId = localStorage.getItem('selectedVMId');
const vmFromQuery = route.query.vm; const vmFromQuery = route.query.vm;
// Restore previous search query from localStorage if available // Restore previous search query from localStorage if available
@ -233,64 +241,69 @@ onMounted(() => {
searchQuery.value = savedSearch; searchQuery.value = savedSearch;
} }
if (vmFromQuery) { let vmToSelect = null;
console.log('Setting selectedVM to:', vmFromQuery);
selectedVM.value = vmFromQuery; // Determine which VM to select
isLoading.value = true; if (savedVMId) {
console.log('VM from localStorage:', savedVMId);
vmToSelect = savedVMId;
} else if (vmFromQuery) {
console.log('VM from URL query:', vmFromQuery);
vmToSelect = vmFromQuery;
// Save to localStorage for persistence
localStorage.setItem('selectedVMId', vmFromQuery);
// Remove the VM ID from the URL without reloading the page
router.replace({ path: '/vms' }, { replace: true });
}
// If we have a VM to select and data is available
if (vmToSelect && props.vmData.length > 0) {
// Check if the VM exists
const vmExists = props.vmData.some(v => v.name === vmToSelect);
if (vmExists) {
selectedVM.value = vmToSelect;
showVMList.value = false; showVMList.value = false;
// If we have data, verify the VM exists // Set the search query to the VM name for display in the search bar
if (props.vmData.length) { searchQuery.value = vmToSelect;
const vmExists = props.vmData.some(v => v.name === vmFromQuery); } else {
invalidVMSelected.value = !vmExists; console.log('Selected VM does not exist:', vmToSelect);
invalidVMSelected.value = true;
localStorage.removeItem('selectedVMId');
} }
} else if (!vmToSelect && props.vmData.length > 0) {
// No VM selected, show the list
showVMList.value = true;
}
isLoading.value = false; isLoading.value = false;
} else if (props.vmData.length > 0) { });
showVMList.value = true;
invalidVMSelected.value = false; // Watch for changes in the selected VM
watch(selectedVM, (newValue) => {
if (newValue) {
// Save to localStorage whenever it changes
localStorage.setItem('selectedVMId', newValue);
} else {
localStorage.removeItem('selectedVMId');
} }
}); });
onUnmounted(() => { // Watch for changes in the VM data
// Remove event listeners and clean up resources
});
// Watch for route changes to update selectedVM
watch(() => route.query.vm, (newVm) => {
// Explicitly update the ref when route changes
selectedVM.value = newVm || '';
if (newVm) {
// Just update UI state, don't trigger another route change
invalidVMSelected.value = false;
showVMList.value = false;
} else if (props.vmData.length > 0) {
showVMList.value = true;
invalidVMSelected.value = false;
}
}, { immediate: 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(() => {
// Scroll to selected VM logic
});
}
});
// Add watcher for vmData to handle loading state
watch(() => props.vmData, (newData) => { watch(() => props.vmData, (newData) => {
if (selectedVM.value && newData.length) { if (selectedVM.value && newData.length > 0) {
// Verify the VM still exists
const vmExists = newData.some(vm => vm.name === selectedVM.value); const vmExists = newData.some(vm => vm.name === selectedVM.value);
invalidVMSelected.value = !vmExists; invalidVMSelected.value = !vmExists;
}
}, { immediate: true });
// Add debug watcher if (!vmExists) {
watch(() => selectedVM.value, (newVal) => { // If VM no longer exists, clear localStorage
console.log('VMOverview selectedVM changed:', newVal); localStorage.removeItem('selectedVMId');
}, { immediate: true }); }
}
}, { deep: true });
// Helper functions // Helper functions
const getStateColor = (state) => { const getStateColor = (state) => {

View File

@ -0,0 +1,27 @@
import { useRouter } from 'vue-router';
/**
* Navigate to a VM without using URL parameters
* @param {string} vmId - The ID of the VM to navigate to
* @param {import('vue-router').Router} router - Vue Router instance
*/
export function navigateToVM(vmId, router) {
// Save the VM ID to localStorage
localStorage.setItem('selectedVMId', vmId);
// Navigate to the VMs page
router.push('/vms');
}
/**
* Navigate to a node without using URL parameters
* @param {string} nodeId - The ID of the node to navigate to
* @param {import('vue-router').Router} router - Vue Router instance
*/
export function navigateToNode(nodeId, router) {
// Save the node ID to localStorage
localStorage.setItem('selectedNodeId', nodeId);
// Navigate to the Nodes page
router.push('/nodes');
}

View File

@ -0,0 +1,32 @@
/**
* Selects a VM and saves the selection to localStorage
* @param {string} vmId - The ID of the VM to select
* @param {import('vue-router').Router} router - Vue Router instance (optional)
*/
export function selectVM(vmId, router = null) {
// Save to localStorage
localStorage.setItem('selectedVMId', vmId);
// Optionally update URL if router is provided
if (router) {
router.push({
path: '/vms',
query: { vm: vmId }
});
}
}
/**
* Gets the currently selected VM from localStorage
* @returns {string|null} The selected VM ID or null if none is selected
*/
export function getSelectedVM() {
return localStorage.getItem('selectedVMId');
}
/**
* Clears the selected VM from localStorage
*/
export function clearSelectedVM() {
localStorage.removeItem('selectedVMId');
}

View File

@ -11,9 +11,14 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import PageTitle from '../components/general/PageTitle.vue'; import PageTitle from '../components/general/PageTitle.vue';
import VMOverview from '../components/pages/vms/VMOverview.vue'; import VMOverview from '../components/pages/vms/VMOverview.vue';
const router = useRouter();
const route = useRoute();
const props = defineProps({ const props = defineProps({
vmData: { vmData: {
type: Array, type: Array,
@ -35,6 +40,9 @@ const props = defineProps({
default: false default: false
} }
}); });
// We'll handle the VM selection in the VMOverview component
// This parent component just needs to pass the data
</script> </script>
<style scoped> <style scoped>