debug logs
This commit is contained in:
+344
-80
@@ -493,8 +493,8 @@ function addVolumeMount(volumeData = null) {
|
|||||||
<select class="form-select bg-dark text-white volume-named-input"
|
<select class="form-select bg-dark text-white volume-named-input"
|
||||||
data-volume-named="${id}"
|
data-volume-named="${id}"
|
||||||
onchange="validateVolumeMount('${id}')"
|
onchange="validateVolumeMount('${id}')"
|
||||||
onfocus="loadVolumesForSelect('${id}')"
|
onfocus="if(this.options.length <= 1) loadVolumesForSelect('${id}')"
|
||||||
onclick="loadVolumesForSelect('${id}')">
|
onclick="if(this.options.length <= 1) loadVolumesForSelect('${id}')">
|
||||||
<option value="">Select or create volume...</option>
|
<option value="">Select or create volume...</option>
|
||||||
</select>
|
</select>
|
||||||
<small class="volume-error-msg" data-volume-named-error="${id}" style="display: none;"></small>
|
<small class="volume-error-msg" data-volume-named-error="${id}" style="display: none;"></small>
|
||||||
@@ -1075,6 +1075,15 @@ window.handleVolumeTypeChange = handleVolumeTypeChange;
|
|||||||
window.validateVolumeMount = validateVolumeMount;
|
window.validateVolumeMount = validateVolumeMount;
|
||||||
window.openFileBrowser = openFileBrowser;
|
window.openFileBrowser = openFileBrowser;
|
||||||
|
|
||||||
|
// Track loading state to prevent duplicate requests
|
||||||
|
const volumeLoadingStates = new Map();
|
||||||
|
|
||||||
|
// Store volumes data per select to restore if cleared
|
||||||
|
const volumeDataCache = new Map();
|
||||||
|
|
||||||
|
// Store active volume handlers to prevent them from being replaced
|
||||||
|
const activeVolumeHandlers = new Map();
|
||||||
|
|
||||||
// Load volumes for named volume select
|
// Load volumes for named volume select
|
||||||
async function loadVolumesForSelect(volumeId) {
|
async function loadVolumesForSelect(volumeId) {
|
||||||
console.log('[DEBUG] Loading volumes for select, volume ID:', volumeId);
|
console.log('[DEBUG] Loading volumes for select, volume ID:', volumeId);
|
||||||
@@ -1084,9 +1093,41 @@ async function loadVolumesForSelect(volumeId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if already loading to prevent duplicate requests
|
||||||
|
if (volumeLoadingStates.has(volumeId)) {
|
||||||
|
console.log('[DEBUG] Volumes already loading for this select, skipping duplicate request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if volumes are already loaded (more than just the placeholder)
|
||||||
|
// Only skip if we have actual volume options (not just placeholder)
|
||||||
|
const hasVolumeOptions = Array.from(namedSelect.options).some(opt => opt.value && opt.value !== '');
|
||||||
|
if (hasVolumeOptions && namedSelect.options.length > 1) {
|
||||||
|
console.log('[DEBUG] Volumes already loaded in select, skipping reload');
|
||||||
|
console.log('[DEBUG] Current options:', Array.from(namedSelect.options).map(opt => opt.textContent));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Show loading state
|
// Mark as loading
|
||||||
namedSelect.innerHTML = '<option value="">Loading volumes...</option>';
|
volumeLoadingStates.set(volumeId, true);
|
||||||
|
|
||||||
|
// Show loading state - preserve existing options if any
|
||||||
|
const currentValue = namedSelect.value;
|
||||||
|
const existingOptions = Array.from(namedSelect.options).filter(opt => opt.value && opt.value !== '');
|
||||||
|
|
||||||
|
// Only clear if we don't have volume options yet
|
||||||
|
if (!hasVolumeOptions) {
|
||||||
|
namedSelect.innerHTML = '<option value="">Loading volumes...</option>';
|
||||||
|
} else {
|
||||||
|
// Keep existing options, just add loading indicator
|
||||||
|
const loadingOption = namedSelect.querySelector('option[value="__loading__"]');
|
||||||
|
if (!loadingOption) {
|
||||||
|
const loadOpt = new Option('Loading...', '__loading__', false, true);
|
||||||
|
loadOpt.disabled = true;
|
||||||
|
namedSelect.add(loadOpt);
|
||||||
|
}
|
||||||
|
}
|
||||||
namedSelect.disabled = true;
|
namedSelect.disabled = true;
|
||||||
|
|
||||||
// Fetch volumes from server
|
// Fetch volumes from server
|
||||||
@@ -1102,39 +1143,50 @@ async function loadVolumesForSelect(volumeId) {
|
|||||||
let volumesReceived = false;
|
let volumesReceived = false;
|
||||||
const requestId = `volumes_${Date.now()}_${Math.random()}`;
|
const requestId = `volumes_${Date.now()}_${Math.random()}`;
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
let responseCount = 0;
|
||||||
|
|
||||||
console.log('[DEBUG] Setting up volume handler with request ID:', requestId);
|
console.log('[DEBUG] ===== Setting up volume handler =====');
|
||||||
|
console.log('[DEBUG] Request ID:', requestId);
|
||||||
|
console.log('[DEBUG] Volume ID:', volumeId);
|
||||||
|
console.log('[DEBUG] Original handler type:', typeof originalHandler);
|
||||||
|
console.log('[DEBUG] Current window.handlePeerResponse:', typeof window.handlePeerResponse);
|
||||||
|
|
||||||
const volumeHandler = (response) => {
|
const volumeHandler = (response) => {
|
||||||
|
responseCount++;
|
||||||
const elapsed = Date.now() - startTime;
|
const elapsed = Date.now() - startTime;
|
||||||
console.log(`[DEBUG] Volume handler received response after ${elapsed}ms`);
|
console.log(`[DEBUG] ===== Volume handler called (call #${responseCount}) =====`);
|
||||||
|
console.log(`[DEBUG] Elapsed time: ${elapsed}ms`);
|
||||||
|
console.log('[DEBUG] Request ID:', requestId);
|
||||||
|
console.log('[DEBUG] Handler is still active:', window.handlePeerResponse === volumeHandler);
|
||||||
console.log('[DEBUG] Full response:', JSON.stringify(response));
|
console.log('[DEBUG] Full response:', JSON.stringify(response));
|
||||||
console.log('[DEBUG] Response keys:', Object.keys(response || {}));
|
console.log('[DEBUG] Response keys:', Object.keys(response || {}));
|
||||||
console.log('[DEBUG] Response success:', response?.success);
|
console.log('[DEBUG] Response success:', response?.success);
|
||||||
console.log('[DEBUG] Response volumes:', response?.volumes);
|
console.log('[DEBUG] Response volumes:', response?.volumes);
|
||||||
console.log('[DEBUG] Response type:', response?.type);
|
console.log('[DEBUG] Response type:', response?.type);
|
||||||
console.log('[DEBUG] Is volumes array?', Array.isArray(response?.volumes));
|
console.log('[DEBUG] Is volumes array?', Array.isArray(response?.volumes));
|
||||||
|
console.log('[DEBUG] Volumes received flag:', volumesReceived);
|
||||||
|
|
||||||
// Log ALL responses to help debug
|
// Log ALL responses to help debug
|
||||||
if (elapsed < 100) {
|
if (elapsed < 100) {
|
||||||
console.log('[DEBUG] Early response - might be from previous request');
|
console.log('[DEBUG] Early response - might be from previous request');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a volumes list response - be more lenient
|
// Check if this is a volumes list response - be VERY lenient
|
||||||
// Accept any response with volumes array or success + volumes
|
// Accept any response with volumes array or success + volumes
|
||||||
const isVolumesResponse =
|
const isVolumesResponse =
|
||||||
(response && response.success === true && Array.isArray(response.volumes)) ||
|
(response && response.success === true && Array.isArray(response.volumes)) ||
|
||||||
(response && response.success === true && response.volumes !== undefined) ||
|
(response && response.success === true && response.volumes !== undefined) ||
|
||||||
(response && response.error && (response.error.includes('volume') || response.error.includes('Volume'))) ||
|
(response && response.error && (response.error.includes('volume') || response.error.includes('Volume'))) ||
|
||||||
(response && !response.type && Array.isArray(response.volumes)) ||
|
(response && !response.type && Array.isArray(response.volumes)) ||
|
||||||
(response && response.type === 'volumes' && response.data);
|
(response && response.type === 'volumes' && response.data) ||
|
||||||
|
(response && response.volumes && typeof response.volumes === 'object' && !Array.isArray(response.volumes) && response.volumes.Volumes);
|
||||||
|
|
||||||
console.log('[DEBUG] Is volumes response?', isVolumesResponse);
|
console.log('[DEBUG] Is volumes response?', isVolumesResponse);
|
||||||
|
|
||||||
if (!isVolumesResponse) {
|
if (!isVolumesResponse) {
|
||||||
// Not a volumes response, pass to original handler
|
// Not a volumes response, pass to original handler
|
||||||
console.log('[DEBUG] Not a volumes response, passing to original handler');
|
console.log('[DEBUG] Not a volumes response, passing to original handler');
|
||||||
if (typeof originalHandler === 'function') {
|
if (typeof originalHandler === 'function' && originalHandler !== volumeHandler) {
|
||||||
originalHandler(response);
|
originalHandler(response);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1143,114 +1195,294 @@ async function loadVolumesForSelect(volumeId) {
|
|||||||
if (volumesReceived) {
|
if (volumesReceived) {
|
||||||
// Already processed, pass to original handler
|
// Already processed, pass to original handler
|
||||||
console.log('[DEBUG] Already processed volumes response, passing to original handler');
|
console.log('[DEBUG] Already processed volumes response, passing to original handler');
|
||||||
if (typeof originalHandler === 'function') {
|
if (typeof originalHandler === 'function' && originalHandler !== volumeHandler) {
|
||||||
originalHandler(response);
|
originalHandler(response);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[DEBUG] Processing volumes response');
|
console.log('[DEBUG] ===== Processing volumes response =====');
|
||||||
|
|
||||||
// Handle success response - check multiple formats
|
// Handle success response - check multiple formats
|
||||||
let volumesArray = null;
|
let volumesArray = null;
|
||||||
|
|
||||||
if (response && response.success === true && Array.isArray(response.volumes)) {
|
if (response && response.success === true && Array.isArray(response.volumes)) {
|
||||||
volumesArray = response.volumes;
|
volumesArray = response.volumes;
|
||||||
|
console.log('[DEBUG] Matched format: success + volumes array');
|
||||||
} else if (response && response.type === 'volumes' && Array.isArray(response.data)) {
|
} else if (response && response.type === 'volumes' && Array.isArray(response.data)) {
|
||||||
// Handle format from main volumes view
|
// Handle format from main volumes view
|
||||||
volumesArray = response.data;
|
volumesArray = response.data;
|
||||||
|
console.log('[DEBUG] Matched format: type=volumes + data array');
|
||||||
} else if (response && Array.isArray(response.volumes)) {
|
} else if (response && Array.isArray(response.volumes)) {
|
||||||
volumesArray = response.volumes;
|
volumesArray = response.volumes;
|
||||||
|
console.log('[DEBUG] Matched format: volumes array (no success field)');
|
||||||
|
} else if (response && response.volumes && typeof response.volumes === 'object' && response.volumes.Volumes && Array.isArray(response.volumes.Volumes)) {
|
||||||
|
// Handle Docker API format: { volumes: { Volumes: [...] } }
|
||||||
|
volumesArray = response.volumes.Volumes;
|
||||||
|
console.log('[DEBUG] Matched format: Docker API format with Volumes property');
|
||||||
|
} else if (response && response.success === true && response.volumes && typeof response.volumes === 'object' && response.volumes.Volumes) {
|
||||||
|
volumesArray = response.volumes.Volumes;
|
||||||
|
console.log('[DEBUG] Matched format: success + volumes.Volumes');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (volumesArray !== null) {
|
if (volumesArray !== null) {
|
||||||
|
console.log('[DEBUG] Successfully extracted volumes array, count:', volumesArray.length);
|
||||||
volumesReceived = true;
|
volumesReceived = true;
|
||||||
window.handlePeerResponse = originalHandler;
|
|
||||||
|
// Remove this handler from active handlers
|
||||||
|
activeVolumeHandlers.delete(volumeId);
|
||||||
|
|
||||||
|
// Only restore original handler if it's not this handler
|
||||||
|
if (window.handlePeerResponse === volumeHandler) {
|
||||||
|
window.handlePeerResponse = originalHandler;
|
||||||
|
console.log('[DEBUG] Restored original handler');
|
||||||
|
} else {
|
||||||
|
console.warn('[WARN] Handler was already replaced! Current handler:', typeof window.handlePeerResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-find the select element to ensure we have the latest reference
|
||||||
|
const currentSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
|
if (!currentSelect) {
|
||||||
|
console.error('[ERROR] Named volume select element not found after receiving response');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[DEBUG] Adding volumes to select, count:', volumesArray.length);
|
console.log('[DEBUG] Adding volumes to select, count:', volumesArray.length);
|
||||||
|
console.log('[DEBUG] Select element found:', currentSelect);
|
||||||
|
console.log('[DEBUG] Select element ID:', currentSelect.id);
|
||||||
|
|
||||||
// Clear and rebuild options
|
// Store current value if any
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
const currentValue = currentSelect.value;
|
||||||
|
|
||||||
|
// Clear and rebuild options - use direct manipulation for reliability
|
||||||
|
// First, remove all existing options except keep track of what we're adding
|
||||||
|
while (currentSelect.options.length > 0) {
|
||||||
|
currentSelect.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[DEBUG] Cleared select, now building options');
|
||||||
|
|
||||||
|
// Add placeholder option
|
||||||
|
const placeholderOption = new Option('Select or create volume...', '', true, false);
|
||||||
|
currentSelect.add(placeholderOption);
|
||||||
|
|
||||||
// Add existing volumes
|
// Add existing volumes
|
||||||
if (volumesArray.length === 0) {
|
if (volumesArray.length === 0) {
|
||||||
const option = document.createElement('option');
|
const option = new Option('No volumes available', '', false, true);
|
||||||
option.value = '';
|
currentSelect.add(option);
|
||||||
option.textContent = 'No volumes available';
|
console.log('[DEBUG] Added "No volumes available" option');
|
||||||
option.disabled = true;
|
|
||||||
namedSelect.appendChild(option);
|
|
||||||
} else {
|
} else {
|
||||||
volumesArray.forEach(volume => {
|
console.log('[DEBUG] Processing volumes array:', volumesArray);
|
||||||
|
volumesArray.forEach((volume, index) => {
|
||||||
|
console.log(`[DEBUG] Processing volume ${index}:`, volume);
|
||||||
const volumeName = volume.Name || volume.name || (typeof volume === 'string' ? volume : null);
|
const volumeName = volume.Name || volume.name || (typeof volume === 'string' ? volume : null);
|
||||||
|
console.log(`[DEBUG] Extracted volume name: "${volumeName}"`);
|
||||||
if (volumeName) {
|
if (volumeName) {
|
||||||
const option = document.createElement('option');
|
const option = new Option(volumeName, volumeName, false, false);
|
||||||
option.value = volumeName;
|
currentSelect.add(option);
|
||||||
option.textContent = volumeName;
|
console.log(`[DEBUG] Added option for volume: "${volumeName}"`);
|
||||||
namedSelect.appendChild(option);
|
} else {
|
||||||
|
console.warn(`[WARN] Could not extract name from volume:`, volume);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
namedSelect.disabled = false;
|
// Restore previous selection if it still exists
|
||||||
|
if (currentValue && Array.from(currentSelect.options).some(opt => opt.value === currentValue)) {
|
||||||
|
currentSelect.value = currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSelect.disabled = false;
|
||||||
|
|
||||||
|
// Store volumes data for this select in case it gets cleared
|
||||||
|
volumeDataCache.set(volumeId, volumesArray);
|
||||||
|
|
||||||
|
// Remove loading option if it exists
|
||||||
|
const loadingOption = currentSelect.querySelector('option[value="__loading__"]');
|
||||||
|
if (loadingOption) {
|
||||||
|
loadingOption.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear loading state
|
||||||
|
volumeLoadingStates.delete(volumeId);
|
||||||
|
|
||||||
console.log('[DEBUG] Volumes added to select successfully');
|
console.log('[DEBUG] Volumes added to select successfully');
|
||||||
|
console.log('[DEBUG] Final select options count:', currentSelect.options.length);
|
||||||
|
console.log('[DEBUG] Final select options:', Array.from(currentSelect.options).map(opt => ({ value: opt.value, text: opt.textContent })));
|
||||||
|
console.log('[DEBUG] Final select value:', currentSelect.value);
|
||||||
|
console.log('[DEBUG] Select element in DOM:', document.contains(currentSelect));
|
||||||
|
|
||||||
|
// Set up a MutationObserver to detect if options are cleared
|
||||||
|
// Disconnect any existing observer for this select
|
||||||
|
if (window.volumeSelectObservers && window.volumeSelectObservers.has(volumeId)) {
|
||||||
|
window.volumeSelectObservers.get(volumeId).disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
const checkSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
|
if (checkSelect && checkSelect.options.length <= 1 && !volumeLoadingStates.has(volumeId)) {
|
||||||
|
console.warn('[WARN] Options were cleared! Restoring from cache...');
|
||||||
|
const cachedVolumes = volumeDataCache.get(volumeId);
|
||||||
|
if (cachedVolumes && cachedVolumes.length > 0) {
|
||||||
|
// Restore options
|
||||||
|
while (checkSelect.options.length > 0) {
|
||||||
|
checkSelect.remove(0);
|
||||||
|
}
|
||||||
|
checkSelect.add(new Option('Select or create volume...', '', true, false));
|
||||||
|
cachedVolumes.forEach(volume => {
|
||||||
|
const volumeName = volume.Name || volume.name;
|
||||||
|
if (volumeName) {
|
||||||
|
checkSelect.add(new Option(volumeName, volumeName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('[DEBUG] Options restored, count:', checkSelect.options.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(currentSelect, { childList: true, subtree: false });
|
||||||
|
|
||||||
|
// Store observer
|
||||||
|
if (!window.volumeSelectObservers) {
|
||||||
|
window.volumeSelectObservers = new Map();
|
||||||
|
}
|
||||||
|
window.volumeSelectObservers.set(volumeId, observer);
|
||||||
|
|
||||||
|
// Verify the options persist after delays
|
||||||
|
[100, 500, 1000].forEach(delay => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const verifySelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
|
if (verifySelect) {
|
||||||
|
console.log(`[DEBUG] Verification after ${delay}ms - Options count:`, verifySelect.options.length);
|
||||||
|
if (verifySelect.options.length <= 1 && !volumeLoadingStates.has(volumeId)) {
|
||||||
|
console.error(`[ERROR] Options were cleared after ${delay}ms!`);
|
||||||
|
// Restore from cache
|
||||||
|
const cachedVolumes = volumeDataCache.get(volumeId);
|
||||||
|
if (cachedVolumes && cachedVolumes.length > 0) {
|
||||||
|
console.log('[DEBUG] Restoring options from cache...');
|
||||||
|
while (verifySelect.options.length > 0) {
|
||||||
|
verifySelect.remove(0);
|
||||||
|
}
|
||||||
|
verifySelect.add(new Option('Select or create volume...', '', true, false));
|
||||||
|
cachedVolumes.forEach(volume => {
|
||||||
|
const volumeName = volume.Name || volume.name;
|
||||||
|
if (volumeName) {
|
||||||
|
verifySelect.add(new Option(volumeName, volumeName));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Handle error response
|
// Handle error response
|
||||||
else if (response && response.error) {
|
else if (response && response.error) {
|
||||||
|
console.error('[ERROR] ===== Volumes request returned error =====');
|
||||||
|
console.error('[ERROR] Error message:', response.error);
|
||||||
volumesReceived = true;
|
volumesReceived = true;
|
||||||
window.handlePeerResponse = originalHandler;
|
activeVolumeHandlers.delete(volumeId);
|
||||||
console.error('[ERROR] Failed to load volumes:', response.error);
|
|
||||||
|
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
if (window.handlePeerResponse === volumeHandler) {
|
||||||
const option = document.createElement('option');
|
window.handlePeerResponse = originalHandler;
|
||||||
option.value = '';
|
}
|
||||||
option.textContent = `Error: ${response.error}`;
|
|
||||||
option.disabled = true;
|
volumeLoadingStates.delete(volumeId);
|
||||||
namedSelect.appendChild(option);
|
|
||||||
namedSelect.disabled = false;
|
const currentSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
|
if (currentSelect) {
|
||||||
|
currentSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = '';
|
||||||
|
option.textContent = `Error: ${response.error}`;
|
||||||
|
option.disabled = true;
|
||||||
|
currentSelect.appendChild(option);
|
||||||
|
currentSelect.disabled = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Unexpected format but might still be valid
|
// Unexpected format but might still be valid
|
||||||
console.warn('[WARN] Unexpected volumes response format:', response);
|
console.warn('[WARN] ===== Unexpected volumes response format =====');
|
||||||
|
console.warn('[WARN] Response:', JSON.stringify(response));
|
||||||
if (response && response.volumes !== undefined) {
|
if (response && response.volumes !== undefined) {
|
||||||
volumesReceived = true;
|
volumesReceived = true;
|
||||||
window.handlePeerResponse = originalHandler;
|
activeVolumeHandlers.delete(volumeId);
|
||||||
|
|
||||||
|
const handlerInfo = activeVolumeHandlers.get(volumeId);
|
||||||
|
if (handlerInfo && window.handlePeerResponse === handlerInfo.protectedHandler) {
|
||||||
|
window.handlePeerResponse = originalHandler;
|
||||||
|
}
|
||||||
|
|
||||||
const volumesArray = Array.isArray(response.volumes) ? response.volumes : [];
|
const volumesArray = Array.isArray(response.volumes) ? response.volumes : [];
|
||||||
console.log('[DEBUG] Processing volumes in unexpected format, count:', volumesArray.length);
|
console.log('[DEBUG] Processing volumes in unexpected format, count:', volumesArray.length);
|
||||||
|
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
const currentSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
if (volumesArray.length === 0) {
|
if (currentSelect) {
|
||||||
const option = document.createElement('option');
|
currentSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
||||||
option.value = '';
|
if (volumesArray.length === 0) {
|
||||||
option.textContent = 'No volumes available';
|
const option = document.createElement('option');
|
||||||
option.disabled = true;
|
option.value = '';
|
||||||
namedSelect.appendChild(option);
|
option.textContent = 'No volumes available';
|
||||||
} else {
|
option.disabled = true;
|
||||||
volumesArray.forEach(volume => {
|
currentSelect.appendChild(option);
|
||||||
const volumeName = volume.Name || volume.name || volume;
|
} else {
|
||||||
if (volumeName) {
|
volumesArray.forEach(volume => {
|
||||||
const option = document.createElement('option');
|
const volumeName = volume.Name || volume.name || volume;
|
||||||
option.value = volumeName;
|
if (volumeName) {
|
||||||
option.textContent = volumeName;
|
const option = document.createElement('option');
|
||||||
namedSelect.appendChild(option);
|
option.value = volumeName;
|
||||||
}
|
option.textContent = volumeName;
|
||||||
});
|
currentSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
currentSelect.disabled = false;
|
||||||
}
|
}
|
||||||
namedSelect.disabled = false;
|
volumeLoadingStates.delete(volumeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the handler BEFORE sending command
|
// Create a wrapper that checks if this handler is still active
|
||||||
window.handlePeerResponse = volumeHandler;
|
const protectedHandler = (response) => {
|
||||||
console.log('[DEBUG] Volume handler set, ready to receive response');
|
const handlerInfo = activeVolumeHandlers.get(volumeId);
|
||||||
|
if (handlerInfo && handlerInfo.handler === volumeHandler) {
|
||||||
|
// Handler is still active, process the response
|
||||||
|
volumeHandler(response);
|
||||||
|
} else {
|
||||||
|
// Handler was replaced, pass to original handler
|
||||||
|
console.log('[DEBUG] Handler was replaced, passing response to original handler');
|
||||||
|
if (typeof originalHandler === 'function' && originalHandler !== volumeHandler) {
|
||||||
|
originalHandler(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store handler reference to prevent replacement
|
||||||
|
activeVolumeHandlers.set(volumeId, {
|
||||||
|
handler: volumeHandler,
|
||||||
|
protectedHandler: protectedHandler,
|
||||||
|
requestId: requestId,
|
||||||
|
startTime: startTime,
|
||||||
|
originalHandler: originalHandler
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set the protected handler BEFORE sending command
|
||||||
|
window.handlePeerResponse = protectedHandler;
|
||||||
|
console.log('[DEBUG] ===== Volume handler installed =====');
|
||||||
|
console.log('[DEBUG] Handler stored in activeVolumeHandlers:', activeVolumeHandlers.has(volumeId));
|
||||||
|
console.log('[DEBUG] window.handlePeerResponse === volumeHandler:', window.handlePeerResponse === volumeHandler);
|
||||||
|
|
||||||
// Send the command - always fetch fresh from server
|
// Send the command - always fetch fresh from server
|
||||||
console.log('[DEBUG] Sending listVolumes command to fetch fresh volumes list');
|
console.log('[DEBUG] ===== Sending listVolumes command =====');
|
||||||
try {
|
try {
|
||||||
window.sendCommand('listVolumes');
|
window.sendCommand('listVolumes');
|
||||||
console.log('[DEBUG] listVolumes command sent successfully');
|
console.log('[DEBUG] listVolumes command sent successfully');
|
||||||
|
console.log('[DEBUG] Waiting for response...');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ERROR] Failed to send listVolumes command:', error);
|
console.error('[ERROR] Failed to send listVolumes command:', error);
|
||||||
|
activeVolumeHandlers.delete(volumeId);
|
||||||
window.handlePeerResponse = originalHandler;
|
window.handlePeerResponse = originalHandler;
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
@@ -1259,36 +1491,68 @@ async function loadVolumesForSelect(volumeId) {
|
|||||||
option.disabled = true;
|
option.disabled = true;
|
||||||
namedSelect.appendChild(option);
|
namedSelect.appendChild(option);
|
||||||
namedSelect.disabled = false;
|
namedSelect.disabled = false;
|
||||||
|
volumeLoadingStates.delete(volumeId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout after 10 seconds (increased from 5)
|
// Timeout after 10 seconds
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (!volumesReceived) {
|
const elapsed = Date.now() - startTime;
|
||||||
console.warn('[WARN] Volumes list request timed out after 10 seconds');
|
console.warn('[WARN] ===== Volumes list request TIMED OUT =====');
|
||||||
console.warn('[WARN] Current handler:', window.handlePeerResponse === volumeHandler ? 'volumeHandler' : 'other');
|
console.warn('[WARN] Elapsed time:', elapsed, 'ms');
|
||||||
window.handlePeerResponse = originalHandler;
|
console.warn('[WARN] Request ID:', requestId);
|
||||||
volumesReceived = true; // Mark as received to prevent double handling
|
console.warn('[WARN] Volumes received:', volumesReceived);
|
||||||
|
console.warn('[WARN] Handler still active:', window.handlePeerResponse === volumeHandler);
|
||||||
|
console.warn('[WARN] Current handler type:', typeof window.handlePeerResponse);
|
||||||
|
console.warn('[WARN] Response count received:', responseCount);
|
||||||
|
console.warn('[WARN] Active handlers:', Array.from(activeVolumeHandlers.keys()));
|
||||||
|
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
if (!volumesReceived) {
|
||||||
const option = document.createElement('option');
|
// Remove from active handlers
|
||||||
option.value = '';
|
activeVolumeHandlers.delete(volumeId);
|
||||||
option.textContent = 'Request timed out - click to retry';
|
|
||||||
option.disabled = true;
|
// Only restore original handler if current handler is still our protected handler
|
||||||
namedSelect.appendChild(option);
|
const handlerInfo = activeVolumeHandlers.get(volumeId);
|
||||||
namedSelect.disabled = false;
|
if (handlerInfo && window.handlePeerResponse === handlerInfo.protectedHandler) {
|
||||||
|
window.handlePeerResponse = originalHandler;
|
||||||
|
console.warn('[WARN] Restored original handler after timeout');
|
||||||
|
} else {
|
||||||
|
console.warn('[WARN] Handler was already replaced before timeout');
|
||||||
|
console.warn('[WARN] Current handler:', typeof window.handlePeerResponse);
|
||||||
|
console.warn('[WARN] Expected handler:', typeof handlerInfo?.protectedHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
volumesReceived = true; // Mark as received to prevent double handling
|
||||||
|
volumeLoadingStates.delete(volumeId);
|
||||||
|
|
||||||
|
const currentSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
|
if (currentSelect) {
|
||||||
|
currentSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = '';
|
||||||
|
option.textContent = 'Request timed out - click to retry';
|
||||||
|
option.disabled = true;
|
||||||
|
currentSelect.appendChild(option);
|
||||||
|
currentSelect.disabled = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[DEBUG] Timeout fired but volumes were already received');
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ERROR] Failed to load volumes:', error);
|
console.error('[ERROR] Failed to load volumes:', error);
|
||||||
namedSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
volumeLoadingStates.delete(volumeId);
|
||||||
const option = document.createElement('option');
|
const currentSelect = document.querySelector(`[data-volume-named="${volumeId}"]`);
|
||||||
option.value = '';
|
if (currentSelect) {
|
||||||
option.textContent = `Error: ${error.message}`;
|
currentSelect.innerHTML = '<option value="">Select or create volume...</option>';
|
||||||
option.disabled = true;
|
const option = document.createElement('option');
|
||||||
namedSelect.appendChild(option);
|
option.value = '';
|
||||||
namedSelect.disabled = false;
|
option.textContent = `Error: ${error.message}`;
|
||||||
|
option.disabled = true;
|
||||||
|
currentSelect.appendChild(option);
|
||||||
|
currentSelect.disabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2428,8 +2692,8 @@ function addDuplicateVolumeMount(volumeData = null) {
|
|||||||
<select class="form-select bg-dark text-white volume-named-input"
|
<select class="form-select bg-dark text-white volume-named-input"
|
||||||
data-volume-named="${id}"
|
data-volume-named="${id}"
|
||||||
onchange="validateVolumeMount('${id}')"
|
onchange="validateVolumeMount('${id}')"
|
||||||
onfocus="loadVolumesForSelect('${id}')"
|
onfocus="if(this.options.length <= 1) loadVolumesForSelect('${id}')"
|
||||||
onclick="loadVolumesForSelect('${id}')">
|
onclick="if(this.options.length <= 1) loadVolumesForSelect('${id}')">
|
||||||
<option value="">Select or create volume...</option>
|
<option value="">Select or create volume...</option>
|
||||||
</select>
|
</select>
|
||||||
<small class="volume-error-msg" data-volume-named-error="${id}" style="display: none;"></small>
|
<small class="volume-error-msg" data-volume-named-error="${id}" style="display: none;"></small>
|
||||||
|
|||||||
+10
-2
@@ -1129,7 +1129,7 @@ swarm.on('connection', (peer) => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'listVolumes':
|
case 'listVolumes':
|
||||||
console.log('[INFO] Handling \'listVolumes\' command');
|
console.log('[INFO] ===== Handling \'listVolumes\' command =====');
|
||||||
try {
|
try {
|
||||||
const volumes = await docker.listVolumes();
|
const volumes = await docker.listVolumes();
|
||||||
const volumesList = volumes.Volumes || [];
|
const volumesList = volumes.Volumes || [];
|
||||||
@@ -1140,11 +1140,19 @@ swarm.on('connection', (peer) => {
|
|||||||
// This handler is for the volume selector in deploy modal
|
// This handler is for the volume selector in deploy modal
|
||||||
response = { success: true, volumes: volumesList };
|
response = { success: true, volumes: volumesList };
|
||||||
console.log(`[DEBUG] Prepared volumes response with ${volumesList.length} volumes`);
|
console.log(`[DEBUG] Prepared volumes response with ${volumesList.length} volumes`);
|
||||||
|
console.log(`[DEBUG] Response structure:`, JSON.stringify({
|
||||||
|
success: response.success,
|
||||||
|
volumesCount: response.volumes?.length || 0,
|
||||||
|
hasVolumes: !!response.volumes
|
||||||
|
}));
|
||||||
console.log(`[DEBUG] Response will be sent:`, !!response);
|
console.log(`[DEBUG] Response will be sent:`, !!response);
|
||||||
|
console.log(`[DEBUG] ===== listVolumes response ready =====`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[ERROR] Failed to list volumes: ${error.message}`);
|
console.error(`[ERROR] ===== Failed to list volumes =====`);
|
||||||
|
console.error(`[ERROR] Error message: ${error.message}`);
|
||||||
console.error(`[ERROR] Error stack:`, error.stack);
|
console.error(`[ERROR] Error stack:`, error.stack);
|
||||||
response = { success: false, error: `Failed to list volumes: ${error.message}` };
|
response = { success: false, error: `Failed to list volumes: ${error.message}` };
|
||||||
|
console.error(`[ERROR] Error response prepared:`, JSON.stringify(response));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user