diff --git a/src/public/app.js b/src/public/app.js
index 2ea942bed..8c4e682ab 100644
--- a/src/public/app.js
+++ b/src/public/app.js
@@ -96,7 +96,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (activeContent) {
activeContent.classList.remove('hidden');
- // --- Handle nested tabs within 'main' ---
+ // --- Handle nested tabs within 'main' ---
if (tabId === 'main') {
// Ensure the 'Dashboard' nested tab is active by default when switching TO 'main'
activateNestedTab('nested-tab-dashboard');
@@ -202,7 +202,6 @@ document.addEventListener('DOMContentLoaded', function() {
};
// --- Threshold Logging State ---
- let isThresholdLoggingActive = false; // Keep for potential UI state, but use activeLogSessions for logic
let activeLogSessions = {}; // Object to hold multiple logging sessions { sessionId: { thresholds: {}, startTime: Date, durationMs: number, timerId: number, entries: [], element: node } }
let thresholdLogEntries = [];
let activeLoggingThresholds = null; // Will store the thresholds active at logging start
@@ -339,6 +338,7 @@ document.addEventListener('DOMContentLoaded', function() {
applyInitialFilterUI();
updateThresholdIndicator(); // Set initial indicator state
+ updateLogControlsVisibility(); // Check visibility on initial load
// Get references to threshold row and toggle button
const thresholdRow = document.getElementById('threshold-slider-row');
@@ -400,6 +400,7 @@ document.addEventListener('DOMContentLoaded', function() {
updateDashboardTable();
saveFilterState(); // Save state whenever a threshold changes
updateThresholdIndicator(); // Update badge count
+ updateLogControlsVisibility(); // Update button visibility
}
// Function to get formatted display text for a threshold type and value
@@ -671,6 +672,7 @@ document.addEventListener('DOMContentLoaded', function() {
updateDashboardTable(); // Update the main dashboard table
if (searchInput) searchInput.dispatchEvent(new Event('input')); // Re-apply text filter
saveFilterState(); // ---> ADDED: Save state <---
+ updateLogControlsVisibility(); // Update button visibility
}
});
});
@@ -678,6 +680,7 @@ document.addEventListener('DOMContentLoaded', function() {
if (searchInput) {
searchInput.addEventListener('input', function() {
updateDashboardTable();
+ updateLogControlsVisibility(); // Update button visibility
});
} else {
console.warn('Element #dashboard-search not found - text filtering disabled.');
@@ -698,6 +701,7 @@ document.addEventListener('DOMContentLoaded', function() {
updateDashboardTable(); // Re-render the table
if (searchInput) searchInput.dispatchEvent(new Event('input')); // Re-apply text filter if needed
saveFilterState(); // ---> ADDED: Save state <---
+ updateLogControlsVisibility(); // Update button visibility
}
});
});
@@ -1389,17 +1393,9 @@ document.addEventListener('DOMContentLoaded', function() {
refreshDashboardData(); // Ensure dashboardData is up-to-date with latest averages
- // --- Define threshold prefixes used for search term filtering ---
- // const thresholdPrefixes = ['cpu>=', 'memory>=', 'disk>=', 'uptime>=', 'diskread>=', 'diskwrite>=', 'netin>=', 'netout>=']; // REMOVED
-
// Get raw search terms, split by comma
const textSearchTerms = searchInput ? searchInput.value.toLowerCase().split(',').map(term => term.trim()).filter(term => term) : [];
- // Separate search terms from threshold filter tags
- // const textSearchTerms = rawSearchTerms.filter(term => !thresholdPrefixes.some(prefix => term.startsWith(prefix))); // REMOVED separation
- // Note: We don't actually need to *use* the threshold tags parsed from the search bar for filtering,
- // because the filtering happens based on the actual thresholdState object below.
-
let filteredData = dashboardData.filter(guest => {
// 1. Type Filter
const typeMatch = filterGuestType === 'all' || (filterGuestType === 'vm' && guest.type === 'VM') || (filterGuestType === 'lxc' && guest.type === 'CT');
@@ -1711,11 +1707,6 @@ document.addEventListener('DOMContentLoaded', function() {
function resetThresholds() {
for (const type in thresholdState) {
if (sliders[type]) { // Slider
- // If logging is active while resetting, stop it.
- if (isThresholdLoggingActive && toggleLogModeButton) {
- toggleLogModeButton.click(); // Simulate click to stop logging cleanly
- }
-
thresholdState[type].value = 0;
const sliderElement = sliders[type];
if (sliderElement) sliderElement.value = 0;
@@ -1759,6 +1750,8 @@ document.addEventListener('DOMContentLoaded', function() {
updateDashboardTable(); // Update table AFTER resetting everything
if (searchInput) searchInput.blur(); // Blur search input after reset
+ // Call visibility check AFTER resetting filters
+ updateLogControlsVisibility();
const backupStatusAllRadio = document.getElementById('backups-filter-status-all');
if(backupStatusAllRadio) backupStatusAllRadio.checked = true;
@@ -2822,24 +2815,6 @@ document.addEventListener('DOMContentLoaded', function() {
// --- START Threshold Logging Functions ---
function startThresholdLogging() {
- /* // REMOVED Check: Allow multiple logs
- if (Object.keys(activeLogSessions).length > 0) {
- console.warn("Threshold logging already active. Stop the current session first.");
- // Optionally provide user feedback here (e.g., flash the button)
- return;
- }
- */
-
- // --- Get Duration ---
- const durationMinutes = parseInt(logDurationSelect.value); // Use select value
- if (isNaN(durationMinutes) || durationMinutes <= 0) {
- alert("Please select a valid duration.");
- logDurationSelect.focus();
- return;
- }
- const durationMs = durationMinutes * 60 * 1000;
- // --- End Get Duration ---
-
// --- Snapshot current filter state ---
const snapshottedThresholds = {};
let activeThresholdCount = 0;
@@ -2922,6 +2897,11 @@ document.addEventListener('DOMContentLoaded', function() {
if (tabToRemove && tabToRemove.classList.contains('active')) {
activateNestedTab('nested-tab-dashboard');
}
+
+ // Hide the entire log area if no log tabs are left
+ if (nestedTabsContainer && logSessionArea && nestedTabsContainer.querySelectorAll('.nested-tab[data-nested-tab^="log-session-"]').length === 0) {
+ logSessionArea.classList.add('hidden');
+ }
}
};
@@ -2954,7 +2934,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
- Logging started for ${durationMinutes} min...
+ Logging started continuously...
|
@@ -2976,7 +2956,6 @@ document.addEventListener('DOMContentLoaded', function() {
newTab.innerHTML = `
${startTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
- (--:--)
`;
newTab.addEventListener('click', () => activateNestedTab(nestedTabId));
@@ -3057,6 +3036,11 @@ document.addEventListener('DOMContentLoaded', function() {
if (tabElement.classList.contains('active')) {
activateNestedTab('nested-tab-dashboard');
}
+
+ // Hide the entire log area if no log tabs are left
+ if (nestedTabsContainer && logSessionArea && nestedTabsContainer.querySelectorAll('.nested-tab[data-nested-tab^="log-session-"]').length === 0) {
+ logSessionArea.classList.add('hidden');
+ }
};
newTab.appendChild(tabCloseButton);
// --- END Add Close Button to Tab ---
@@ -3089,18 +3073,10 @@ document.addEventListener('DOMContentLoaded', function() {
// } // REMOVED
// --- END Append panel ---
- // Start the timer to automatically stop the session
- const timerId = setTimeout(() => {
- // Pass 'timer' reason when stopped by timeout
- stopThresholdLogging(sessionId, 'timer');
- }, durationMs);
-
// Store session info
activeLogSessions[sessionId] = {
thresholds: snapshottedThresholds,
startTime: startTime,
- durationMs: durationMs,
- timerId: timerId,
entries: [],
element: panel, // Reference to the DOM element for easy removal
searchTerms: snapshottedSearchTerms, // Store snapshotted search terms
@@ -3112,7 +3088,7 @@ document.addEventListener('DOMContentLoaded', function() {
// isThresholdLoggingActive = true; // REMOVED
// logDurationSelect.disabled = true; // REMOVED - Keep select enabled
- console.log(`[Log Session ${sessionId}] Started. Duration: ${durationMinutes}m. Thresholds:`, snapshottedThresholds);
+ console.log(`[Log Session ${sessionId}] Started. Thresholds:`, snapshottedThresholds);
}
function stopThresholdLogging(sessionId, reason = 'manual') { // Added reason parameter
@@ -3124,20 +3100,8 @@ document.addEventListener('DOMContentLoaded', function() {
console.log(`[Log Session ${sessionId}] Stopping. Reason: ${reason}`);
- clearTimeout(session.timerId); // Clear the auto-stop timer regardless of reason
-
// --- Modifications for Persistence ---
if (session.element) {
- // 1. Disable the close button on the panel
- /* // REMOVED THIS BLOCK - We want the 'x' to always remove the panel
- const closeButton = session.element.querySelector('.log-session-header button');
- if (closeButton) {
- closeButton.disabled = true;
- closeButton.classList.add('opacity-50', 'cursor-not-allowed');
- closeButton.onclick = null; // Remove listener
- }
- */
-
// 2. Add final status message to the table body (Less critical now, but keep for timer expiry case)
const tableBody = session.element.querySelector(`#log-table-${sessionId} tbody`);
if (tableBody) {
@@ -3206,7 +3170,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Populate cells with comprehensive metric data
- console.log(`[addLogRow] About to add row for ${entry.guestName} (${entry.guestId}) to session ${sessionId}`); // DEBUG
+ // console.log(`[addLogRow] About to add row for ${entry.guestName} (${entry.guestId}) to session ${sessionId}`); // DEBUG
row.innerHTML = `
${entry.timestamp.toLocaleTimeString()} |
${guestDisplayHTML} |
@@ -3225,9 +3189,9 @@ document.addEventListener('DOMContentLoaded', function() {
// --- DEBUG: Check if row is in DOM and potentially visible ---
const addedRow = tableBody.firstChild; // Get the row we just prepended
if (addedRow === row) {
- console.log(`[addLogRow] Row for ${entry.guestName} successfully prepended to session ${sessionId}. Computed display: ${window.getComputedStyle(addedRow).display}`);
+ // console.log(`[addLogRow] Row for ${entry.guestName} successfully prepended to session ${sessionId}. Computed display: ${window.getComputedStyle(addedRow).display}`);
} else {
- console.error(`[addLogRow] Failed to prepend or find row for ${entry.guestName} in session ${sessionId}!`);
+ // console.error(`[addLogRow] Failed to prepend or find row for ${entry.guestName} in session ${sessionId}!`);
}
// --- END DEBUG ---
@@ -3251,39 +3215,12 @@ function checkThresholdViolations() {
// --- Get current UI filter state ---
const currentFilterGuestType = filterGuestType;
const currentFilterStatus = filterStatus;
- // const rawSearchTerms = searchInput ? searchInput.value.toLowerCase().split(',').map(term => term.trim()).filter(term => term) : []; // REMOVED
- // const currentTextSearchTerms = rawSearchTerms.filter(term => !thresholdPrefixes.some(prefix => term.startsWith(prefix))); // REMOVED
// --- End Get current UI filter state ---
// We assume refreshDashboardData() has been called recently by the main interval,
// so dashboardData is up-to-date.
dashboardData.forEach(guest => {
- // --- REVERT: Remove checks for current UI filters ---
- /* // REMOVED Block
- // --- Apply Dashboard Filters BEFORE checking thresholds ---
- // Must be running
- if (guest.status !== 'running') return;
-
- // Type Filter
- const typeMatch = currentFilterGuestType === 'all' || (currentFilterGuestType === 'vm' && guest.type === 'VM') || (currentFilterGuestType === 'lxc' && guest.type === 'CT');
- if (!typeMatch) return; // Skip if type doesn't match UI filter
-
- // Status Filter (Redundant if only checking running, but keep for consistency?)
- const statusMatch = currentFilterStatus === 'all' || guest.status === currentFilterStatus;
- if (!statusMatch) return; // Skip if status doesn't match UI filter
-
- // Text Search Filter
- const searchMatch = currentTextSearchTerms.length === 0 || currentTextSearchTerms.some(term =>
- (guest.name && guest.name.toLowerCase().includes(term)) ||
- (guest.node && guest.node.toLowerCase().includes(term)) ||
- (guest.vmid && guest.vmid.toString().includes(term)) ||
- (guest.id && guest.id.toString().includes(term))
- );
- if (!searchMatch) return; // Skip if text search doesn't match
- // --- End Apply Dashboard Filters ---
- */
-
// --- Keep only the check for running status ---
if (guest.status !== 'running') return; // Only log for running guests
// --- End Keep only check ---
@@ -3292,7 +3229,7 @@ function checkThresholdViolations() {
const session = activeLogSessions[sessionId];
if (!session) return; // Should not happen, but safety check
- // --- Apply Snapshotted Type/Status Filters FIRST --- <<< Added
+ // --- Apply Snapshotted Type/Status Filters FIRST ---
const sessionFilterGuestType = session.filterGuestType;
const sessionFilterStatus = session.filterStatus;
@@ -3305,7 +3242,7 @@ function checkThresholdViolations() {
if (!statusMatch) return; // Skip guest if status doesn't match session filter
// --- End Apply Snapshotted Type/Status Filters ---
- // --- Apply Snapshotted Text Search Filter --- <<< Renamed section
+ // --- Apply Snapshotted Text Search Filter ---
const snapshottedSearch = session.searchTerms || []; // Get snapshotted terms
let guestMatchedSearch = false; // Flag specifically for guest match
let nodeMatchedSearch = false; // Flag specifically for node match
@@ -3339,7 +3276,6 @@ function checkThresholdViolations() {
// --- Now check thresholds (if any were snapshotted) ---
let meetsAllThresholds = true; // Start assuming guest meets all thresholds
- // let shouldLogEntry = false; // Old OR logic flag
let violationDetails = []; // Still useful to store which thresholds were active for context
for (const type in session.thresholds) {
@@ -3365,28 +3301,20 @@ function checkThresholdViolations() {
}
// Check for violation (guest value >= threshold limit)
- // if (guestValue >= thresholdValue) { // Old OR check
if (guestValue < thresholdValue) { // Check if guest FAILS this threshold
- // valueFormatted = formatThresholdValue(type, guestValue);
- // limitFormatted = formatThresholdValue(type, thresholdValue);
- // shouldLogEntry = true; // Old OR logic flag
- // violationDetails.push({ type: getReadableThresholdName(type), value: formatThresholdValue(type, guestValue), limit: formatThresholdValue(type, thresholdValue) });
meetsAllThresholds = false; // Guest failed at least one threshold
// --- DEBUG LOG ---
- console.log(`[Log Check FAIL] Guest: ${guest.name}, Metric: ${type}, Value: ${guestValue}, Threshold: ${thresholdValue}`);
+ // console.log(`[Log Check FAIL] Guest: ${guest.name}, Metric: ${type}, Value: ${guestValue}, Threshold: ${thresholdValue}`);
// --- END DEBUG LOG ---
break; // No need to check further thresholds for this guest
} else {
// --- DEBUG LOG ---
- console.log(`[Log Check PASS] Guest: ${guest.name}, Metric: ${type}, Value: ${guestValue}, Threshold: ${thresholdValue}`);
+ // console.log(`[Log Check PASS] Guest: ${guest.name}, Metric: ${type}, Value: ${guestValue}, Threshold: ${thresholdValue}`);
// --- END DEBUG LOG ---
- // Optional: Store passed threshold details for context if needed
- // violationDetails.push({ type: getReadableThresholdName(type), value: formatThresholdValue(type, guestValue), limit: formatThresholdValue(type, thresholdValue) });
}
} // End loop through session thresholds
// If the guest met ALL thresholds checked
- // if (shouldLogEntry) { // Old OR check
if (meetsAllThresholds) {
// Gather all current metrics for the guest
const metricsSnapshot = {
@@ -3418,14 +3346,13 @@ function checkThresholdViolations() {
activeThresholdKeys: Object.keys(session.thresholds), // Pass the keys for highlighting
guestMatchedSearch: guestMatchedSearch, // <<< ADDED specific flag
nodeMatchedSearch: nodeMatchedSearch // <<< ADDED specific flag
- // violations: violationDetails // Removed for now, as only logging passes
};
// Avoid logging the exact same violation repeatedly very quickly (debouncing)
const lastEntry = session.entries.length > 0 ? session.entries[0] : null; // Check the most recent entry (inserted at top)
if (!lastEntry ||
!(lastEntry.guestId === logEntry.guestId &&
- (now.getTime() - lastEntry.timestamp.getTime()) < 5000) // Simple time-based debounce per guest
+ (now.getTime() - lastEntry.timestamp.getTime()) < 1000) // Simple time-based debounce per guest (Reduced to 1s)
)
{
addLogRow(sessionId, logEntry);
@@ -3435,24 +3362,6 @@ function checkThresholdViolations() {
}); // End loop through guests
}
- // Add listener for the Start/Stop Log button
- if (toggleLogModeButton) {
- toggleLogModeButton.addEventListener('click', () => {
- if (isThresholdLoggingActive) {
- // Find the active session ID (assuming only one for now)
- const activeSessionId = Object.keys(activeLogSessions)[0];
- if (activeSessionId) {
- // Explicitly pass 'manual' when clicking the main button
- stopThresholdLogging(activeSessionId, 'manual');
- }
- } else {
- startThresholdLogging();
- }
- });
- } else {
- console.warn('#toggle-log-mode-button not found.');
- }
-
// Add listener for the NEW Start Log button
const startLogButton = document.getElementById('start-log-button');
if (startLogButton) {
@@ -3522,16 +3431,6 @@ function checkThresholdViolations() {
// --- START Reset Functions ---
// Define standalone resetThresholds function
function resetThresholds() {
- /* // REMOVED: Don't stop active log on view reset
- // If logging is active while resetting thresholds, stop it.
- if (isThresholdLoggingActive) {
- const activeSessionId = Object.keys(activeLogSessions)[0];
- if (activeSessionId) {
- stopThresholdLogging(activeSessionId, 'manual'); // Stop manually to preserve logs
- }
- }
- */
-
for (const type in thresholdState) {
if (sliders[type]) { // Slider
thresholdState[type].value = 0;
@@ -3580,7 +3479,6 @@ function resetThresholds() {
// Extract search term like "Search: 'term'"
const termMatch = search.match(/^Search: "(.+)"$/);
if (termMatch) {
- // parts.push(`S:'${termMatch[1].substring(0, 10)}'`); // Old format
parts.push(`'${termMatch[1].substring(0, 10)}'`); // New format: just the term in quotes
}
}
@@ -3683,45 +3581,54 @@ function resetThresholds() {
}
// --- END Search Bar Threshold Integration ---
- // --- Global Timer Update Logic ---
- function formatRemainingTime(ms) {
- if (ms <= 0) return "00:00";
- const totalSeconds = Math.max(0, Math.floor(ms / 1000));
- const minutes = Math.floor(totalSeconds / 60);
- const seconds = totalSeconds % 60;
- return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
- }
+ // --- NEW Function to check filter state and toggle controls ---
+ function updateLogControlsVisibility() {
+ // console.log("[Debug] updateLogControlsVisibility called"); // Add log entry point
+ const startButton = document.getElementById('start-log-button');
- function updateAllLogTimers() {
- const now = Date.now();
- // Select all visible timer spans first
- document.querySelectorAll('.log-timer-display').forEach(timerSpan => {
- const sessionId = timerSpan.dataset.sessionId;
- const session = activeLogSessions[sessionId];
+ if (!startButton) {
+ // console.error("[Debug] Start log button not found!");
+ return; // Exit if start button not found
+ }
- if (session) {
- const endTime = session.startTime.getTime() + session.durationMs;
- const remainingMs = endTime - now;
- timerSpan.textContent = formatRemainingTime(remainingMs);
+ let isAnyFilterActive = false;
+ let activeReason = 'None'; // Store reason for debugging
- // Optional: Change style when finished
- if (remainingMs <= 0) {
- timerSpan.classList.add('text-red-500', 'dark:text-red-400');
- }
-
- } else {
- // If session is no longer active but span exists, ensure it shows 00:00
- // This might happen briefly if stopThresholdLogging hasn't updated it yet
- if (timerSpan.textContent !== "00:00") {
- timerSpan.textContent = "00:00";
- timerSpan.classList.add('text-red-500', 'dark:text-red-400');
- }
+ // 1. Check Thresholds
+ for (const type in thresholdState) {
+ if (thresholdState[type].value > 0) {
+ isAnyFilterActive = true;
+ activeReason = `Threshold: ${type}=${thresholdState[type].value}`;
+ break;
}
- });
- }
+ }
+ // console.log(`[Debug] After Thresholds Check: isAnyFilterActive=${isAnyFilterActive}, Reason=${activeReason}`);
- // Start the global timer interval
- setInterval(updateAllLogTimers, 1000); // Update every second
- // --- END Global Timer Update Logic ---
+ // 2. Check Search Input (if thresholds not already active)
+ if (!isAnyFilterActive && searchInput && searchInput.value.trim() !== '') {
+ isAnyFilterActive = true;
+ activeReason = `Search: '${searchInput.value.trim()}'`;
+ }
+ // console.log(`[Debug] After Search Check: isAnyFilterActive=${isAnyFilterActive}, Reason=${activeReason}`);
+
+ // 3. Check Type Filter (if still not active)
+ if (!isAnyFilterActive && filterGuestType !== 'all') {
+ isAnyFilterActive = true;
+ activeReason = `Type Filter: ${filterGuestType}`;
+ }
+ // console.log(`[Debug] After Type Filter Check: isAnyFilterActive=${isAnyFilterActive}, Reason=${activeReason}`);
+
+ // 4. Check Status Filter (if still not active)
+ if (!isAnyFilterActive && filterStatus !== 'all') {
+ isAnyFilterActive = true;
+ activeReason = `Status Filter: ${filterStatus}`;
+ }
+ // console.log(`[Debug] After Status Filter Check: isAnyFilterActive=${isAnyFilterActive}, Reason=${activeReason}`);
+
+ // Toggle button visibility
+ // console.log(`[Debug] Toggling button hidden state to: ${!isAnyFilterActive}`);
+ startButton.classList.toggle('hidden', !isAnyFilterActive);
+ }
+ // --- END NEW Function ---
}); // End DOMContentLoaded