90 ? 'error' : diskUsage > 70 ? 'warning' : 'primary'}
- />
- );
- case 'download':
- return (
-
- {formatNetworkRate(downloadRate)}
-
- );
- case 'upload':
- return (
-
- {formatNetworkRate(uploadRate)}
-
- );
- case 'uptime':
- return (
-
- {isRunning ? formatUptime(uptime) : '—'}
-
- );
- default:
- return null;
- }
- };
-
- // Render the table row
- return (
-
- {/* Render cells based on column order */}
- {Array.isArray(visibleColumns) && visibleColumns.length > 0 ? (
- visibleColumns.map(column => {
- // Don't skip rendering the role column if user chose to make it visible
- // Only skip if user hasn't configured it and no cluster detected
- const roleColumnShouldDisplay = column.id !== 'role' ||
- column.visible === true ||
- localStorage.getItem('CLUSTER_DETECTED') === 'true';
-
- if (!roleColumnShouldDisplay) {
- return null;
- }
-
- // Check if this cell has a matching search term
- const hasMatch = cellHasMatch(column.id);
-
- // Special handling for node column to use custom text color
- const isNodeColumn = column.id === 'node';
-
- return (
- column && (
-
- {renderCellContent(column.id)}
-
- )
- );
- })
- ) : (
- No visible columns
- )}
-
- );
-};
-
-// Helper function to get minimum width for each column
-const getMinWidthForColumn = (columnId) => {
- const minWidths = {
- // Fixed narrow columns - add a few pixels to accommodate sort icon
- type: 50, // Very small - just "VM" or "CT"
- id: 60, // Very small - just numeric IDs
- status: 50, // Minimal - just an icon
- role: 100, // Primary/Secondary chip
-
- // Auto-sized columns
- name: 120, // Names - minimum width
- node: 80, // Node names - minimum width (reduced from 100)
-
- // Flexible equal columns
- cpu: 140, // Medium - progress bar with percentage
- memory: 140, // Progress bar with byte values
- disk: 140, // Progress bar with byte values
-
- // Fixed width columns
- download: 90, // Network rates
- upload: 90, // Network rates
- uptime: 90 // Time display
- };
-
- return minWidths[columnId] || 80;
-};
-
-export default NetworkTableRow;
\ No newline at end of file
diff --git a/frontend/src/components/network/UIComponents.jsx b/frontend/src/components/network/UIComponents.jsx
deleted file mode 100644
index 364aac7ac..000000000
--- a/frontend/src/components/network/UIComponents.jsx
+++ /dev/null
@@ -1,280 +0,0 @@
-import React from 'react';
-import {
- Box,
- Typography,
- LinearProgress,
- Tooltip,
- alpha,
- useTheme
-} from '@mui/material';
-import { formatPercentage } from '../../utils/formatters';
-import { pulseAnimation, logoPulseAnimation, centerGlowAnimation } from '../../constants/networkConstants';
-
-// Animated Logo component
-export const AnimatedLogo = React.memo(({ size = 32, darkMode = false }) => {
- return (
-
- {/* Background circle with subtle pulse */}
-
-
- {/* Outer ring */}
-
-
- {/* Pulse ring (animated) */}
-
-
- {/* Center dot */}
-
-
- );
-});
-
-// Animated Logo with Text component
-export const AnimatedLogoWithText = React.memo(({ size = 32, darkMode = false }) => {
- return (
-
-
-
- Pulse
-
-
- );
-});
-
-// Progress bar with label and tooltip
-export const ProgressWithLabel = React.memo(({ value, color = "primary", disabled = false, tooltipText }) => {
- // Ensure value is a number and between 0-100
- const normalizedValue = typeof value === 'number'
- ? Math.min(Math.max(0, value), 100)
- : 0;
-
- const progressBar = (
-
-
-
- {formatPercentage(normalizedValue)}
-
-
-
- alpha(theme.palette.grey[300], 0.5),
- '& .MuiLinearProgress-bar': {
- borderRadius: 4,
- transition: 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
- }
- }}
- />
-
-
- );
-
- if (tooltipText) {
- return (
-
- {progressBar}
-
- );
- }
-
- return progressBar;
-});
-
-// Status indicator circle
-export const StatusIndicator = React.memo(({ status }) => {
- const theme = useTheme();
- const isDarkMode = theme.palette.mode === 'dark';
- let color = 'grey';
-
- switch (status.toLowerCase()) {
- case 'running':
- color = '#4caf50'; // success green
- break;
- case 'stopped':
- color = '#f44336'; // error red
- break;
- case 'paused':
- color = '#ff9800'; // warning orange
- break;
- default:
- color = '#9e9e9e'; // grey
- }
-
- return (
-
- );
-});
-
-// KeyboardShortcut component with better a11y
-export const KeyboardShortcut = ({ shortcut, sx = {} }) => (
-
- {shortcut}
-
-);
-
-// Add a new HighlightedText component for search term highlighting
-export const HighlightedText = ({ text, searchTerms = [], variant = "body2", sx = {}, ...props }) => {
- const theme = useTheme();
-
- if (!text || !searchTerms || searchTerms.length === 0) {
- return {text};
- }
-
- // Convert to string to handle numeric values
- const textStr = String(text || '');
-
- // Prepare regular expression for matching - case insensitive
- // Escape special regex characters in search terms
- const escapedTerms = searchTerms.map(term =>
- String(term).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- );
-
- // Create regex pattern from all terms
- const pattern = new RegExp(`(${escapedTerms.join('|')})`, 'gi');
-
- // Split text by matches
- const parts = textStr.split(pattern);
-
- // Skip highlighting if no matches
- if (parts.length <= 1) {
- return {text};
- }
-
- return (
-
- {parts.map((part, i) => {
- // Check if this part matches any search term (case insensitive)
- const isMatch = searchTerms.some(term =>
- part.toLowerCase() === String(term).toLowerCase()
- );
-
- return isMatch ? (
-
- {part}
-
- ) : part;
- })}
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/components/network/components/NetworkHeader.jsx b/frontend/src/components/network/components/NetworkHeader.jsx
deleted file mode 100644
index c65f7300b..000000000
--- a/frontend/src/components/network/components/NetworkHeader.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import {
- Box
-} from '@mui/material';
-
-const NetworkHeader = ({
- openFilters,
- filters,
- handleFilterButtonClick,
- filterButtonRef
-}) => {
- return (
-
- {/* Left side - Intentionally empty, removed Dashboard title */}
-
-
- {/* Right side - Actions - Column visibility button has been moved to the main app header */}
-
- {/* Additional buttons can be added here if needed */}
-
-
- );
-};
-
-export default NetworkHeader;
\ No newline at end of file
diff --git a/frontend/src/components/network/components/NetworkNotification.jsx b/frontend/src/components/network/components/NetworkNotification.jsx
deleted file mode 100644
index 2e0c0cec4..000000000
--- a/frontend/src/components/network/components/NetworkNotification.jsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import { Snackbar, Alert } from '@mui/material';
-
-const NetworkNotification = ({
- snackbarOpen,
- snackbarMessage,
- snackbarSeverity,
- handleSnackbarClose
-}) => {
- return (
-
-
- {snackbarMessage}
-
-
- );
-};
-
-export default NetworkNotification;
\ No newline at end of file
diff --git a/frontend/src/components/network/components/NetworkPopovers.jsx b/frontend/src/components/network/components/NetworkPopovers.jsx
deleted file mode 100644
index 7a0b6f939..000000000
--- a/frontend/src/components/network/components/NetworkPopovers.jsx
+++ /dev/null
@@ -1,206 +0,0 @@
-import React from 'react';
-import {
- Popover,
- Paper,
- Box,
- Typography,
- IconButton,
- Button,
- Slider
-} from '@mui/material';
-import FilterAltOffIcon from '@mui/icons-material/FilterAltOff';
-import { useSearchContext } from '../../../context/SearchContext';
-
-const NetworkPopovers = ({
- // Filter popover props
- filterAnchorEl,
- openFilters,
- handleCloseFilterPopover,
- filters,
- updateFilter,
- handleSliderDragStart,
- handleSliderDragEnd,
- resetFilters,
-
- // Formatters
- formatPercentage,
- formatNetworkRateForFilter
-}) => {
- // Get clearSearchTerms from context to ensure search terms are cleared when filters are reset
- const { clearSearchTerms } = useSearchContext();
-
- // Handle resetting all filters including search terms
- const handleResetAllFilters = () => {
- resetFilters();
- clearSearchTerms();
- handleCloseFilterPopover();
- };
-
- return (
- <>
- {/* Filter popover */}
-
- >
- );
-};
-
-export default NetworkPopovers;
\ No newline at end of file
diff --git a/frontend/src/components/network/components/NetworkTable.jsx b/frontend/src/components/network/components/NetworkTable.jsx
deleted file mode 100644
index 41c010481..000000000
--- a/frontend/src/components/network/components/NetworkTable.jsx
+++ /dev/null
@@ -1,272 +0,0 @@
-import React, { useState, useEffect, useMemo } from 'react';
-import {
- Card,
- CardContent,
- TableContainer,
- Table,
- Paper,
- Box,
- alpha,
- useTheme
-} from '@mui/material';
-import NetworkTableHeader from '../NetworkTableHeader';
-import NetworkTableBody from '../NetworkTableBody';
-import { STORAGE_KEY_COLUMN_VISIBILITY } from '../../../constants/networkConstants';
-import axios from 'axios';
-
-const NetworkTable = ({
- sortConfig,
- requestSort,
- columnVisibility,
- toggleColumnVisibility,
- resetColumnVisibility,
- columnMenuAnchorEl,
- handleColumnMenuOpen,
- handleColumnMenuClose,
- openColumnMenu,
- forceUpdateCounter,
- columnOrder,
- setColumnOrder,
- activeFilteredColumns,
- sortedAndFilteredData,
- guestData,
- metricsData,
- getNodeName,
- extractNumericId,
- resetFilters,
- showStopped,
- setShowStopped,
- guestTypeFilter,
- setGuestTypeFilter,
- availableNodes = [],
- selectedNode = 'all',
- handleNodeChange = () => {},
- handleStatusChange = () => {},
- handleTypeChange = () => {},
- filters = {},
- updateFilter = () => {},
- handleFilterButtonClick = () => {},
- filterButtonRef = null,
- openFilters = false,
- handleCloseFilterPopover = () => {},
- sharedGuestIdMap = {},
- updateRoleColumnVisibility = () => {}
-}) => {
- const theme = useTheme();
-
- // Table container ref
- const tableContainerRef = React.useRef(null);
- const tableRef = React.useRef(null);
-
- // Get the column index from the column order
- const getColumnIndex = (columnId) => {
- // First get visible columns in the right order
- const visibleColumnIds = columnOrder.filter(id => columnVisibility[id]?.visible);
- return visibleColumnIds.indexOf(columnId);
- };
-
- // Add useMemo to detect cluster mode from actual node data
- const isClusterPresent = useMemo(() => {
- // Get all nodes from the current guest data
- const nodesInGuestData = guestData?.map(guest => guest.node) || [];
- const uniqueNodes = [...new Set(nodesInGuestData)];
-
- // If we have more than one node or if any guest has a hastate
- const hasHaStates = guestData?.some(guest =>
- guest.hastate &&
- guest.hastate !== 'ignored' &&
- guest.hastate !== '-'
- );
-
- // Check if any guests are marked as shared - another strong indicator of cluster mode
- const hasSharedGuests = guestData?.some(guest => guest.shared === true);
-
- // Check for HA resource data by looking for guests with ':' in their ID (sid format)
- const hasHaResourceFormat = guestData?.some(guest =>
- guest.id && typeof guest.id === 'string' && guest.id.includes(':')
- );
-
- // Check if shared guest map has any entries
- const hasSharedGuestMapEntries = Object.keys(sharedGuestIdMap || {}).length > 0;
-
- // Log what we're detecting for debugging
- console.log('Cluster detection from guest data:', {
- uniqueNodes,
- nodeCount: uniqueNodes.length,
- hasHaStates,
- hasSharedGuests,
- hasHaResourceFormat,
- hasSharedGuestMapEntries,
- haStatesFound: guestData?.filter(g => g.hastate && g.hastate !== 'ignored').map(g => g.hastate) || []
- });
-
- // Store the result in localStorage for persistence across page loads
- const isCluster = uniqueNodes.length > 1 || hasHaStates || hasSharedGuests || hasHaResourceFormat || hasSharedGuestMapEntries;
-
- // Update localStorage - this will trigger the detection in useColumnManagement
- const previousValue = localStorage.getItem('CLUSTER_DETECTED');
- if (previousValue !== isCluster.toString()) {
- console.log('Updating cluster detection state:', isCluster);
- localStorage.setItem('CLUSTER_DETECTED', isCluster.toString());
- }
-
- return isCluster;
- }, [guestData, sharedGuestIdMap]);
-
- // Check if we're in mock data mode
- const isMockData = useMemo(() => {
- // Check explicit mock data flags first
- const explicitMockEnabled = localStorage.getItem('MOCK_DATA_ENABLED') === 'true' ||
- localStorage.getItem('use_mock_data') === 'true';
-
- // Only consider localhost as mock data if no explicit setting exists
- // This allows us to connect to real Proxmox on localhost without triggering mock mode
- const isOnLocalhost = window.location.hostname === 'localhost';
-
- return explicitMockEnabled;
- }, []);
-
- // Make a separate debug effect to constantly log status
- useEffect(() => {
- console.log('CURRENT COLUMN VISIBILITY STATUS:', {
- roleColumnVisible: columnVisibility?.role?.visible,
- clusterDetected: localStorage.getItem('CLUSTER_DETECTED') === 'true',
- isClusterPresent,
- isMockData
- });
- }, [columnVisibility, isClusterPresent, isMockData]);
-
- // Add immediate API check when component mounts
- useEffect(() => {
- // Only run once on initial mount
- const checkClusterStatusDirectly = async () => {
- try {
- console.log('Directly checking cluster status from API...');
-
- // Check if we're in mock data mode
- const isMockData = localStorage.getItem('MOCK_DATA_ENABLED') === 'true' ||
- localStorage.getItem('use_mock_data') === 'true';
-
- // For production, check cluster status from the API
- const response = await axios.get('/api/cluster-status');
- const isCluster = response?.data?.clusterEnabled || false;
- console.log('API returned cluster status:', isCluster);
-
- // Update localStorage and column visibility
- localStorage.setItem('CLUSTER_DETECTED', isCluster.toString());
-
- // Always enforce the server-reported cluster status for column visibility
- // Even in mock data mode, respect the actual cluster status from the server
- if (typeof updateRoleColumnVisibility === 'function') {
- updateRoleColumnVisibility(isCluster);
- console.log(`HA Status column visibility set to ${isCluster} based on API response`);
- }
-
- // If not a cluster, force hide the column to ensure it's not visible
- if (!isCluster && columnVisibility?.role?.visible) {
- console.log('Server confirmed no cluster, forcing HA Status column to hide');
- toggleColumnVisibility('role');
- }
- } catch (error) {
- console.error('Error checking cluster status:', error);
-
- // Use client-side detection as fallback
- if (isClusterPresent) {
- localStorage.setItem('CLUSTER_DETECTED', 'true');
- if (typeof updateRoleColumnVisibility === 'function') {
- updateRoleColumnVisibility(true);
- }
- } else {
- localStorage.setItem('CLUSTER_DETECTED', 'false');
- if (typeof updateRoleColumnVisibility === 'function') {
- updateRoleColumnVisibility(false);
- }
- }
- }
- };
-
- // Call immediately
- checkClusterStatusDirectly();
- }, [isClusterPresent, updateRoleColumnVisibility, columnVisibility, toggleColumnVisibility]);
-
- return (
-
-
-
-
-
-
-
- );
-};
-
-export default NetworkTable;
\ No newline at end of file
diff --git a/frontend/src/components/network/components/index.js b/frontend/src/components/network/components/index.js
deleted file mode 100644
index cca9b8e76..000000000
--- a/frontend/src/components/network/components/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export { default as NetworkHeader } from './NetworkHeader';
-export { default as NetworkPopovers } from './NetworkPopovers';
-export { default as NetworkNotification } from './NetworkNotification';
-export { default as NetworkTable } from './NetworkTable';
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/index.js b/frontend/src/components/network/hooks/index.js
deleted file mode 100644
index b58225cdb..000000000
--- a/frontend/src/components/network/hooks/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export { default as useNetworkFilters } from './useNetworkFilters';
-export { default as useColumnManagement } from './useColumnManagement';
-export { default as usePopoverManagement } from './usePopoverManagement';
-export { default as useNotifications } from './useNotifications';
-export { default as useSortManagement } from './useSortManagement';
-export { default as useKeyboardShortcuts } from './useKeyboardShortcuts';
-export { default as useActiveFilteredColumns } from './useActiveFilteredColumns';
-export { default as useDataProcessing } from './useDataProcessing';
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useActiveFilteredColumns.js b/frontend/src/components/network/hooks/useActiveFilteredColumns.js
deleted file mode 100644
index 13f8afc65..000000000
--- a/frontend/src/components/network/hooks/useActiveFilteredColumns.js
+++ /dev/null
@@ -1,352 +0,0 @@
-import { useMemo } from 'react';
-import { useSearchContext } from '../../../context/SearchContext';
-
-const useActiveFilteredColumns = ({
- filters,
- guestTypeFilter,
- showStopped,
- nodeData
-}) => {
- // Get search state from context
- const { searchTerm, activeSearchTerms } = useSearchContext();
-
- // Determine which columns have active filters
- const activeFilteredColumns = useMemo(() => {
- const result = {};
-
- // Resource filters
- if (filters.cpu > 0) result.cpu = true;
- if (filters.memory > 0) result.memory = true;
- if (filters.disk > 0) result.disk = true;
- if (filters.download > 0) result.netIn = true;
- if (filters.upload > 0) result.netOut = true;
-
- // Search terms - determine which column to highlight based on the search term
- // This is now only used for UI indicators in header (like filter icons)
- // not for highlighting anymore
- if (activeSearchTerms.length > 0 || searchTerm) {
- const allTerms = [...activeSearchTerms];
- if (searchTerm) allTerms.push(searchTerm);
-
- // Process node data for efficient matching
- const nodeInfo = processNodeData(nodeData);
-
- // Process each search term to determine which column(s) to highlight
- allTerms.forEach(term => {
- const termLower = term.trim().toLowerCase();
-
- // Skip empty terms
- if (!termLower) return;
-
- // Try to match the term to specific column types
- // This now returns an array of column types for partial matches
- const columnTypes = identifyColumnTypes(termLower, nodeInfo);
-
- // Apply the appropriate highlighting for all matching column types
- if (Array.isArray(columnTypes)) {
- columnTypes.forEach(columnType => {
- applyColumnHighlighting(result, columnType, termLower);
- });
- } else if (columnTypes) {
- // For backward compatibility - still handle single column type
- applyColumnHighlighting(result, columnTypes, termLower);
- }
- });
- }
-
- // Guest type filter affects the type column
- if (guestTypeFilter !== 'all') {
- result.type = true;
- }
-
- // Highlight status column when we're filtering by status (not showing all)
- if (showStopped !== null) {
- result.status = true;
- }
-
- return result;
- }, [filters, activeSearchTerms, searchTerm, guestTypeFilter, showStopped, nodeData]);
-
- return activeFilteredColumns;
-};
-
-// Helper function to process node data for efficient matching
-function processNodeData(nodeData) {
- const nodeNames = [];
- const nodeIds = [];
- const nodePatterns = ['node', 'pve', 'prox', 'cluster', 'host', 'server'];
-
- // Add role-related terms to help with highlighting
- const rolePatterns = ['role', 'primary', 'secondary', 'pri', 'sec', 'shared'];
-
- if (nodeData && Array.isArray(nodeData)) {
- nodeData.forEach(node => {
- if (node.name) nodeNames.push(node.name.toLowerCase());
- if (node.id) nodeIds.push(node.id.toLowerCase());
-
- // Create common node name patterns
- const name = (node.name || '').toLowerCase();
- if (name.includes('-')) {
- // Add base name (e.g., 'pve' from 'pve-01')
- const baseName = name.split('-')[0];
- if (baseName && !nodePatterns.includes(baseName)) {
- nodePatterns.push(baseName);
- }
-
- // Add prefix with first digit (e.g., 'pve-0' from 'pve-01')
- const parts = name.split('-');
- if (parts.length > 1 && parts[1].length > 0) {
- const prefix = parts[0] + '-' + parts[1][0];
- if (!nodePatterns.includes(prefix)) {
- nodePatterns.push(prefix);
- }
- }
- }
- });
- }
-
- return { nodeNames, nodeIds, nodePatterns, rolePatterns };
-}
-
-// Helper function to identify which column a search term applies to
-const identifyColumnTypes = (termLower, nodeInfo = {}) => {
- // Column-specific prefixes
- if (termLower.startsWith('status:')) return 'status';
- if (termLower.startsWith('type:')) return 'type';
- if (termLower.startsWith('node:')) return 'node';
- if (termLower.startsWith('role:')) return 'role';
- if (termLower.startsWith('cpu:')) return 'cpu';
- if (termLower.startsWith('memory:') || termLower.startsWith('mem:')) return 'memory';
- if (termLower.startsWith('disk:')) return 'disk';
- if (termLower.startsWith('download:') || termLower.startsWith('dl:')) return 'download';
- if (termLower.startsWith('upload:') || termLower.startsWith('ul:')) return 'upload';
-
- // Resource keywords (exact matches)
- if (['cpu', 'memory', 'mem', 'disk', 'network', 'net'].includes(termLower)) {
- if (termLower === 'network' || termLower === 'net') {
- return 'network';
- }
- if (termLower === 'mem') {
- return 'memory';
- }
- return termLower;
- }
-
- // Partial resource expressions (e.g., "cpu>")
- if (/^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)$/i.test(termLower)) {
- const resource = termLower.match(/^(cpu|mem(ory)?|disk|network|net)/i)[0].toLowerCase();
- if (resource === 'network' || resource === 'net') {
- return 'network';
- }
- if (resource === 'mem') {
- return 'memory';
- }
- return resource;
- }
-
- // Complete resource expressions (e.g., "cpu>50")
- const resourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i;
- const resourceMatch = termLower.match(resourceExpressionRegex);
- if (resourceMatch) {
- let resource = resourceMatch[1].toLowerCase();
- if (resource === 'network' || resource === 'net') {
- return 'network';
- }
- if (resource === 'mem') {
- return 'memory';
- }
- return resource;
- }
-
- // Direct threshold expressions without column prefix (e.g., ">50")
- const directThresholdRegex = /^(>|<|>=|<=)(\d+)$/;
- const directThresholdMatch = termLower.match(directThresholdRegex);
- if (directThresholdMatch) {
- // This is likely a CPU filter as it's the most common direct threshold
- return 'cpu';
- }
-
- // Column-specific searches with colon (e.g., "name:ubuntu")
- if (termLower.includes(':')) {
- const [prefix] = termLower.split(':', 2);
- const validPrefixes = ['name', 'id', 'node', 'status', 'type', 'cpu', 'memory', 'mem', 'disk', 'network', 'net'];
-
- if (validPrefixes.includes(prefix.trim())) {
- const prefixTrim = prefix.trim();
- if (prefixTrim === 'network' || prefixTrim === 'net') {
- return 'network';
- }
- if (prefixTrim === 'mem') {
- return 'memory';
- }
- return prefixTrim;
- }
- }
-
- // For short partial terms (1-2 characters), we'll highlight multiple potential matches
- if (termLower.length <= 2) {
- const matchingColumns = [];
-
- // Check for partial matches in all possible terms
-
- // Resource terms
- const resourceTerms = ['cpu', 'memory', 'disk', 'network', 'net'];
- for (const resource of resourceTerms) {
- if (resource.startsWith(termLower)) {
- matchingColumns.push(resource === 'network' || resource === 'net' ? 'network' : resource);
- }
- }
-
- // Type terms
- const typeTerms = ['ct', 'container', 'vm', 'virtual', 'machine', 'qemu', 'lxc'];
- if (typeTerms.some(type => type.startsWith(termLower))) {
- matchingColumns.push('type');
- }
-
- // Status terms
- const statusTerms = ['running', 'stopped', 'online', 'offline', 'active', 'inactive'];
- if (statusTerms.some(status => status.startsWith(termLower))) {
- matchingColumns.push('status');
- }
-
- // Node terms - check if term matches beginning of any node name/id
- const { nodeNames, nodeIds } = nodeInfo;
- if (nodeNames.some(name => name.startsWith(termLower)) ||
- nodeIds.some(id => id.startsWith(termLower))) {
- matchingColumns.push('node');
- }
-
- // For single letters, also include ID and name as potential matches
- if (termLower.length === 1) {
- // Always include name for single letter (could be start of any name)
- if (!matchingColumns.includes('name')) matchingColumns.push('name');
-
- // For single digit, include ID
- if (/^\d$/.test(termLower) && !matchingColumns.includes('id')) {
- matchingColumns.push('id');
- }
- }
-
- // If we found any matches, return them
- if (matchingColumns.length > 0) {
- return matchingColumns;
- }
-
- // For 1-2 character alpha terms with no specific matches,
- // default to highlighting name column as it's the most likely target
- if (/[a-z]/i.test(termLower)) {
- return ['name'];
- }
- }
-
- // Past this point, handle longer terms (3+ chars) with more specific highlighting
-
- // Type-specific terms (exact matches)
- if (['ct', 'container', 'vm', 'virtual machine', 'qemu', 'lxc'].includes(termLower)) {
- return 'type';
- }
-
- // Status-specific terms (exact matches)
- const statusTerms = ['running', 'stopped', 'online', 'offline', 'active', 'inactive'];
- if (statusTerms.includes(termLower)) {
- return 'status';
- }
-
- // Partial status term matches (e.g., "runni" should match "running")
- // This is critical for providing good feedback while typing
- if (statusTerms.some(status => status.startsWith(termLower) || termLower.startsWith(status))) {
- return 'status';
- }
-
- // Partial type term matches (e.g., "conta" should match "container")
- const typeTerms = ['ct', 'container', 'vm', 'virtual', 'machine', 'qemu', 'lxc'];
- if (typeTerms.some(type => type.startsWith(termLower) || termLower.startsWith(type))) {
- return 'type';
- }
-
- // ID-specific terms (pure numbers)
- if (/^\d+$/.test(termLower)) {
- return 'id';
- }
-
- // Node matching
- const { nodeNames, nodeIds, nodePatterns, rolePatterns } = nodeInfo;
-
- // Exact node name/id match
- if (nodeNames.includes(termLower) || nodeIds.includes(termLower)) {
- return 'node';
- }
-
- // Node name contains term or term contains node name
- if (nodeNames.some(name => name.includes(termLower) || termLower.includes(name))) {
- return 'node';
- }
-
- // Node id contains term or term contains node id
- if (nodeIds.some(id => id.includes(termLower) || termLower.includes(id))) {
- return 'node';
- }
-
- // Term matches node patterns
- if (nodePatterns.some(pattern => termLower.includes(pattern))) {
- return 'node';
- }
-
- // Term matches role patterns
- if (rolePatterns && rolePatterns.some(pattern => termLower.includes(pattern))) {
- return 'role';
- }
-
- // Term is likely a node reference
- if (termLower.includes('-') || /^[a-z]{1,3}\d{1,2}$/i.test(termLower)) {
- return 'node';
- }
-
- // Default - this is likely a name search
- // Only highlight name if it's likely to be a name search and
- // doesn't match any partial patterns above
- if (/[a-z]/i.test(termLower) && termLower.length > 1) {
- return 'name';
- }
-
- // If we can't determine a specific column, don't highlight anything
- return null;
-}
-
-// Helper function to apply highlighting based on column type
-function applyColumnHighlighting(result, columnType, term) {
- if (!columnType) return;
-
- switch (columnType) {
- case 'cpu':
- result.cpu = true;
- break;
- case 'memory':
- result.memory = true;
- break;
- case 'disk':
- result.disk = true;
- break;
- case 'network':
- result.netIn = true;
- result.netOut = true;
- break;
- case 'node':
- result.node = true;
- break;
- case 'id':
- result.id = true;
- break;
- case 'status':
- result.status = true;
- break;
- case 'type':
- result.type = true;
- break;
- case 'name':
- result.name = true;
- break;
- }
-}
-
-export default useActiveFilteredColumns;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useColumnManagement.js b/frontend/src/components/network/hooks/useColumnManagement.js
deleted file mode 100644
index cc1350f62..000000000
--- a/frontend/src/components/network/hooks/useColumnManagement.js
+++ /dev/null
@@ -1,253 +0,0 @@
-import { useState, useCallback, useEffect } from 'react';
-import {
- STORAGE_KEY_COLUMN_VISIBILITY,
- STORAGE_KEY_COLUMN_ORDER,
- DEFAULT_COLUMN_CONFIG
-} from '../../../constants/networkConstants';
-
-const useColumnManagement = (showNotification) => {
- // Initialize column visibility state from localStorage or defaults
- const [columnVisibility, setColumnVisibility] = useState(() => {
- try {
- // Always enable cluster detection for column visibility
- localStorage.setItem('CLUSTER_DETECTED', 'true');
- console.log('Forcing HA Status column to be visible by default');
-
- // Get column visibility from localStorage
- const storedColumnVisibility = localStorage.getItem(STORAGE_KEY_COLUMN_VISIBILITY);
-
- let config;
-
- // If localStorage has data, use it
- if (storedColumnVisibility) {
- config = JSON.parse(storedColumnVisibility);
- } else {
- // Use the defaults as defined in DEFAULT_COLUMN_CONFIG
- config = JSON.parse(JSON.stringify(DEFAULT_COLUMN_CONFIG));
-
- // Ensure the 'role' column (HA Status) is always visible by default
- if (config.role) {
- config.role.visible = true;
- }
- }
-
- return config;
- } catch (error) {
- console.error('Error initializing column visibility:', error);
- return DEFAULT_COLUMN_CONFIG;
- }
- });
-
- // Initialize column order from localStorage or defaults
- const [columnOrder, setColumnOrder] = useState(() => {
- try {
- const storedOrder = localStorage.getItem(STORAGE_KEY_COLUMN_ORDER);
- if (storedOrder) {
- return JSON.parse(storedOrder);
- } else {
- return Object.keys(DEFAULT_COLUMN_CONFIG);
- }
- } catch (error) {
- console.error('Error initializing column order:', error);
- return Object.keys(DEFAULT_COLUMN_CONFIG);
- }
- });
-
- // Column menu state for the dropdown
- const [columnMenuAnchorEl, setColumnMenuAnchorEl] = useState(null);
- const openColumnMenu = Boolean(columnMenuAnchorEl);
-
- // Force update counter for triggering re-renders
- const [forceUpdateCounter, setForceUpdateCounter] = useState(0);
-
- // Handle column menu open
- const handleColumnMenuOpen = useCallback((event) => {
- setColumnMenuAnchorEl(event.currentTarget);
- }, []);
-
- // Handle column menu close
- const handleColumnMenuClose = useCallback(() => {
- setColumnMenuAnchorEl(null);
- }, []);
-
- // Force update function
- const forceUpdate = useCallback(() => {
- setForceUpdateCounter(c => c + 1);
- }, []);
-
- // Toggle column visibility
- const toggleColumnVisibility = useCallback((columnId) => {
- setColumnVisibility(prev => {
- const newState = JSON.parse(JSON.stringify(prev));
-
- // Verify column exists
- if (!newState[columnId]) {
- console.warn(`Attempted to toggle non-existent column: ${columnId}`);
- return prev;
- }
-
- // Don't allow hiding the last visible column
- if (newState[columnId].visible && Object.values(newState).filter(col => col.visible).length <= 1) {
- if (showNotification) {
- showNotification('At least one column must remain visible');
- }
- return prev;
- }
-
- // Toggle visibility
- newState[columnId].visible = !newState[columnId].visible;
-
- // If this is the role column, log it specially
- if (columnId === 'role') {
- console.log(`User manually set HA Status column to ${newState[columnId].visible ? 'visible' : 'hidden'}`);
- }
-
- // Save to localStorage
- localStorage.setItem(STORAGE_KEY_COLUMN_VISIBILITY, JSON.stringify(newState));
-
- return newState;
- });
- }, [showNotification]);
-
- // Reset column visibility to defaults
- const resetColumnVisibility = useCallback(() => {
- try {
- // Make a copy of the default config
- const defaultConfig = JSON.parse(JSON.stringify(DEFAULT_COLUMN_CONFIG));
-
- // Ensure HA Status (role) column is visible
- if (defaultConfig.role) {
- defaultConfig.role.visible = true;
- }
-
- // Reset column order to the default order as well
- const defaultOrder = Object.keys(DEFAULT_COLUMN_CONFIG);
-
- // Update state and save to localStorage
- setColumnVisibility(defaultConfig);
- setColumnOrder(defaultOrder);
-
- // Always enable cluster detection for HA Status column
- localStorage.setItem('CLUSTER_DETECTED', 'true');
-
- // Save both to localStorage
- localStorage.setItem(STORAGE_KEY_COLUMN_VISIBILITY, JSON.stringify(defaultConfig));
- localStorage.setItem(STORAGE_KEY_COLUMN_ORDER, JSON.stringify(defaultOrder));
-
- // Force UI update
- setForceUpdateCounter(c => c + 1);
-
- // Close the menu if it's open
- setColumnMenuAnchorEl(null);
-
- // Show a notification
- if (showNotification) {
- showNotification('Column visibility reset to defaults');
- }
-
- console.log('Column visibility and order reset to defaults');
- } catch (error) {
- console.error('Error resetting column visibility:', error);
-
- if (showNotification) {
- showNotification('Error resetting column visibility');
- }
- }
- }, [showNotification, setColumnMenuAnchorEl]);
-
- // Update role column visibility
- const updateRoleColumnVisibility = useCallback((shouldShow) => {
- // Log what we're doing
- console.log('updateRoleColumnVisibility called with:', {
- shouldShow
- });
-
- // Save the cluster detection state to localStorage for persistence
- localStorage.setItem('CLUSTER_DETECTED', shouldShow ? 'true' : 'false');
-
- // Only update column visibility for new users or on explicit calls
- // This allows the system to set default visibility but lets users override
- const hasExistingVisibilitySettings = localStorage.getItem(STORAGE_KEY_COLUMN_VISIBILITY);
-
- // Only force column visibility if there are no existing user settings
- if (!hasExistingVisibilitySettings) {
- setColumnVisibility(prev => {
- if (!prev.role) return prev;
-
- const newState = {
- ...prev,
- role: { ...prev.role, visible: shouldShow }
- };
-
- // Save to localStorage
- localStorage.setItem(STORAGE_KEY_COLUMN_VISIBILITY, JSON.stringify(newState));
- console.log(`Setting initial HA Status column visibility to ${shouldShow}`);
-
- return newState;
- });
-
- // Force UI update
- setForceUpdateCounter(c => c + 1);
- }
- }, []);
-
- // Save column visibility preferences whenever they change
- useEffect(() => {
- try {
- // Check if columnVisibility is valid
- if (!columnVisibility || Object.keys(columnVisibility).length === 0) {
- console.error('Invalid column visibility state, resetting to defaults');
- setColumnVisibility(JSON.parse(JSON.stringify(DEFAULT_COLUMN_CONFIG)));
- return;
- }
-
- // Ensure at least one column is visible
- const hasVisibleColumn = Object.values(columnVisibility).some(col => col.visible);
- if (!hasVisibleColumn) {
- console.error('No visible columns in state, not saving to localStorage');
- return;
- }
-
- // Save to localStorage
- localStorage.setItem(STORAGE_KEY_COLUMN_VISIBILITY, JSON.stringify(columnVisibility));
- } catch (error) {
- console.error('Error saving column visibility:', error);
- }
- }, [columnVisibility]);
-
- // Save column order preferences whenever they change
- useEffect(() => {
- try {
- // Check if columnOrder is valid
- if (!columnOrder || !Array.isArray(columnOrder) || columnOrder.length === 0) {
- console.error('Invalid column order state, resetting to defaults');
- setColumnOrder(Object.keys(DEFAULT_COLUMN_CONFIG));
- return;
- }
-
- // Save to localStorage
- localStorage.setItem(STORAGE_KEY_COLUMN_ORDER, JSON.stringify(columnOrder));
- } catch (error) {
- console.error('Error saving column order:', error);
- }
- }, [columnOrder]);
-
- return {
- columnVisibility,
- setColumnVisibility,
- columnOrder,
- setColumnOrder,
- columnMenuAnchorEl,
- setColumnMenuAnchorEl,
- openColumnMenu,
- handleColumnMenuOpen,
- handleColumnMenuClose,
- forceUpdateCounter,
- forceUpdate,
- toggleColumnVisibility,
- resetColumnVisibility,
- updateRoleColumnVisibility
- };
-};
-
-export default useColumnManagement;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useDataProcessing.js b/frontend/src/components/network/hooks/useDataProcessing.js
deleted file mode 100644
index f1a2d72a0..000000000
--- a/frontend/src/components/network/hooks/useDataProcessing.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import { useMemo, useCallback } from 'react';
-import { getSortedAndFilteredData, getNodeFilteredGuests as nodeFilteredGuestsUtil, getNodeName as getNodeNameUtil, extractNumericId as extractNumericIdUtil } from '../../../utils/networkUtils';
-import { useSearchContext } from '../../../context/SearchContext';
-
-const useDataProcessing = ({
- guestData,
- nodeData,
- sortConfig,
- filters,
- showStopped,
- selectedNode,
- guestTypeFilter,
- metricsData
-}) => {
- // Get search state from context
- const { searchTerm, activeSearchTerms } = useSearchContext();
-
- // Helper function to extract numeric ID from strings like "node-1-ct-105"
- const extractNumericId = useCallback((fullId) => {
- return extractNumericIdUtil(fullId);
- }, []);
-
- // Helper function to get the node name from the node ID
- const getNodeName = useCallback((nodeId) => {
- return getNodeNameUtil(nodeId, nodeData);
- }, [nodeData]);
-
- // Filter guests based on selected node
- const getNodeFilteredGuests = useCallback((guests) => {
- const result = nodeFilteredGuestsUtil(guests, selectedNode);
-
- console.log(`Node filtering for "${selectedNode}":`);
- console.log(`- Before: ${guests?.length || 0} guests`);
- console.log(`- After: ${result?.length || 0} guests`);
-
- if (result?.length > 0) {
- // Log node assignments in the filtered result
- const nodeAssignments = {};
- result.forEach(guest => {
- const nodeId = guest.node;
- if (!nodeAssignments[nodeId]) {
- nodeAssignments[nodeId] = [];
- }
- nodeAssignments[nodeId].push(`${guest.id} (${guest.name})`);
- });
-
- console.log('FILTERED GUESTS BY NODE:');
- Object.keys(nodeAssignments).sort().forEach(nodeId => {
- console.log(`Node "${nodeId}": ${nodeAssignments[nodeId].length} guests`);
- console.log(` Guests: ${nodeAssignments[nodeId].join(', ')}`);
- });
- }
-
- return result;
- }, [selectedNode]);
-
- // Get sorted and filtered data
- const processedData = useMemo(() => {
- // Debug logging
- console.log('useDataProcessing - Processing data:');
- console.log('- guestData:', guestData?.length || 0, 'guests');
- console.log('- selectedNode:', selectedNode);
- console.log('- sortConfig:', sortConfig);
-
- // Check for duplicate guest IDs
- if (guestData && guestData.length > 0) {
- const guestIds = new Set();
- const duplicates = [];
-
- guestData.forEach(guest => {
- if (guestIds.has(guest.id)) {
- duplicates.push(guest.id);
- } else {
- guestIds.add(guest.id);
- }
- });
-
- if (duplicates.length > 0) {
- console.warn('⚠️ Found duplicate guest IDs:', duplicates);
- }
-
- // Check for guests with unexpected node values
- const nodeAssignments = {};
- guestData.forEach(guest => {
- const nodeId = guest.node;
- if (!nodeAssignments[nodeId]) {
- nodeAssignments[nodeId] = [];
- }
- nodeAssignments[nodeId].push(`${guest.id}`);
- });
-
- console.log('🔍 Current guest node assignments:');
- Object.keys(nodeAssignments).sort().forEach(nodeId => {
- // Check if this is one of our problem nodes
- const isWatchNode = ['pve-prod-01', 'pve-prod-02'].includes(nodeId);
- const marker = isWatchNode ? '⚠️' : '✅';
- console.log(`${marker} Node "${nodeId}": ${nodeAssignments[nodeId].length} guests`);
-
- // For problem nodes, list all the guests
- if (isWatchNode) {
- console.log(` Guests: ${nodeAssignments[nodeId].join(', ')}`);
- }
- });
- }
-
- // First filter by node
- const nodeFilteredData = selectedNode === 'all'
- ? guestData
- : getNodeFilteredGuests(guestData);
-
- console.log('- nodeFilteredData:', nodeFilteredData?.length || 0, 'guests after node filtering');
-
- // Then apply all other filters and sorting
- const result = getSortedAndFilteredData(
- nodeFilteredData,
- sortConfig,
- filters,
- showStopped,
- activeSearchTerms,
- searchTerm,
- metricsData,
- guestTypeFilter,
- nodeData
- );
-
- // Final detailed logging to debug the issue
- if (result && result.length > 0) {
- console.log(`🔍 FINAL: ${result.length} guests will be displayed`);
- const finalNodeAssignments = {};
- result.forEach(guest => {
- const nodeId = guest.node || 'unknown';
- if (!finalNodeAssignments[nodeId]) {
- finalNodeAssignments[nodeId] = [];
- }
- finalNodeAssignments[nodeId].push(guest.id);
- });
-
- console.log('🔍 FINAL node distribution:');
- Object.keys(finalNodeAssignments).sort().forEach(nodeId => {
- console.log(` - ${nodeId}: ${finalNodeAssignments[nodeId].length} guests`);
- console.log(` Guest IDs: ${finalNodeAssignments[nodeId].join(', ')}`);
- });
- }
-
- console.log('- Final filtered data:', result?.length || 0, 'guests');
- return result;
- }, [
- guestData,
- sortConfig,
- filters,
- showStopped,
- activeSearchTerms,
- searchTerm,
- selectedNode,
- getNodeFilteredGuests,
- guestTypeFilter,
- nodeData,
- metricsData
- ]);
-
- // Format percentage for display
- const formatPercentage = useCallback((value) => {
- return `${value}%`;
- }, []);
-
- // Format network rate for filter display
- const formatNetworkRateForFilter = useCallback((value) => {
- if (value === 0) return '0 KB/s';
- if (value <= 10) return `${value * 10} KB/s`;
- if (value <= 50) return `${(value - 10) * 20 + 100} KB/s`;
- if (value <= 80) return `${(value - 50) * 50 + 900} KB/s`;
- return `${(value - 80) * 500 + 2400} KB/s`;
- }, []);
-
- return {
- extractNumericId,
- getNodeName,
- getNodeFilteredGuests,
- processedData,
- formatPercentage,
- formatNetworkRateForFilter
- };
-};
-
-export default useDataProcessing;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useKeyboardShortcuts.js b/frontend/src/components/network/hooks/useKeyboardShortcuts.js
deleted file mode 100644
index 8f04ba74f..000000000
--- a/frontend/src/components/network/hooks/useKeyboardShortcuts.js
+++ /dev/null
@@ -1,134 +0,0 @@
-import { useState, useEffect, useCallback, useRef } from 'react';
-import { useSearchContext } from '../../../context/SearchContext';
-
-const useKeyboardShortcuts = ({
- openFilters,
- openColumnMenu,
- resetFilters,
- closeAllPopovers,
- showNotification
-}) => {
- const [escRecentlyPressed, setEscRecentlyPressed] = useState(false);
-
- // Get search functions from context directly
- const { setSearchTerm, setIsSearching, clearSearchTerms, isSearching } = useSearchContext();
-
- // Track last key pressed timestamp to prevent double handling of events
- const lastKeyPressTime = useRef(0);
-
- // Set up keyboard shortcuts
- useEffect(() => {
- // Global keyboard handler
- const handleGlobalKeyDown = (e) => {
- // Critical protection against double event handling
- // This prevents the same key event from being processed twice
- const now = Date.now();
- if (now - lastKeyPressTime.current < 50) {
- return; // Ignore events that are too close together
- }
- lastKeyPressTime.current = now;
-
- // STOP HANDLING KEYBOARD SHORTCUTS IF USER IS TYPING IN A FORM ELEMENT
- // This is the most important check to prevent shortcuts from interfering with typing
- const isEditableElement =
- e.target.tagName === 'INPUT' ||
- e.target.tagName === 'TEXTAREA' ||
- e.target.isContentEditable ||
- e.target.getAttribute('role') === 'textbox';
-
- // If user is typing in ANY input, don't hijack their keystrokes
- if (isEditableElement) {
- return;
- }
-
- // Escape key to close filters or clear search
- if (e.key === 'Escape') {
- // Close any open popovers
- closeAllPopovers();
-
- // Reset all filters
- resetFilters();
-
- // Clear all search terms
- clearSearchTerms();
-
- // Set flag to prevent other shortcuts from triggering
- setEscRecentlyPressed(true);
- setTimeout(() => setEscRecentlyPressed(false), 300);
-
- // Show notification
- showNotification('All filters have been cleared', 'info');
- return;
- }
-
- // Ctrl+F or Cmd+F to focus search without setting a term
- if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
- e.preventDefault();
- setIsSearching(true);
- return;
- }
-
- // / to focus search without setting a term
- if (e.key === '/' && !escRecentlyPressed) {
- e.preventDefault();
- setIsSearching(true);
- return;
- }
-
- // Enter to focus search without setting a term
- if (e.key === 'Enter' && !escRecentlyPressed) {
- e.preventDefault();
- setIsSearching(true);
- return;
- }
-
- // Respect the search state
- if (isSearching) {
- // If the search field is focused, don't handle keyboard shortcuts
- return;
- }
-
- // Capture single printable characters to focus search AND start typing
- const isPrintableChar =
- e.key.length === 1 &&
- !e.ctrlKey &&
- !e.metaKey &&
- !e.altKey &&
- !e.key.match(/^F\d+$/); // Exclude function keys
-
- if (isPrintableChar) {
- e.preventDefault();
-
- // Set the first character
- setSearchTerm(e.key);
-
- // Focus the search field
- setIsSearching(true);
- }
- };
-
- // Use the capture phase to get events before other handlers
- window.addEventListener('keydown', handleGlobalKeyDown);
-
- return () => {
- window.removeEventListener('keydown', handleGlobalKeyDown);
- };
- }, [
- openFilters,
- openColumnMenu,
- escRecentlyPressed,
- resetFilters,
- closeAllPopovers,
- setSearchTerm,
- setIsSearching,
- clearSearchTerms,
- showNotification,
- isSearching
- ]);
-
- return {
- escRecentlyPressed
- };
-};
-
-export default useKeyboardShortcuts;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useNetworkFilters.js b/frontend/src/components/network/hooks/useNetworkFilters.js
deleted file mode 100644
index 4901b5a9b..000000000
--- a/frontend/src/components/network/hooks/useNetworkFilters.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import { useState, useCallback, useEffect } from 'react';
-import {
- STORAGE_KEY_FILTERS,
- STORAGE_KEY_SHOW_STOPPED,
- STORAGE_KEY_SHOW_FILTERS,
- STORAGE_KEY_GUEST_TYPE_FILTER
-} from '../../../constants/networkConstants';
-
-const useNetworkFilters = () => {
- // Filter states
- const [filters, setFilters] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_FILTERS);
- return saved ? JSON.parse(saved) : {
- cpu: 0,
- memory: 0,
- disk: 0,
- download: 0,
- upload: 0
- };
- } catch (e) {
- console.error('Error loading filter preferences:', e);
- return {
- cpu: 0,
- memory: 0,
- disk: 0,
- download: 0,
- upload: 0
- };
- }
- });
-
- // UI state
- const [showStopped, setShowStopped] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_SHOW_STOPPED);
- if (saved === null) return null; // If no saved preference, show all systems
- const parsedValue = JSON.parse(saved);
- // Convert the old boolean values to the new tri-state system
- if (parsedValue === true) return true; // Show stopped systems
- if (parsedValue === false) return false; // Show running systems
- return parsedValue; // Return the value as is (should be null, true, or false)
- } catch (e) {
- console.error('Error loading show stopped preference:', e);
- return null; // Default to showing all systems
- }
- });
-
- const [showFilters, setShowFilters] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_SHOW_FILTERS);
- return saved ? JSON.parse(saved) === true : false;
- } catch (e) {
- console.error('Error loading show filters preference:', e);
- return false;
- }
- });
-
- // Add guest type filter state - load from localStorage or use default
- const [guestTypeFilter, setGuestTypeFilter] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_GUEST_TYPE_FILTER);
- return saved ? JSON.parse(saved) : 'all'; // Default: 'all' (show both VMs and LXCs)
- } catch (e) {
- console.error('Error loading guest type filter preference:', e);
- return 'all'; // Default to showing all guest types
- }
- });
-
- // State for tracking which slider is being dragged
- const [sliderDragging, setSliderDragging] = useState(null);
-
- // Save guest type filter preference whenever it changes
- useEffect(() => {
- try {
- localStorage.setItem(STORAGE_KEY_GUEST_TYPE_FILTER, JSON.stringify(guestTypeFilter));
- } catch (e) {
- console.error('Error saving guest type filter preference:', e);
- }
- }, [guestTypeFilter]);
-
- // Save show stopped preference whenever it changes
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_SHOW_STOPPED, JSON.stringify(showStopped));
- }, [showStopped]);
-
- // Save show filters preference whenever it changes
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_SHOW_FILTERS, JSON.stringify(showFilters));
- }, [showFilters]);
-
- // Save filter preferences whenever they change
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_FILTERS, JSON.stringify(filters));
- }, [filters]);
-
- // Update filter value
- const updateFilter = useCallback((filterName, newValue) => {
- setFilters(prev => ({
- ...prev,
- [filterName]: newValue
- }));
- }, []);
-
- // Handle slider drag start
- const handleSliderDragStart = useCallback((filterName) => {
- setSliderDragging(filterName);
- }, []);
-
- // Handle slider drag end
- const handleSliderDragEnd = useCallback(() => {
- setSliderDragging(null);
- }, []);
-
- // Clear a specific filter
- const clearFilter = useCallback((filterName) => {
- setFilters(prev => ({
- ...prev,
- [filterName]: 0
- }));
- }, []);
-
- // Function to reset all filters
- const resetFilters = useCallback(() => {
- setFilters({
- cpu: 0,
- memory: 0,
- disk: 0,
- download: 0,
- upload: 0
- });
- setShowStopped(null);
- setGuestTypeFilter('all');
- }, []);
-
- // Count active filters - now only count the slider filters
- const activeFilterCount = Object.values(filters).filter(val => val > 0).length;
-
- return {
- filters,
- setFilters,
- showStopped,
- setShowStopped,
- showFilters,
- setShowFilters,
- guestTypeFilter,
- setGuestTypeFilter,
- sliderDragging,
- updateFilter,
- handleSliderDragStart,
- handleSliderDragEnd,
- clearFilter,
- resetFilters,
- activeFilterCount
- };
-};
-
-export default useNetworkFilters;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useNotifications.js b/frontend/src/components/network/hooks/useNotifications.js
deleted file mode 100644
index b571165fc..000000000
--- a/frontend/src/components/network/hooks/useNotifications.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useState, useCallback } from 'react';
-
-const useNotifications = () => {
- // Snackbar state for notifications
- const [snackbarOpen, setSnackbarOpen] = useState(false);
- const [snackbarMessage, setSnackbarMessage] = useState('');
- const [snackbarSeverity, setSnackbarSeverity] = useState('info');
-
- // Handle snackbar close
- const handleSnackbarClose = useCallback((event, reason) => {
- if (reason === 'clickaway') {
- return;
- }
- setSnackbarOpen(false);
- }, []);
-
- // Show a notification
- const showNotification = useCallback((message, severity = 'info') => {
- setSnackbarMessage(message);
- setSnackbarSeverity(severity);
- setSnackbarOpen(true);
- }, []);
-
- return {
- snackbarOpen,
- snackbarMessage,
- snackbarSeverity,
- handleSnackbarClose,
- showNotification
- };
-};
-
-export default useNotifications;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/usePopoverManagement.js b/frontend/src/components/network/hooks/usePopoverManagement.js
deleted file mode 100644
index 1bdaa9b9c..000000000
--- a/frontend/src/components/network/hooks/usePopoverManagement.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { useState, useRef, useCallback } from 'react';
-
-const usePopoverManagement = () => {
- // State for filter menu
- const [filterAnchorEl, setFilterAnchorEl] = useState(null);
- const openFilters = Boolean(filterAnchorEl);
-
- // Add a ref for the filter button
- const filterButtonRef = useRef(null);
-
- // Filter popover handlers
- const handleFilterButtonClick = useCallback((event) => {
- setFilterAnchorEl(event.currentTarget);
- }, []);
-
- const handleCloseFilterPopover = useCallback(() => {
- setFilterAnchorEl(null);
- }, []);
-
- // Function to close all popovers
- const closeAllPopovers = useCallback(() => {
- setFilterAnchorEl(null);
- }, []);
-
- return {
- filterAnchorEl,
- openFilters,
- filterButtonRef,
- handleFilterButtonClick,
- handleCloseFilterPopover,
- closeAllPopovers
- };
-};
-
-export default usePopoverManagement;
\ No newline at end of file
diff --git a/frontend/src/components/network/hooks/useSortManagement.js b/frontend/src/components/network/hooks/useSortManagement.js
deleted file mode 100644
index f24c96ff0..000000000
--- a/frontend/src/components/network/hooks/useSortManagement.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { useState, useCallback, useEffect } from 'react';
-import { STORAGE_KEY_SORT } from '../../../constants/networkConstants';
-
-const useSortManagement = () => {
- // Sort state
- const [sortConfig, setSortConfig] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_SORT);
- return saved ? JSON.parse(saved) : { key: "node", direction: 'asc' };
- } catch (e) {
- console.error('Error loading sort preferences:', e);
- return { key: "node", direction: 'asc' };
- }
- });
-
- // Request sort by key
- const requestSort = useCallback((key, forcedDirection) => {
- console.log(`Sorting by ${key}${forcedDirection ? ' with forced direction: ' + forcedDirection : ''}`);
-
- setSortConfig(prev => {
- // If a forced direction is provided, use that
- if (forcedDirection) {
- const newConfig = {
- key,
- direction: forcedDirection
- };
- console.log(`Setting sort: ${key} → ${forcedDirection} (forced)`);
- return newConfig;
- }
-
- // Otherwise toggle direction if same key, or default to ascending for new key
- const newDirection = prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc';
-
- const newConfig = {
- key,
- direction: newDirection
- };
-
- console.log(`Setting sort: ${key} → ${newDirection} (toggled from ${prev.key === key ? prev.direction : 'new column'})`);
- return newConfig;
- });
- }, []);
-
- // Log sort config changes for debugging
- useEffect(() => {
- console.log('Current sort config:', sortConfig);
- }, [sortConfig]);
-
- // Save sort preferences whenever they change
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_SORT, JSON.stringify(sortConfig));
- }, [sortConfig]);
-
- return {
- sortConfig,
- setSortConfig,
- requestSort
- };
-};
-
-export default useSortManagement;
\ No newline at end of file
diff --git a/frontend/src/constants/networkConstants.js b/frontend/src/constants/networkConstants.js
deleted file mode 100644
index afb14767e..000000000
--- a/frontend/src/constants/networkConstants.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { keyframes } from '@mui/material';
-
-// Define pulse animation for background
-export const pulseAnimation = keyframes`
- 0% {
- transform: scale(1);
- opacity: 0.9;
- }
- 50% {
- transform: scale(1.02);
- opacity: 1;
- }
- 100% {
- transform: scale(1);
- opacity: 0.9;
- }
-`;
-
-// Define logo pulse animation - more subtle and elegant for the logo
-export const logoPulseAnimation = keyframes`
- 0% {
- opacity: 0.8;
- transform: scale(0.98);
- box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4);
- }
- 50% {
- opacity: 1;
- transform: scale(1.02);
- box-shadow: 0 0 6px 1px rgba(255, 255, 255, 0.3);
- }
- 100% {
- opacity: 0.8;
- transform: scale(0.98);
- box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.4);
- }
-`;
-
-// Define center dot glow animation
-export const centerGlowAnimation = keyframes`
- 0% {
- box-shadow: 0 0 4px 0px rgba(255, 255, 255, 0.5);
- }
- 50% {
- box-shadow: 0 0 8px 2px rgba(255, 255, 255, 0.7);
- }
- 100% {
- box-shadow: 0 0 4px 0px rgba(255, 255, 255, 0.5);
- }
-`;
-
-// Add fade-in animation
-export const fadeIn = keyframes`
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-`;
-
-// Add localStorage keys as constants
-export const STORAGE_KEY_FILTERS = 'network_display_filters';
-export const STORAGE_KEY_SORT = 'network_display_sort';
-export const STORAGE_KEY_SHOW_STOPPED = 'network_display_show_stopped';
-export const STORAGE_KEY_SHOW_FILTERS = 'network_display_show_filters';
-export const STORAGE_KEY_SEARCH_TERMS = 'network_display_search_terms';
-export const STORAGE_KEY_COLUMN_VISIBILITY = 'network_display_column_visibility';
-export const STORAGE_KEY_COLUMN_ORDER = 'network_display_column_order';
-export const STORAGE_KEY_COLUMN_DRAG_ENABLED = 'network_display_column_drag_enabled';
-export const STORAGE_KEY_GUEST_TYPE_FILTER = 'network_display_guest_type_filter';
-
-// Define default column configuration
-export const DEFAULT_COLUMN_CONFIG = {
- name: { id: 'name', label: 'Name', visible: true },
- status: { id: 'status', label: 'Status', visible: true },
- node: { id: 'node', label: 'Node', visible: true },
- role: { id: 'role', label: 'HA Status', visible: true },
- type: { id: 'type', label: 'Type', visible: true },
- id: { id: 'id', label: 'ID', visible: true },
- cpu: { id: 'cpu', label: 'CPU', visible: true },
- memory: { id: 'memory', label: 'Memory', visible: true },
- disk: { id: 'disk', label: 'Disk', visible: true },
- download: { id: 'download', label: 'Download', visible: true },
- upload: { id: 'upload', label: 'Upload', visible: true },
- uptime: { id: 'uptime', label: 'Uptime', visible: true }
-};
\ No newline at end of file
diff --git a/frontend/src/context/SearchContext.jsx b/frontend/src/context/SearchContext.jsx
deleted file mode 100644
index 71bbfbca8..000000000
--- a/frontend/src/context/SearchContext.jsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import React, { createContext, useState, useContext, useEffect, useCallback } from 'react';
-import { STORAGE_KEY_SEARCH_TERMS } from '../constants/networkConstants';
-
-// Create the context
-export const SearchContext = createContext({
- searchTerm: '',
- setSearchTerm: () => {},
- activeSearchTerms: [],
- addSearchTerm: () => {},
- removeSearchTerm: () => {},
- clearSearchTerms: () => {},
- isSearching: false,
- setIsSearching: () => {},
- handleSpecialSearchTerm: () => {},
-});
-
-// Custom hook to use the search context
-export const useSearchContext = () => useContext(SearchContext);
-
-// Search provider component
-export const SearchProvider = ({ children }) => {
- // Search state
- const [searchTerm, setSearchTermInternal] = useState('');
- const [activeSearchTerms, setActiveSearchTerms] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_SEARCH_TERMS);
- return saved ? JSON.parse(saved) : [];
- } catch (e) {
- console.error('Error loading active search terms:', e);
- return [];
- }
- });
- const [isSearching, setIsSearching] = useState(false);
-
- // Wrap setSearchTerm to add logging for debugging
- const setSearchTerm = useCallback((value) => {
- console.log('SearchContext: setSearchTerm called with value:', value);
- // Make sure we always have a string, even if undefined is passed
- const safeValue = value === undefined ? '' : value;
- setSearchTermInternal(safeValue);
- }, []);
-
- // Save active search terms whenever they change
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_SEARCH_TERMS, JSON.stringify(activeSearchTerms));
- }, [activeSearchTerms]);
-
- // Function to handle special search terms (status:, type:, etc.)
- const handleSpecialSearchTerm = useCallback((term, action = 'add') => {
- const termLower = term.toLowerCase().trim();
- const isAdding = action === 'add';
-
- // Create a custom event to dispatch
- const event = new CustomEvent('searchTermAction', {
- detail: {
- term: termLower,
- action: isAdding ? 'add' : 'remove'
- }
- });
-
- // Dispatch the event for other components to listen to
- window.dispatchEvent(event);
- }, []);
-
- // Function to add a search term
- const addSearchTerm = useCallback((term) => {
- if (!activeSearchTerms.includes(term) && term.trim()) {
- setActiveSearchTerms(prev => [...prev, term]);
-
- // Handle special search terms
- handleSpecialSearchTerm(term, 'add');
- }
- }, [activeSearchTerms, handleSpecialSearchTerm]);
-
- // Function to remove a search term
- const removeSearchTerm = useCallback((term) => {
- setActiveSearchTerms(prev => prev.filter(t => t !== term));
-
- // Handle special search terms
- handleSpecialSearchTerm(term, 'remove');
- }, [handleSpecialSearchTerm]);
-
- // Function to clear all search terms
- const clearSearchTerms = useCallback(() => {
- // Handle special search terms for each active term
- activeSearchTerms.forEach(term => {
- handleSpecialSearchTerm(term, 'remove');
- });
-
- setActiveSearchTerms([]);
- // Use the wrapped setSearchTerm
- setSearchTerm('');
- }, [activeSearchTerms, handleSpecialSearchTerm, setSearchTerm]);
-
- // Context value
- const searchContextValue = {
- searchTerm,
- setSearchTerm,
- activeSearchTerms,
- addSearchTerm,
- removeSearchTerm,
- clearSearchTerms,
- isSearching,
- setIsSearching,
- handleSpecialSearchTerm,
- };
-
- return (
-
- {children}
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/context/ThemeContext.jsx b/frontend/src/context/ThemeContext.jsx
deleted file mode 100644
index 7c1de0059..000000000
--- a/frontend/src/context/ThemeContext.jsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import React, { createContext, useState, useContext, useEffect, useMemo } from 'react';
-import { ThemeProvider, createTheme } from '@mui/material/styles';
-import CssBaseline from '@mui/material/CssBaseline';
-
-// Storage key
-const STORAGE_KEY_DARK_MODE = 'app_dark_mode';
-
-// Create the context
-export const ThemeContext = createContext({
- darkMode: false,
- toggleDarkMode: () => {},
-});
-
-// Custom hook to use the theme context
-export const useThemeContext = () => useContext(ThemeContext);
-
-// Theme provider component
-export const AppThemeProvider = ({ children }) => {
- // Initialize dark mode from localStorage or system preference
- const [darkMode, setDarkMode] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_DARK_MODE);
- if (saved === null) {
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
- }
- return JSON.parse(saved) === true;
- } catch (e) {
- console.error('Error loading dark mode preference:', e);
- return false;
- }
- });
-
- // Listen for system preference changes
- useEffect(() => {
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
- const handleChange = (e) => {
- // Only update if the user hasn't set a preference
- if (localStorage.getItem(STORAGE_KEY_DARK_MODE) === null) {
- setDarkMode(e.matches);
- }
- };
-
- // Add event listener
- mediaQuery.addEventListener('change', handleChange);
-
- // Cleanup
- return () => mediaQuery.removeEventListener('change', handleChange);
- }, []);
-
- // Save dark mode preference when it changes
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_DARK_MODE, JSON.stringify(darkMode));
- }, [darkMode]);
-
- // Toggle dark mode function
- const toggleDarkMode = () => {
- setDarkMode(prev => !prev);
- };
-
- // Context value
- const themeContextValue = useMemo(() => ({
- darkMode,
- toggleDarkMode,
- }), [darkMode]);
-
- // Create MUI theme based on dark mode state
- const theme = useMemo(() => createTheme({
- palette: {
- mode: darkMode ? 'dark' : 'light',
- primary: {
- main: '#3a7bd5',
- light: '#5e9cf5',
- dark: '#2c5ea3',
- contrastText: '#ffffff',
- },
- secondary: {
- main: darkMode ? '#bb86fc' : '#00b8d4',
- light: darkMode ? '#d7b8fc' : '#62ebff',
- dark: darkMode ? '#9d4edd' : '#0088a3',
- contrastText: '#ffffff',
- },
- background: {
- default: darkMode ? '#121212' : '#f8f9fa',
- paper: darkMode ? '#1e1e1e' : '#ffffff',
- },
- success: {
- main: '#4caf50',
- light: '#80e27e',
- dark: '#087f23',
- },
- warning: {
- main: '#ff9800',
- light: '#ffc947',
- dark: '#c66900',
- },
- error: {
- main: '#f44336',
- light: '#ff7961',
- dark: '#ba000d',
- },
- info: {
- main: '#2196f3',
- light: '#64b5f6',
- dark: '#0069c0',
- },
- text: {
- primary: darkMode ? '#e0e0e0' : '#212121',
- secondary: darkMode ? '#a0a0a0' : '#757575',
- disabled: darkMode ? '#6c6c6c' : '#9e9e9e',
- },
- divider: darkMode ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.12)',
- },
- typography: {
- fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
- h6: {
- fontWeight: 500,
- letterSpacing: '0.0075em',
- },
- body1: {
- fontSize: '0.9rem',
- },
- body2: {
- fontSize: '0.8rem',
- },
- },
- shape: {
- borderRadius: 8,
- },
- components: {
- MuiAppBar: {
- styleOverrides: {
- root: {
- boxShadow: darkMode
- ? '0 2px 10px rgba(0, 0, 0, 0.5)'
- : '0 2px 10px rgba(0, 0, 0, 0.05)',
- },
- },
- },
- MuiPaper: {
- styleOverrides: {
- root: {
- boxShadow: darkMode
- ? '0 2px 10px rgba(0, 0, 0, 0.2)'
- : '0 2px 10px rgba(0, 0, 0, 0.05)',
- },
- },
- },
- MuiTableCell: {
- styleOverrides: {
- root: {
- padding: '12px 16px',
- borderBottomColor: darkMode ? 'rgba(255, 255, 255, 0.08)' : 'rgba(224, 224, 224, 1)',
- },
- head: {
- fontWeight: 600,
- color: darkMode ? '#e0e0e0' : '#424242',
- userSelect: 'none',
- },
- },
- },
- MuiTableRow: {
- styleOverrides: {
- root: {
- '&:last-child td, &:last-child th': {
- border: 0,
- },
- },
- },
- },
- MuiChip: {
- styleOverrides: {
- root: {
- fontWeight: 500,
- },
- },
- },
- MuiCssBaseline: {
- styleOverrides: {
- body: {
- transition: 'background-color 0.3s ease, color 0.3s ease',
- scrollbarWidth: 'thin',
- scrollbarColor: darkMode ? '#3a3a3a #1e1e1e' : '#bbb #f1f1f1',
- userSelect: 'none',
- '&::-webkit-scrollbar': {
- width: '8px',
- height: '8px',
- },
- '&::-webkit-scrollbar-track': {
- background: darkMode ? '#1e1e1e' : '#f1f1f1',
- },
- '&::-webkit-scrollbar-thumb': {
- background: darkMode ? '#3a3a3a' : '#bbb',
- borderRadius: '4px',
- },
- '&::-webkit-scrollbar-thumb:hover': {
- background: darkMode ? '#555' : '#999',
- },
- },
- },
- },
- },
- }), [darkMode]);
-
- return (
-
-
-
- {children}
-
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/context/UserSettingsContext.jsx b/frontend/src/context/UserSettingsContext.jsx
deleted file mode 100644
index e5d31f75f..000000000
--- a/frontend/src/context/UserSettingsContext.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { createContext, useState, useContext, useEffect, useMemo } from 'react';
-import { useThemeContext } from './ThemeContext';
-
-// Storage keys
-const STORAGE_KEY_COMPACT_MODE = 'app_compact_mode';
-const STORAGE_KEY_SHOW_ONLY_RUNNING = 'app_show_only_running';
-
-// Create the context
-export const UserSettingsContext = createContext({
- compactMode: true,
- toggleCompactMode: () => {},
- getTableCellPadding: () => {},
- showOnlyRunning: false,
- toggleShowOnlyRunning: () => {},
-});
-
-// Custom hook to use the user settings context
-export const useUserSettings = () => useContext(UserSettingsContext);
-
-// User Settings provider component
-export const UserSettingsProvider = ({ children }) => {
- const { darkMode, toggleDarkMode } = useThemeContext();
-
- // Initialize compact mode - always true (no longer checking localStorage)
- const [compactMode, setCompactMode] = useState(true);
-
- // Initialize showOnlyRunning state from localStorage
- const [showOnlyRunning, setShowOnlyRunning] = useState(() => {
- try {
- const saved = localStorage.getItem(STORAGE_KEY_SHOW_ONLY_RUNNING);
- return saved === 'true';
- } catch (e) {
- console.error('Error loading show only running preference:', e);
- return false;
- }
- });
-
- // Save compact mode preference when it changes - keeping for backward compatibility
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_COMPACT_MODE, 'true');
- }, []);
-
- // Save showOnlyRunning preference when it changes
- useEffect(() => {
- localStorage.setItem(STORAGE_KEY_SHOW_ONLY_RUNNING, showOnlyRunning);
-
- // Dispatch a custom event that NetworkDisplay can listen for
- if (window) {
- const event = new CustomEvent('showOnlyRunningChange', {
- detail: { showOnlyRunning }
- });
- window.dispatchEvent(event);
- }
- }, [showOnlyRunning]);
-
- // Toggle compact mode function - keeping for backward compatibility
- const toggleCompactMode = () => {
- // No-op since we always want compact mode
- console.debug('Compact mode toggle attempted but compact mode is always enabled');
- };
-
- // Toggle showOnlyRunning function
- const toggleShowOnlyRunning = () => {
- setShowOnlyRunning(prev => !prev);
- };
-
- // Helper function to get the table cell padding based on the compact mode
- const getTableCellPadding = (isNarrowColumn = false) => {
- if (isNarrowColumn) {
- return '0px 8px'; // Fixed narrow columns always have minimal padding
- }
-
- // Always use compact mode padding
- return '4px 8px';
- };
-
- // Context value
- const settingsContextValue = useMemo(() => ({
- compactMode: true, // Always true
- toggleCompactMode,
- getTableCellPadding,
- darkMode,
- toggleDarkMode,
- showOnlyRunning,
- toggleShowOnlyRunning,
- }), [darkMode, toggleDarkMode, showOnlyRunning]);
-
- return (
-
- {children}
-
- );
-};
\ No newline at end of file
diff --git a/frontend/src/hooks/useFormattedMetrics.js b/frontend/src/hooks/useFormattedMetrics.js
deleted file mode 100644
index 0461e6b17..000000000
--- a/frontend/src/hooks/useFormattedMetrics.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import { useMemo } from 'react';
-
-/**
- * Custom hook to transform metrics data from the useSocket hook into the format expected by the components
- * Uses space-efficient data representations for important metrics
- * @param {Array} metricsData - The metrics data from the useSocket hook
- * @returns {Object} The transformed metrics data
- */
-const useFormattedMetrics = (metricsData) => {
- return useMemo(() => {
- // Initialize the structure
- const result = {
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- };
-
- // If no metrics data, return empty structure
- if (!metricsData || !Array.isArray(metricsData) || metricsData.length === 0) {
- return result;
- }
-
- // Process each metric in the array
- metricsData.forEach(metric => {
- if (!metric || !metric.guestId) {
- return;
- }
-
- const guestId = metric.guestId;
-
- // Extract metrics from the metrics object structure
- const metricData = metric.metrics || {};
-
- // CPU metrics - store as integer percentage (0-100)
- if (typeof metricData.cpu === 'number') {
- const cpuValue = metricData.cpu;
- // Convert to integer percentage if needed (assuming it's already a percentage)
- const cpuPercentage = Math.round(cpuValue);
-
- result.cpu[guestId] = {
- usage: cpuPercentage
- };
- }
-
- // Memory metrics - store values in appropriate units
- const memoryData = metricData.memory || {};
- if (memoryData) {
- // Store percentage as integer (0-100)
- const memoryPercentage = memoryData.usedPercentage !== undefined
- ? Math.round(memoryData.usedPercentage)
- : (memoryData.total && memoryData.used
- ? Math.round((memoryData.used / memoryData.total) * 100)
- : 0);
-
- result.memory[guestId] = {
- used: memoryData.used,
- total: memoryData.total,
- usagePercent: memoryPercentage
- };
- }
-
- // Disk metrics - store values in appropriate units
- const diskData = metricData.disk || {};
- if (diskData) {
- // Store percentage as integer (0-100)
- const diskPercentage = diskData.usedPercentage !== undefined
- ? Math.round(diskData.usedPercentage)
- : (diskData.total && diskData.used
- ? Math.round((diskData.used / diskData.total) * 100)
- : 0);
-
- result.disk[guestId] = {
- used: diskData.used,
- total: diskData.total,
- usagePercent: diskPercentage
- };
- }
-
- // Network metrics - round to appropriate precision
- const networkData = metricData.network || {};
- if (networkData) {
- // Round network rates to integers if they're small, or to 2 decimal places if large
- const inRate = networkData.inRate || 0;
- const outRate = networkData.outRate || 0;
-
- result.network[guestId] = {
- // Use appropriate precision based on value size
- inRate: inRate < 1000 ? Math.round(inRate) : inRate,
- outRate: outRate < 1000 ? Math.round(outRate) : outRate
- };
- }
- });
-
- return result;
- }, [metricsData]);
-};
-
-export default useFormattedMetrics;
\ No newline at end of file
diff --git a/frontend/src/hooks/useMockMetrics.js b/frontend/src/hooks/useMockMetrics.js
deleted file mode 100644
index 697b9bd2d..000000000
--- a/frontend/src/hooks/useMockMetrics.js
+++ /dev/null
@@ -1,279 +0,0 @@
-import { useState, useEffect } from 'react';
-
-/**
- * Custom hook to provide mock metrics data for testing
- * @returns {Object} The mock metrics data
- */
-const useMockMetrics = (guestData) => {
- const [mockMetrics, setMockMetrics] = useState({
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- });
-
- // Keep track of trends for more realistic changes
- const [trends, setTrends] = useState({});
-
- // Generate initial mock metrics
- useEffect(() => {
- if (!guestData || guestData.length === 0) return;
-
- const newMockMetrics = {
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- };
-
- const initialTrends = {};
-
- guestData.forEach(guest => {
- // Generate random CPU usage between 5% and 80%
- newMockMetrics.cpu[guest.id] = {
- usage: Math.floor(Math.random() * 75) + 5
- };
-
- // Generate random memory usage between 10% and 90%
- const memoryPercent = Math.floor(Math.random() * 80) + 10;
- newMockMetrics.memory[guest.id] = {
- used: Math.floor(Math.random() * 8 * 1024 * 1024 * 1024) + 512 * 1024 * 1024, // 512MB to 8.5GB
- total: 16 * 1024 * 1024 * 1024, // 16GB
- usagePercent: memoryPercent
- };
-
- // Generate random disk usage between 20% and 95%
- const diskPercent = Math.floor(Math.random() * 75) + 20;
- newMockMetrics.disk[guest.id] = {
- used: Math.floor(Math.random() * 900 * 1024 * 1024 * 1024) + 100 * 1024 * 1024 * 1024, // 100GB to 1TB
- total: 1024 * 1024 * 1024 * 1024, // 1TB
- usagePercent: diskPercent
- };
-
- // Generate random network rates
- newMockMetrics.network[guest.id] = {
- inRate: Math.floor(Math.random() * 10 * 1024 * 1024) + 1024, // 1KB/s to 10MB/s
- outRate: Math.floor(Math.random() * 5 * 1024 * 1024) + 1024 // 1KB/s to 5MB/s
- };
-
- // Initialize trends
- initialTrends[guest.id] = {
- cpu: Math.random() > 0.5 ? 1 : -1, // Random initial trend direction
- memory: Math.random() > 0.5 ? 1 : -1, // Random initial trend direction
- disk: 1, // Disk typically grows
- netIn: 0, // No initial trend
- netOut: 0, // No initial trend
- spikeCooldown: 0, // No initial cooldown
- loadLevel: Math.floor(Math.random() * 3) // 0=light, 1=medium, 2=heavy
- };
- });
-
- setMockMetrics(newMockMetrics);
- setTrends(initialTrends);
- }, [guestData]);
-
- // Update mock metrics periodically with more dynamic changes
- useEffect(() => {
- if (!guestData || guestData.length === 0) return;
-
- const interval = setInterval(() => {
- setMockMetrics(prev => {
- const updated = { ...prev };
-
- setTrends(prevTrends => {
- const updatedTrends = { ...prevTrends };
-
- guestData.forEach(guest => {
- if (!updatedTrends[guest.id]) {
- // Initialize trend data for new guests
- updatedTrends[guest.id] = {
- cpu: Math.random() > 0.5 ? 1 : -1,
- memory: Math.random() > 0.5 ? 1 : -1,
- disk: 1,
- netIn: 0,
- netOut: 0,
- spikeCooldown: 0,
- loadLevel: Math.floor(Math.random() * 3)
- };
- }
-
- const trend = updatedTrends[guest.id];
-
- // Occasionally change trend direction
- if (Math.random() < 0.1) {
- trend.cpu *= -1;
- }
- if (Math.random() < 0.05) {
- trend.memory *= -1;
- }
-
- // Occasionally change load level
- if (Math.random() < 0.05) {
- trend.loadLevel = Math.floor(Math.random() * 3);
- }
-
- // Decrease spike cooldown
- if (trend.spikeCooldown > 0) {
- trend.spikeCooldown--;
- }
-
- // Handle CPU updates with trend-based changes
- const cpuConfig = {
- changeRange: trend.loadLevel === 0 ? 2 : trend.loadLevel === 1 ? 5 : 10,
- minValue: trend.loadLevel === 0 ? 5 : trend.loadLevel === 1 ? 20 : 40,
- maxValue: trend.loadLevel === 0 ? 30 : trend.loadLevel === 1 ? 60 : 95,
- spikeChance: trend.loadLevel === 0 ? 0.02 : trend.loadLevel === 1 ? 0.05 : 0.1
- };
-
- // Update CPU
- if (updated.cpu[guest.id]) {
- let newCpuUsage = updated.cpu[guest.id].usage;
-
- // Check for spikes
- if (trend.spikeCooldown === 0 && Math.random() < cpuConfig.spikeChance) {
- // Create a spike
- newCpuUsage = Math.min(95, newCpuUsage + Math.random() * 40 + 20);
- trend.spikeCooldown = Math.floor(Math.random() * 5) + 3; // 3-7 intervals cooldown
- } else {
- // Normal fluctuation
- const change = (Math.random() * cpuConfig.changeRange * 2 - cpuConfig.changeRange) +
- (trend.cpu * cpuConfig.changeRange * 0.5);
- newCpuUsage = Math.max(cpuConfig.minValue,
- Math.min(cpuConfig.maxValue, newCpuUsage + change));
- }
-
- updated.cpu[guest.id].usage = newCpuUsage;
- }
-
- // Update Memory with trend-based changes
- if (updated.memory[guest.id]) {
- const memConfig = {
- changePercent: 0.03, // Maximum 3% change per update
- minPercent: 10,
- maxPercent: 90
- };
-
- // Memory often follows CPU with a delay
- const cpuInfluence = trend.cpu > 0 ? 1 : -0.5;
- const change = ((Math.random() * memConfig.changePercent * 2) - memConfig.changePercent +
- (trend.memory * memConfig.changePercent * 0.7) +
- (cpuInfluence * memConfig.changePercent * 0.3)) * 100;
-
- const newPercent = Math.max(memConfig.minPercent,
- Math.min(memConfig.maxPercent,
- updated.memory[guest.id].usagePercent + change));
-
- const newUsed = updated.memory[guest.id].total * (newPercent / 100);
-
- updated.memory[guest.id].used = newUsed;
- updated.memory[guest.id].usagePercent = newPercent;
- }
-
- // Update Disk with slow growth and occasional cleanups
- if (updated.disk[guest.id]) {
- // Disk usually grows slowly
- let diskChange = (Math.random() * 0.5); // 0-0.5% growth per update
-
- // Occasional disk cleanup (5% chance)
- if (Math.random() < 0.05) {
- diskChange = -1 * (Math.random() * 3 + 1); // 1-4% reduction
- }
-
- const newPercent = Math.max(20, Math.min(95, updated.disk[guest.id].usagePercent + diskChange));
- const newUsed = updated.disk[guest.id].total * (newPercent / 100);
-
- updated.disk[guest.id].used = newUsed;
- updated.disk[guest.id].usagePercent = newPercent;
- }
-
- // Update Network with burst patterns
- if (updated.network[guest.id]) {
- // Define realistic baseline values (in bytes/sec)
- const baselineInRate = 20 * 1024; // 20 KB/s baseline
- const baselineOutRate = 10 * 1024; // 10 KB/s baseline
-
- // Maximum sustainable values (in bytes/sec)
- const maxSustainedInRate = 300 * 1024; // 300 KB/s max sustained
- const maxSustainedOutRate = 150 * 1024; // 150 KB/s max sustained
-
- // Current values
- const currentInRate = updated.network[guest.id].inRate;
- const currentOutRate = updated.network[guest.id].outRate;
-
- // Force strong regression to baseline over time
- // Reset to baseline every ~30 seconds (1 in 20 chance per 1.5 sec interval)
- const needsReset = Math.random() < 0.05;
-
- // Determine if we should generate a burst
- // Reduce burst frequency when rates are already high
- const ratesAreHigh = currentInRate > 100 * 1024 || currentOutRate > 50 * 1024;
- const burstProbability = ratesAreHigh ? 0.05 : 0.1; // Reduce burst chance when already high
- const burstMode = Math.random() < burstProbability;
-
- // Calculate new rates based on current state
- let newInRate, newOutRate;
-
- if (needsReset) {
- // Periodic reset to baseline to prevent long-term growth
- newInRate = baselineInRate + (Math.random() * 10 * 1024); // baseline + small randomness
- newOutRate = baselineOutRate + (Math.random() * 5 * 1024); // baseline + small randomness
- } else if (burstMode) {
- // Generate a traffic burst
- const burstInSize = Math.random() * 150 * 1024 + 50 * 1024; // 50-200KB/s increase
- const burstOutSize = Math.random() * 100 * 1024 + 30 * 1024; // 30-130KB/s increase
-
- // Apply burst, but cap at max values
- newInRate = Math.min(maxSustainedInRate, currentInRate + burstInSize);
- newOutRate = Math.min(maxSustainedOutRate, currentOutRate + burstOutSize);
- } else {
- // Normal regression toward baseline - stronger the further from baseline
- const inRateDistanceFromBaseline = Math.max(0, currentInRate - baselineInRate);
- const outRateDistanceFromBaseline = Math.max(0, currentOutRate - baselineOutRate);
-
- // Decay rate increases with distance (50-80% decay)
- const inDecayRate = 0.5 + (inRateDistanceFromBaseline / (maxSustainedInRate * 2));
- const outDecayRate = 0.5 + (outRateDistanceFromBaseline / (maxSustainedOutRate * 2));
-
- // Calculate decay amount (strong regression to baseline)
- const inDecayAmount = inRateDistanceFromBaseline * Math.min(0.8, inDecayRate);
- const outDecayAmount = outRateDistanceFromBaseline * Math.min(0.8, outDecayRate);
-
- // Add small random fluctuation (-10KB to +5KB)
- const randomFluctuation = (Math.random() * 15 - 10) * 1024;
-
- // Apply decay with fluctuation, ensuring we don't go below baseline
- newInRate = Math.max(
- baselineInRate,
- currentInRate - inDecayAmount + randomFluctuation
- );
-
- newOutRate = Math.max(
- baselineOutRate,
- currentOutRate - outDecayAmount + (randomFluctuation / 2)
- );
- }
-
- // Final safety caps
- newInRate = Math.min(maxSustainedInRate, Math.max(baselineInRate / 2, newInRate));
- newOutRate = Math.min(maxSustainedOutRate, Math.max(baselineOutRate / 2, newOutRate));
-
- // Update the network metrics
- updated.network[guest.id].inRate = newInRate;
- updated.network[guest.id].outRate = newOutRate;
- }
- });
-
- return updatedTrends;
- });
-
- return updated;
- });
- }, 1500); // Update more frequently (every 1.5 seconds instead of 5 seconds)
-
- return () => clearInterval(interval);
- }, [guestData]);
-
- return mockMetrics;
-};
-
-export default useMockMetrics;
\ No newline at end of file
diff --git a/frontend/src/hooks/useSocket.js b/frontend/src/hooks/useSocket.js
deleted file mode 100644
index 401f5ea82..000000000
--- a/frontend/src/hooks/useSocket.js
+++ /dev/null
@@ -1,1546 +0,0 @@
-import { useState, useEffect, useCallback, useRef } from 'react';
-import { io } from 'socket.io-client';
-import { clearAppData } from '../utils/storageUtils';
-
-// Constants
-const CONNECTION_TIMEOUT_MS = 5000; // 5 seconds timeout for connection attempts
-
-// Add this function for more dynamic mock data generation
-const generateDynamicMetric = (baseValue, min, max, changeRange) => {
- // Random value between -changeRange and +changeRange
- const change = (Math.random() * changeRange * 2) - changeRange;
- return Math.max(min, Math.min(max, baseValue + change));
-};
-
-/**
- * Custom hook to manage WebSocket connections with Socket.io
- * @param {string} url - WebSocket server URL (defaults to current origin)
- * @returns {Object} Socket state and message handlers
- */
-const useSocket = (url) => {
- // In development mode, we need to connect to the backend server on port 7654
- // In production, we can use window.location.origin since both frontend and backend are served from the same origin
- const isDevelopment = import.meta.env.DEV;
-
- // Get the current host
- const currentHost = window.location.hostname;
-
- // For development, we need to explicitly connect to the backend server
- // For production, we use the same origin that served the page
- let socketUrl;
- if (isDevelopment) {
- // First check environment variables, then fall back to localStorage
- const envUseMockData = import.meta.env.VITE_USE_MOCK_DATA === 'true';
- const envMockDataEnabled = import.meta.env.VITE_MOCK_DATA_ENABLED === 'true';
-
- // If environment variables are set, they take precedence
- // Otherwise, check localStorage
- const useMockData = (envUseMockData || envMockDataEnabled) ||
- (localStorage.getItem('use_mock_data') === 'true' ||
- localStorage.getItem('MOCK_DATA_ENABLED') === 'true');
-
- // Always connect to the backend server (7654), which will handle the mock data if needed
- socketUrl = `http://${currentHost}:7654`;
- } else {
- // In production, use the same origin that served the page
- socketUrl = window.location.origin;
- }
-
- // Check if we're using mock data - prioritize environment variables
- const envUseMockData = import.meta.env.VITE_USE_MOCK_DATA === 'true';
- const envMockDataEnabled = import.meta.env.VITE_MOCK_DATA_ENABLED === 'true';
-
- // First check environment variables, then fall back to localStorage
- const useMockData = (envUseMockData || envMockDataEnabled) ||
- (localStorage.getItem('use_mock_data') === 'true' ||
- localStorage.getItem('MOCK_DATA_ENABLED') === 'true');
-
- // Store the mock data status as a state variable so it can be exposed in the return value
- const [isMockData, setIsMockData] = useState(useMockData);
-
- const [isConnected, setIsConnected] = useState(false);
- const [lastMessage, setLastMessage] = useState(null);
- const [nodeData, setNodeData] = useState([]);
- const [guestData, setGuestData] = useState([]);
- const [metricsData, setMetricsData] = useState([]);
- const [processedMetricsData, setProcessedMetricsData] = useState({
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- });
- const [forceUpdateCounter, setForceUpdateCounter] = useState(0);
- const [error, setError] = useState(null);
- const [connectionStatus, setConnectionStatus] = useState('connecting');
-
- // Use ref to maintain socket instance across renders
- const socketRef = useRef(null);
- // Use ref for ping interval
- const pingIntervalRef = useRef(null);
- // Track reconnection attempts to prevent infinite loops
- const reconnectAttemptsRef = useRef(0);
- // Reduce reconnection attempts in development mode
- const MAX_RECONNECT_ATTEMPTS = isDevelopment ? 1 : 3;
-
- // Also add a metrics update interval
- const metricsIntervalRef = useRef(null);
- // Track if we've already set up the interval
- const hasSetupMetricsInterval = useRef(false);
-
- const [nodeStatus, setNodeStatus] = useState({});
- const [pendingGuests, setPendingGuests] = useState({});
-
- // Initialize socket connection
- useEffect(() => {
- // Clear any existing socket connection
- if (socketRef.current) {
- socketRef.current.disconnect();
- }
-
- // Log connection attempt
- console.log(`Attempting to connect to socket server at: ${socketUrl}`);
-
- // In mock data mode, we can immediately set up the UI without waiting for a connection
- if (useMockData) {
- console.log('Using mock data mode - will connect to mock server');
- // We'll let the actual server connection process handle setting up the data
- // Don't create fake data here - rely on the mock server
- }
-
- // Set a timeout to ensure we don't wait forever for the socket connection
- const connectionTimeout = setTimeout(() => {
- if (!isConnected) {
- console.log('Socket connection timeout');
- setConnectionStatus('error');
- setError('Connection timeout - failed to connect to server');
- }
- }, CONNECTION_TIMEOUT_MS);
-
- try {
- // Create new socket connection
- socketRef.current = io(socketUrl, {
- transports: ['websocket', 'polling'],
- reconnectionAttempts: MAX_RECONNECT_ATTEMPTS,
- reconnectionDelay: 2000,
- reconnectionDelayMax: 10000,
- timeout: 20000,
- autoConnect: true,
- forceNew: true,
- randomizationFactor: 0.5
- });
-
- // Set up event handlers
- socketRef.current.on('connect', () => {
- console.log('Socket connected successfully');
- clearTimeout(connectionTimeout); // Clear the timeout on successful connection
- setIsConnected(true);
- setConnectionStatus('connected');
- setError(null);
- reconnectAttemptsRef.current = 0; // Reset reconnect attempts on successful connection
-
- // Register as a dashboard client to get all guests
- socketRef.current.emit('register', {
- nodeId: 'dashboard',
- nodeName: 'dashboard',
- clientType: 'dashboard'
- });
- console.log('Registered as dashboard client');
-
- // Request server configuration to check if mock data is enabled on the server
- socketRef.current.emit('getServerConfig', (config) => {
- if (config && (config.useMockData || config.mockDataEnabled)) {
- console.log('Server is using mock data');
- setIsMockData(true);
- // Store this information in localStorage for persistence
- localStorage.setItem('use_mock_data', 'true');
- localStorage.setItem('MOCK_DATA_ENABLED', 'true');
- } else {
- console.log('Server is using real data');
- setIsMockData(false);
- // Clear the localStorage values
- localStorage.removeItem('use_mock_data');
- localStorage.removeItem('MOCK_DATA_ENABLED');
- }
- });
-
- // Request initial data
- socketRef.current.emit('requestNodeData');
- socketRef.current.emit('requestGuestData');
- socketRef.current.emit('requestMetricsData');
-
- // Set up custom guests handler to ensure no deduplication
- socketRef.current.on('guests', (data) => {
- // Check if we got either an array directly or an object with a guests property
- const guestData = Array.isArray(data) ? data : (data && data.guests ? data.guests : []);
-
- if (!Array.isArray(guestData)) {
- console.error('🔍 SOCKET: Received invalid guest data format:', data);
- return;
- }
-
- // Log what we received for debugging
- console.log(`🔍 SOCKET: Received ${guestData.length} guests from server`);
-
- // Log distribution by node for debugging
- const guestsByNode = {};
- guestData.forEach(guest => {
- if (!guest) return;
- const nodeId = guest.node || 'unknown';
- if (!guestsByNode[nodeId]) {
- guestsByNode[nodeId] = [];
- }
- guestsByNode[nodeId].push(guest.id);
- });
-
- console.log('🔍 SOCKET: Guest distribution from server:');
- Object.keys(guestsByNode).sort().forEach(nodeId => {
- console.log(` - ${nodeId}: ${guestsByNode[nodeId].length} guests (${guestsByNode[nodeId].join(', ')})`);
- });
-
- // CRITICAL: Set guest data directly without any filtering
- setGuestData(guestData);
-
- // Force update to trigger re-renders
- setForceUpdateCounter(prevCounter => prevCounter + 1);
- });
-
- // Start ping interval
- if (pingIntervalRef.current) {
- clearInterval(pingIntervalRef.current);
- }
- pingIntervalRef.current = setInterval(() => {
- if (socketRef.current && socketRef.current.connected) {
- socketRef.current.emit('ping', { timestamp: Date.now() });
- }
- }, 5000);
- });
-
- // Handle page visibility changes
- const handleVisibilityChange = () => {
- if (document.visibilityState === 'visible' && socketRef.current && !socketRef.current.connected) {
- if (reconnectAttemptsRef.current < MAX_RECONNECT_ATTEMPTS) {
- reconnectAttemptsRef.current++;
- reconnect();
- } else {
- console.warn(`Maximum reconnection attempts (${MAX_RECONNECT_ATTEMPTS}) reached. Not attempting further reconnections.`);
- // If using mock data, still show the UI
- if (useMockData) {
- setIsConnected(true);
- setConnectionStatus('connected');
- }
- }
- }
- };
-
- // Handle page unload
- const handleBeforeUnload = () => {
- if (socketRef.current) {
- socketRef.current.disconnect();
- }
- };
-
- // Set up page visibility and unload listeners
- document.addEventListener('visibilitychange', handleVisibilityChange);
- window.addEventListener('beforeunload', handleBeforeUnload);
-
- // Handle connection errors
- socketRef.current.on('connect_error', (err) => {
- console.error('Socket connection error:', err);
-
- // If we're reconnecting, emit events to get the current data
- if (reconnectAttemptsRef.current > 0) {
- setConnectionStatus('reconnecting');
- console.log(`Reconnection attempt ${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS} failed`);
- } else {
- setConnectionStatus('error');
- setIsConnected(false);
- setError(`Connection error: ${err.message}`);
- }
- });
-
- socketRef.current.on('disconnect', (reason) => {
- console.log(`Socket disconnected: ${reason}`);
- setIsConnected(false);
- setConnectionStatus('disconnected');
-
- // Don't attempt to reconnect if we've reached the maximum attempts
- if (reconnectAttemptsRef.current >= MAX_RECONNECT_ATTEMPTS) {
- console.warn(`Maximum reconnection attempts (${MAX_RECONNECT_ATTEMPTS}) reached. Not attempting further reconnections.`);
- return;
- }
-
- // Only attempt to reconnect for certain disconnect reasons
- if (reason === 'io server disconnect' || reason === 'transport close') {
- reconnectAttemptsRef.current++;
- // Calculate a delay with exponential backoff and randomization
- const baseDelay = 3000; // Base delay of 3 seconds
- const attempt = reconnectAttemptsRef.current;
- const exponentialDelay = baseDelay * Math.pow(1.5, attempt); // Exponential backoff
- const jitter = Math.random() * 2000; // Random jitter up to 2 seconds
- const reconnectDelay = exponentialDelay + jitter;
-
- console.log(`Will attempt reconnection in ${Math.round(reconnectDelay / 1000)} seconds (attempt ${attempt}/${MAX_RECONNECT_ATTEMPTS})`);
-
- // Attempt to reconnect after the calculated delay
- setTimeout(() => {
- if (socketRef.current) {
- socketRef.current.connect();
- }
- }, reconnectDelay);
- }
- });
-
- // Message handler
- socketRef.current.on('message', (message) => {
- setLastMessage(message);
-
- switch (message.type) {
- case 'CONNECTED':
- break;
-
- case 'NODE_STATUS_UPDATE':
- handleNodeStatusUpdate(message.payload);
- break;
-
- case 'GUEST_STATUS_UPDATE':
- handleGuestStatusUpdate(message.payload);
- break;
-
- case 'METRICS_UPDATE':
- handleMetricsUpdate(message.payload);
- break;
-
- case 'EVENT':
- handleEventUpdate(message.payload);
- break;
-
- case 'ERROR':
- setError(message.payload);
- break;
-
- default:
- }
- });
-
- // Direct event handler for migration events
- socketRef.current.on('event', (event) => {
- if (event && event.type === 'migration') {
- handleEventUpdate(event);
- }
- });
-
- // Setup socket event listeners
- socketRef.current.on('nodeData', (data) => {
- if (Array.isArray(data)) {
- console.log('🔍 SOCKET: Received nodeData event with', data.length, 'nodes');
- console.log('🔍 SOCKET: Node data:', data);
-
- // Verify we have all three expected nodes
- const nodeNames = data.map(node => node.name).sort();
- console.log('🔍 SOCKET: Node names:', nodeNames);
-
- // Check if we have the expected PVE nodes
- const hasPveNodes = data.some(node =>
- (node.name && (node.name.includes('pve-prod') || node.name.includes('pve-dev')))
- );
-
- if (!hasPveNodes) {
- console.error('🚨 SOCKET: Missing expected PVE nodes in nodeData!');
- }
-
- if (data.length < 3) {
- console.warn(`⚠️ SOCKET: Expected at least 3 nodes but received ${data.length}`);
- }
-
- // Check if this is mock data by looking for the 'pve-prod' prefix in node names
- const isMockDataNodes = data.some(node => node.name && (
- node.name.startsWith('pve-prod') ||
- node.name.startsWith('MOCK-')
- ));
- if (isMockDataNodes) {
- console.log('Detected mock data from node names');
- setIsMockData(true);
- }
-
- // Ensure we're not getting empty or invalid node data
- const validNodes = data.filter(node => node && node.id && node.name);
- if (validNodes.length < data.length) {
- console.warn(`⚠️ SOCKET: Filtered out ${data.length - validNodes.length} invalid nodes`);
- setNodeData(validNodes);
- } else {
- setNodeData(data);
- }
- } else {
- console.error('🚨 SOCKET: Received nodeData event but data is not an array:', data);
- }
- });
-
- // Handle guests update from server
- socketRef.current.on('guests', (data) => {
- // Check if we got either an array directly or an object with a guests property
- const guestData = Array.isArray(data) ? data : (data.guests || []);
-
- if (!Array.isArray(guestData)) {
- console.error('Received invalid guest data format from socket:', data);
- return;
- }
-
- // Add descriptive logging to help debug the issue
- console.log(`🔍 SOCKET: Received ${guestData.length} guests from server`);
-
- // Log distribution by node for debugging
- const guestsByNode = {};
- guestData.forEach(guest => {
- const nodeId = guest.node;
- if (!guestsByNode[nodeId]) {
- guestsByNode[nodeId] = [];
- }
- guestsByNode[nodeId].push(guest.id);
- });
-
- console.log('🔍 SOCKET: Guest distribution by node:');
- Object.keys(guestsByNode).sort().forEach(nodeId => {
- console.log(` - ${nodeId}: ${guestsByNode[nodeId].length} guests`);
- });
-
- // IMPORTANT: Handle guest data without deduplication
- // Just set the state directly without any filtering
- setGuestData(guestData);
-
- // Mark the update with a new timestamp
- setLastMessage({
- type: 'GUEST_UPDATE',
- timestamp: new Date().toISOString(),
- count: guestData.length
- });
-
- // Force update to trigger re-renders
- setForceUpdateCounter(prevCounter => prevCounter + 1);
- });
-
- // Cleanup function
- return () => {
- clearTimeout(connectionTimeout); // Clear the timeout on cleanup
- if (socketRef.current) {
- socketRef.current.disconnect();
- }
-
- // Remove event listeners
- document.removeEventListener('visibilitychange', handleVisibilityChange);
- window.removeEventListener('beforeunload', handleBeforeUnload);
- };
- } catch (error) {
- clearTimeout(connectionTimeout); // Clear the timeout on error
- console.error('Error initializing socket connection:', error);
- setError('Failed to initialize socket connection');
-
- // If we're using mock data, still show the UI
- if (useMockData) {
- setIsConnected(true);
- setConnectionStatus('connected');
- } else {
- setConnectionStatus('error');
- }
- }
- }, [socketUrl, useMockData]); // Remove isConnected from dependency array to prevent reconnection loops
-
- // Generate mock guest data useEffect has been removed to prevent duplicate guests
- // Server-side mock data from src/mock/custom-data.ts is now the only source of truth
-
- // Create or update metrics for mock data display
- useEffect(() => {
- if (useMockData && guestData.length > 0) {
- console.log('Setting up mock metrics with %d guests (%d running)',
- guestData.length,
- guestData.filter(g => g.status === 'running').length
- );
-
- // Initialize metrics data if empty
- if (metricsData.length === 0) {
- // Create initial mock metrics for running guests
- const mockMetrics = guestData
- .filter(guest => guest.status === 'running') // Only generate metrics for running guests
- .map(guest => {
- // Generate more interesting initial values with guaranteed minimums
- const cpuUsage = Math.max(25, 10 + Math.random() * 40); // 25-50% initial CPU
- const memPercent = Math.max(30, 20 + Math.random() * 40); // 30-60% initial memory
- const diskPercent = Math.max(40, 30 + Math.random() * 50); // 40-80% initial disk
-
- console.log(`Generating initial metrics for ${guest.name || 'unnamed'} (${guest.id}): CPU: ${cpuUsage.toFixed(1)}%, Memory: ${memPercent.toFixed(1)}%, Disk: ${diskPercent.toFixed(1)}%`);
-
- return {
- guestId: guest.id,
- nodeId: guest.node || 'node-1',
- timestamp: Date.now(),
- metrics: {
- cpu: cpuUsage,
- memory: {
- total: 16 * 1024 * 1024 * 1024, // 16 GB
- used: (16 * 1024 * 1024 * 1024) * (memPercent / 100), // Memory used based on percentage
- percentUsed: memPercent
- },
- disk: {
- total: 500 * 1024 * 1024 * 1024, // 500 GB
- used: (500 * 1024 * 1024 * 1024) * (diskPercent / 100), // Disk used based on percentage
- percentUsed: diskPercent
- },
- network: {
- // Use more realistic values for data center environments
- inRate: 100 * 1024 + Math.random() * 400 * 1024, // 100-500 KB/s baseline
- outRate: 50 * 1024 + Math.random() * 150 * 1024, // 50-200 KB/s baseline
- },
- uptime: guest.uptime || 3600 * (1 + Math.floor(Math.random() * 72)) // Ensure uptime value exists
- }
- };
- });
-
- console.log(`Generated ${mockMetrics.length} initial mock metrics`);
- setMetricsData(mockMetrics);
-
- // Also initialize the processed metrics data
- const processedData = {
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- };
-
- // Process each mock metric entry for UI components
- mockMetrics.forEach(metricEntry => {
- const { guestId: metricGuestId, metrics } = metricEntry;
-
- // CPU - ensure it's a number and has correct properties
- const cpuValue = parseFloat(metrics.cpu) || 0;
- processedData.cpu[metricGuestId] = {
- usage: cpuValue
- };
-
- // Memory - ensure percentages are numbers and have correct properties
- const memPercentValue = parseFloat(metrics.memory.percentUsed) || 0;
- processedData.memory[metricGuestId] = {
- total: metrics.memory.total,
- used: metrics.memory.used,
- percentUsed: memPercentValue,
- usagePercent: memPercentValue // Important: UI uses this property
- };
-
- // Disk: More dynamic changes with occasional cleanups
- let diskDelta;
- if (Math.random() < 0.15) { // Increased chance from 10% to 15%
- // More significant disk cleanup
- diskDelta = -1 * (Math.random() * 8 + 3); // 3-11% reduction (increased)
- // Define shouldLog for conditionally enabling console logs
- const diskShouldLog = Math.random() < 0.1;
- if (diskShouldLog) {
- console.log(`Disk cleanup for ${metricGuestId}: ${(metrics.disk.percentUsed || 0).toFixed(1)}% -> ${((metrics.disk.percentUsed || 50) + diskDelta).toFixed(1)}%`);
- }
- } else {
- // Disk more noticeable growth
- diskDelta = Math.random() * 2.5; // 0-2.5% growth (increased)
- }
- const newDiskPercent = Math.max(20, Math.min(95, (metrics.disk.percentUsed || 50) + diskDelta));
- const newDiskUsed = (metrics.disk.total || 500 * 1024 * 1024 * 1024) * (newDiskPercent / 100);
-
- // Network: More dramatic bursty traffic patterns
- let initialNewInRate, initialNewOutRate;
-
- // Define network traffic patterns based on VM type
- // Simplified - use fewer variables and clearer workload patterns
- const vmGuestId = metricGuestId || '';
- const vmLastDigit = vmGuestId ? parseInt(vmGuestId.slice(-1)) : 0;
-
- // Define VM workload types (based on last digit of ID for deterministic behavior)
- const workloadType =
- [9].includes(vmLastDigit) ? 'high_bandwidth' : // 10% are high bandwidth servers (backups, mirrors, etc)
- [1, 5].includes(vmLastDigit) ? 'web_server' : // 20% are web servers
- [2, 3, 7].includes(vmLastDigit) ? 'database' : // 30% are databases
- [0, 4].includes(vmLastDigit) ? 'file_server' : // 20% are file servers
- 'idle'; // 20% are mostly idle VMs
-
- // Get current values with fallbacks to appropriate defaults
- let currentInRate = metrics.network?.inRate || 0;
- let currentOutRate = metrics.network?.outRate || 0;
-
- // Define workload-specific network traffic patterns
- const networkPatterns = {
- high_bandwidth: {
- // Very high bandwidth servers (backups, streaming, mirrors, etc.)
- baselineIn: 100 * 1024, // 100 KB/s in
- baselineOut: 150 * 1024, // 150 KB/s out
- maxIn: 50 * 1024 * 1024, // 50 MB/s max in
- maxOut: 70 * 1024 * 1024, // 70 MB/s max out
- burstProbability: 0.3, // 30% chance of burst
- burstInSize: () => {
- // 10% chance of mega burst (5-20 MB/s)
- if (Math.random() < 0.1) {
- return (Math.random() * 15 + 5) * 1024 * 1024;
- }
- // Normal burst (0.5-3 MB/s)
- return (Math.random() * 2.5 + 0.5) * 1024 * 1024;
- },
- burstOutSize: () => {
- // 10% chance of mega burst (10-30 MB/s)
- if (Math.random() < 0.1) {
- return (Math.random() * 20 + 10) * 1024 * 1024;
- }
- // Normal burst (1-5 MB/s)
- return (Math.random() * 4 + 1) * 1024 * 1024;
- },
- decayRate: 0.3 // 30% decay rate (slower to allow higher values to persist)
- },
- web_server: {
- // Web servers have higher baseline output than input, with frequent small bursts
- baselineIn: 20 * 1024, // 20 KB/s in
- baselineOut: 50 * 1024, // 50 KB/s out
- maxIn: 5 * 1024 * 1024, // 5 MB/s (~40 Mbps) max in
- maxOut: 10 * 1024 * 1024, // 10 MB/s (~80 Mbps) max out
- burstProbability: 0.25, // 25% chance of burst (increased)
- burstInSize: () => {
- // 5% chance of large burst (1-3 MB/s)
- if (Math.random() < 0.05) {
- return (Math.random() * 2 + 1) * 1024 * 1024;
- }
- // Normal burst (50-300 KB/s)
- return (Math.random() * 250 + 50) * 1024;
- },
- burstOutSize: () => {
- // 5% chance of large burst (2-5 MB/s)
- if (Math.random() < 0.05) {
- return (Math.random() * 3 + 2) * 1024 * 1024;
- }
- // Normal burst (100-500 KB/s)
- return (Math.random() * 400 + 100) * 1024;
- },
- decayRate: 0.5 // 50% decay rate toward baseline
- },
- database: {
- // Databases have moderate, more stable traffic
- baselineIn: 30 * 1024, // 30 KB/s in
- baselineOut: 20 * 1024, // 20 KB/s out
- maxIn: 4 * 1024 * 1024, // 4 MB/s max in
- maxOut: 3 * 1024 * 1024, // 3 MB/s max out
- burstProbability: 0.15, // 15% chance of burst (increased)
- burstInSize: () => {
- // 3% chance of large burst (0.5-2 MB/s)
- if (Math.random() < 0.03) {
- return (Math.random() * 1.5 + 0.5) * 1024 * 1024;
- }
- // Normal burst (50-250 KB/s)
- return (Math.random() * 200 + 50) * 1024;
- },
- burstOutSize: () => {
- // 3% chance of large burst (0.3-1.5 MB/s)
- if (Math.random() < 0.03) {
- return (Math.random() * 1.2 + 0.3) * 1024 * 1024;
- }
- // Normal burst (30-150 KB/s)
- return (Math.random() * 120 + 30) * 1024;
- },
- decayRate: 0.45 // 45% decay rate (slower to allow higher values to persist)
- },
- file_server: {
- // File servers have high baseline with large bursts
- baselineIn: 50 * 1024, // 50 KB/s in
- baselineOut: 80 * 1024, // 80 KB/s out
- maxIn: 20 * 1024 * 1024, // 20 MB/s max in
- maxOut: 25 * 1024 * 1024, // 25 MB/s max out
- burstProbability: 0.2, // 20% chance of burst (increased)
- burstInSize: () => {
- // 8% chance of large burst (2-8 MB/s)
- if (Math.random() < 0.08) {
- return (Math.random() * 6 + 2) * 1024 * 1024;
- }
- // Normal burst (200-800 KB/s)
- return (Math.random() * 600 + 200) * 1024;
- },
- burstOutSize: () => {
- // 8% chance of large burst (3-12 MB/s)
- if (Math.random() < 0.08) {
- return (Math.random() * 9 + 3) * 1024 * 1024;
- }
- // Normal burst (300-1200 KB/s)
- return (Math.random() * 900 + 300) * 1024;
- },
- decayRate: 0.4 // 40% decay rate (slower to allow higher values to persist)
- },
- idle: {
- // Idle VMs have minimal traffic
- baselineIn: 2 * 1024, // 2 KB/s in
- baselineOut: 1 * 1024, // 1 KB/s out
- maxIn: 1 * 1024 * 1024, // 1 MB/s max in
- maxOut: 500 * 1024, // 500 KB/s max out
- burstProbability: 0.05, // 5% chance of burst
- burstInSize: () => {
- // 1% chance of unexpected large burst (100-500 KB/s)
- if (Math.random() < 0.01) {
- return (Math.random() * 400 + 100) * 1024;
- }
- // Normal small burst (10-50 KB/s)
- return (Math.random() * 40 + 10) * 1024;
- },
- burstOutSize: () => {
- // 1% chance of unexpected large burst (50-250 KB/s)
- if (Math.random() < 0.01) {
- return (Math.random() * 200 + 50) * 1024;
- }
- // Normal small burst (5-30 KB/s)
- return (Math.random() * 25 + 5) * 1024;
- },
- decayRate: 0.7 // 70% decay rate (quickly returns to baseline)
- }
- };
-
- // Get pattern for this VM
- const pattern = networkPatterns[workloadType];
-
- // Determine if this update will create a burst
- const createBurst = Math.random() < pattern.burstProbability;
- // Occasional complete reset to baseline (1% chance)
- const forceReset = Math.random() < 0.01;
-
- // Define log chance once for this section
- const netLogChance = Math.random() < 0.05; // 5% chance to log
-
- if (forceReset) {
- // Reset to baseline with small random variation
- initialNewInRate = pattern.baselineIn * (0.8 + Math.random() * 0.4); // 80-120% of baseline
- initialNewOutRate = pattern.baselineOut * (0.8 + Math.random() * 0.4);
-
- if (netLogChance) {
- console.log(`Network reset for ${vmGuestId} (${workloadType}): In: ${(currentInRate/1024).toFixed(1)}KB/s → ${(initialNewInRate/1024).toFixed(1)}KB/s`);
- }
- }
- else if (createBurst) {
- // Apply a burst (increase traffic significantly)
- const burstInSize = pattern.burstInSize();
- const burstOutSize = pattern.burstOutSize();
-
- initialNewInRate = Math.min(pattern.maxIn, currentInRate + burstInSize);
- initialNewOutRate = Math.min(pattern.maxOut, currentOutRate + burstOutSize);
-
- if (netLogChance) {
- console.log(`Network burst for ${vmGuestId} (${workloadType}): In: ${(currentInRate/1024).toFixed(1)}KB/s → ${(initialNewInRate/1024).toFixed(1)}KB/s`);
- }
- }
- else {
- // Regular update: decay toward baseline + small random fluctuation
- const inDistance = Math.max(0, currentInRate - pattern.baselineIn);
- const outDistance = Math.max(0, currentOutRate - pattern.baselineOut);
-
- // Stronger decay when further from baseline (more realistic)
- const inDecayFactor = pattern.decayRate * (1 + inDistance / (pattern.maxIn * 0.5));
- const outDecayFactor = pattern.decayRate * (1 + outDistance / (pattern.maxOut * 0.5));
-
- // Apply decay with small random fluctuation
- const inDecay = inDistance * Math.min(0.95, inDecayFactor);
- const outDecay = outDistance * Math.min(0.95, outDecayFactor);
-
- // Small random fluctuations (-0.5KB to +1KB)
- const fluctuation = (Math.random() * 1.5 - 0.5) * 1024;
-
- initialNewInRate = Math.max(
- pattern.baselineIn * 0.8, // Don't go below 80% of baseline
- Math.min(
- pattern.maxIn,
- currentInRate - inDecay + fluctuation
- )
- );
-
- initialNewOutRate = Math.max(
- pattern.baselineOut * 0.8, // Don't go below 80% of baseline
- Math.min(
- pattern.maxOut,
- currentOutRate - outDecay + (fluctuation * 0.8)
- )
- );
- }
-
- // Final safety check - ensure values are within appropriate ranges
- initialNewInRate = Math.max(1024, Math.min(pattern.maxIn, initialNewInRate));
- initialNewOutRate = Math.max(512, Math.min(pattern.maxOut, initialNewOutRate));
-
- processedData.disk[metricGuestId] = {
- total: metrics.disk.total,
- used: metrics.disk.used,
- percentUsed: newDiskPercent,
- usagePercent: newDiskPercent // Important: UI uses this property
- };
-
- processedData.network[metricGuestId] = {
- inRate: initialNewInRate,
- outRate: initialNewOutRate
- };
- });
-
- // Update the processed metrics data state
- setProcessedMetricsData(processedData);
- }
-
- // Always set up an interval to update mock metrics, regardless of whether
- // we just initialized the data or not
- console.log("Setting up metrics update interval");
-
- // Only set up the interval if it's not already running
- if (metricsIntervalRef.current) {
- clearInterval(metricsIntervalRef.current);
- metricsIntervalRef.current = null;
- }
-
- // Set a flag to indicate we're setting up the interval
- if (!hasSetupMetricsInterval.current) {
- console.log("First time setting up metrics interval");
- hasSetupMetricsInterval.current = true;
- } else {
- console.log("Re-setting up metrics interval");
- }
-
- metricsIntervalRef.current = setInterval(() => {
- try {
- // Define shouldLog once at the top for use throughout this interval function
- const intervalShouldLog = Math.random() < 0.1; // Only log about 10% of updates
-
- if (intervalShouldLog) {
- console.log('Mock update interval - updating data');
- }
-
- // Also occasionally change guest status
- if (Math.random() < 0.05) { // 5% chance per interval
- setGuestData(prevGuests => {
- try {
- // Make a copy of the guests array
- const updatedGuests = [...prevGuests];
-
- // Randomly select a guest to change status
- const randomIndex = Math.floor(Math.random() * updatedGuests.length);
- const guest = updatedGuests[randomIndex];
-
- // Determine new status - with probability favoring current state
- let newStatus;
- if (guest.status === 'running') {
- // 10% chance a running guest stops
- newStatus = Math.random() < 0.1 ? 'stopped' : 'running';
- } else {
- // 20% chance a stopped guest starts
- newStatus = Math.random() < 0.2 ? 'running' : 'stopped';
- }
-
- // Only update if status changed
- if (newStatus !== guest.status) {
- updatedGuests[randomIndex] = {
- ...guest,
- status: newStatus,
- // Reset or set uptime accordingly
- uptime: newStatus === 'running' ? 300 : 0 // New running guests start with 5 min uptime
- };
-
- if (intervalShouldLog) {
- console.log(`Mock guest ${guest.name || 'unnamed'} (${guest.id}): changed status from ${guest.status} to ${newStatus}`);
- }
- }
-
- return updatedGuests;
- } catch (error) {
- console.error('Error updating guest status:', error);
- return prevGuests; // Return unchanged if error
- }
- });
- }
-
- // Always update uptime for running guests
- setGuestData(prevGuests => {
- try {
- const updatedGuests = prevGuests.map(guest => {
- if (guest && guest.status === 'running') {
- // Increase uptime by the interval time (in seconds)
- return {
- ...guest,
- uptime: (guest.uptime || 0) + 2 // Add 2 seconds per interval
- };
- }
- return guest;
- });
- return updatedGuests;
- } catch (error) {
- console.error('Error updating guest uptime:', error);
- return prevGuests; // Return unchanged if error
- }
- });
-
- // Directly increase force update counter to ensure UI re-renders
- setForceUpdateCounter(prev => (prev + 1) % 10000);
-
- // Update metrics regardless of existing data
- setMetricsData(prev => {
- try {
- const currentMetrics = prev || [];
-
- // Get current list of running guests
- const runningGuestIds = guestData
- .filter(g => g && g.status === 'running')
- .map(g => g.id);
-
- // Update existing metrics
- const updatedMetrics = currentMetrics
- .filter(metric => runningGuestIds.includes(metric.guestId)) // Keep only metrics for running guests
- .map(metric => {
- // Get the corresponding guest to update uptime
- const guest = guestData.find(g => g.id === metric.guestId);
-
- // CPU: More dynamic changes with occasional spikes
- let newCpu;
- if (Math.random() < 0.35) { // Increased chance of spike from 20% to 35%
- // Significant spike
- newCpu = Math.min(95, metric.metrics.cpu + 25 + Math.random() * 30);
- if (intervalShouldLog) {
- console.log(`CPU spike for ${metric.guestId}: ${(metric.metrics.cpu || 0).toFixed(1)}% -> ${newCpu.toFixed(1)}%`);
- }
- } else if (Math.random() < 0.35) { // Increased chance of drop from 20% to 35%
- // Significant drop
- newCpu = Math.max(5, metric.metrics.cpu - 25 - Math.random() * 20);
- if (intervalShouldLog) {
- console.log(`CPU drop for ${metric.guestId}: ${(metric.metrics.cpu || 0).toFixed(1)}% -> ${newCpu.toFixed(1)}%`);
- }
- } else {
- // More noticeable regular changes - increase changeRange from 12 to 20
- newCpu = generateDynamicMetric(metric.metrics.cpu || 0, 5, 95, 20);
- }
-
- // Memory: More noticeable changes
- let memoryDelta = (Math.random() * 12) - 6; // -6 to +6 base change (increased)
- if (newCpu > (metric.metrics.cpu || 0) + 10) {
- // If CPU spiked up, memory likely increases too
- memoryDelta += 5;
- } else if (newCpu < (metric.metrics.cpu || 0) - 10) {
- // If CPU dropped significantly, memory might decrease too
- memoryDelta -= 2;
- }
- const newMemPercent = Math.max(10, Math.min(90, (metric.metrics?.memory?.percentUsed || 50) + memoryDelta));
- const newMemUsed = (metric.metrics.memory?.total || 16 * 1024 * 1024 * 1024) * (newMemPercent / 100);
-
- // Disk: More dynamic changes with occasional cleanups
- let diskDelta;
- if (Math.random() < 0.15) { // Increased chance from 10% to 15%
- // More significant disk cleanup
- diskDelta = -1 * (Math.random() * 8 + 3); // 3-11% reduction (increased)
- // Define shouldLog for conditionally enabling console logs
- const diskShouldLog = Math.random() < 0.1;
- if (diskShouldLog) {
- console.log(`Disk cleanup for ${metric.guestId}: ${(metric.metrics.disk?.percentUsed || 0).toFixed(1)}% -> ${((metric.metrics.disk?.percentUsed || 50) + diskDelta).toFixed(1)}%`);
- }
- } else {
- // Disk more noticeable growth
- diskDelta = Math.random() * 2.5; // 0-2.5% growth (increased)
- }
- const newDiskPercent = Math.max(20, Math.min(95, (metric.metrics.disk?.percentUsed || 50) + diskDelta));
- const newDiskUsed = (metric.metrics.disk?.total || 500 * 1024 * 1024 * 1024) * (newDiskPercent / 100);
-
- // Network: More dramatic bursty traffic patterns
- let metricNewInRate, metricNewOutRate;
-
- // Define network traffic patterns based on VM type
- // Simplified - use fewer variables and clearer workload patterns
- const vmGuestId = metric.guestId || '';
- const vmLastDigit = vmGuestId ? parseInt(vmGuestId.slice(-1)) : 0;
-
- // Define VM workload types (based on last digit of ID for deterministic behavior)
- const workloadType =
- [9].includes(vmLastDigit) ? 'high_bandwidth' : // 10% are high bandwidth servers (backups, mirrors, etc)
- [1, 5].includes(vmLastDigit) ? 'web_server' : // 20% are web servers
- [2, 3, 7].includes(vmLastDigit) ? 'database' : // 30% are databases
- [0, 4].includes(vmLastDigit) ? 'file_server' : // 20% are file servers
- 'idle'; // 20% are mostly idle VMs
-
- // Get current values with fallbacks to appropriate defaults
- let currentInRate = metric.metrics.network?.inRate || 0;
- let currentOutRate = metric.metrics.network?.outRate || 0;
-
- // Define workload-specific network traffic patterns
- const networkPatterns = {
- high_bandwidth: {
- // Very high bandwidth servers (backups, streaming, mirrors, etc.)
- baselineIn: 100 * 1024, // 100 KB/s in
- baselineOut: 150 * 1024, // 150 KB/s out
- maxIn: 50 * 1024 * 1024, // 50 MB/s max in
- maxOut: 70 * 1024 * 1024, // 70 MB/s max out
- burstProbability: 0.3, // 30% chance of burst
- burstInSize: () => {
- // 10% chance of mega burst (5-20 MB/s)
- if (Math.random() < 0.1) {
- return (Math.random() * 15 + 5) * 1024 * 1024;
- }
- // Normal burst (0.5-3 MB/s)
- return (Math.random() * 2.5 + 0.5) * 1024 * 1024;
- },
- burstOutSize: () => {
- // 10% chance of mega burst (10-30 MB/s)
- if (Math.random() < 0.1) {
- return (Math.random() * 20 + 10) * 1024 * 1024;
- }
- // Normal burst (1-5 MB/s)
- return (Math.random() * 4 + 1) * 1024 * 1024;
- },
- decayRate: 0.3 // 30% decay rate (slower to allow higher values to persist)
- },
- web_server: {
- // Web servers have higher baseline output than input, with frequent small bursts
- baselineIn: 20 * 1024, // 20 KB/s in
- baselineOut: 50 * 1024, // 50 KB/s out
- maxIn: 5 * 1024 * 1024, // 5 MB/s (~40 Mbps) max in
- maxOut: 10 * 1024 * 1024, // 10 MB/s (~80 Mbps) max out
- burstProbability: 0.25, // 25% chance of burst (increased)
- burstInSize: () => {
- // 5% chance of large burst (1-3 MB/s)
- if (Math.random() < 0.05) {
- return (Math.random() * 2 + 1) * 1024 * 1024;
- }
- // Normal burst (50-300 KB/s)
- return (Math.random() * 250 + 50) * 1024;
- },
- burstOutSize: () => {
- // 5% chance of large burst (2-5 MB/s)
- if (Math.random() < 0.05) {
- return (Math.random() * 3 + 2) * 1024 * 1024;
- }
- // Normal burst (100-500 KB/s)
- return (Math.random() * 400 + 100) * 1024;
- },
- decayRate: 0.5 // 50% decay rate toward baseline
- },
- database: {
- // Databases have moderate, more stable traffic
- baselineIn: 30 * 1024, // 30 KB/s in
- baselineOut: 20 * 1024, // 20 KB/s out
- maxIn: 4 * 1024 * 1024, // 4 MB/s max in
- maxOut: 3 * 1024 * 1024, // 3 MB/s max out
- burstProbability: 0.15, // 15% chance of burst (increased)
- burstInSize: () => {
- // 3% chance of large burst (0.5-2 MB/s)
- if (Math.random() < 0.03) {
- return (Math.random() * 1.5 + 0.5) * 1024 * 1024;
- }
- // Normal burst (50-250 KB/s)
- return (Math.random() * 200 + 50) * 1024;
- },
- burstOutSize: () => {
- // 3% chance of large burst (0.3-1.5 MB/s)
- if (Math.random() < 0.03) {
- return (Math.random() * 1.2 + 0.3) * 1024 * 1024;
- }
- // Normal burst (30-150 KB/s)
- return (Math.random() * 120 + 30) * 1024;
- },
- decayRate: 0.45 // 45% decay rate (slower to allow higher values to persist)
- },
- file_server: {
- // File servers have high baseline with large bursts
- baselineIn: 50 * 1024, // 50 KB/s in
- baselineOut: 80 * 1024, // 80 KB/s out
- maxIn: 20 * 1024 * 1024, // 20 MB/s max in
- maxOut: 25 * 1024 * 1024, // 25 MB/s max out
- burstProbability: 0.2, // 20% chance of burst (increased)
- burstInSize: () => {
- // 8% chance of large burst (2-8 MB/s)
- if (Math.random() < 0.08) {
- return (Math.random() * 6 + 2) * 1024 * 1024;
- }
- // Normal burst (200-800 KB/s)
- return (Math.random() * 600 + 200) * 1024;
- },
- burstOutSize: () => {
- // 8% chance of large burst (3-12 MB/s)
- if (Math.random() < 0.08) {
- return (Math.random() * 9 + 3) * 1024 * 1024;
- }
- // Normal burst (300-1200 KB/s)
- return (Math.random() * 900 + 300) * 1024;
- },
- decayRate: 0.4 // 40% decay rate (slower to allow higher values to persist)
- },
- idle: {
- // Idle VMs have minimal traffic
- baselineIn: 2 * 1024, // 2 KB/s in
- baselineOut: 1 * 1024, // 1 KB/s out
- maxIn: 1 * 1024 * 1024, // 1 MB/s max in
- maxOut: 500 * 1024, // 500 KB/s max out
- burstProbability: 0.05, // 5% chance of burst
- burstInSize: () => {
- // 1% chance of unexpected large burst (100-500 KB/s)
- if (Math.random() < 0.01) {
- return (Math.random() * 400 + 100) * 1024;
- }
- // Normal small burst (10-50 KB/s)
- return (Math.random() * 40 + 10) * 1024;
- },
- burstOutSize: () => {
- // 1% chance of unexpected large burst (50-250 KB/s)
- if (Math.random() < 0.01) {
- return (Math.random() * 200 + 50) * 1024;
- }
- // Normal small burst (5-30 KB/s)
- return (Math.random() * 25 + 5) * 1024;
- },
- decayRate: 0.7 // 70% decay rate (quickly returns to baseline)
- }
- };
-
- // Get pattern for this VM
- const pattern = networkPatterns[workloadType];
-
- // Determine if this update will create a burst
- const createBurst = Math.random() < pattern.burstProbability;
- // Occasional complete reset to baseline (1% chance)
- const forceReset = Math.random() < 0.01;
-
- // Define log chance once for this section
- const netLogChance = Math.random() < 0.05; // 5% chance to log
-
- if (forceReset) {
- // Reset to baseline with small random variation
- metricNewInRate = pattern.baselineIn * (0.8 + Math.random() * 0.4); // 80-120% of baseline
- metricNewOutRate = pattern.baselineOut * (0.8 + Math.random() * 0.4);
-
- if (netLogChance) {
- console.log(`Network reset for ${vmGuestId} (${workloadType}): In: ${(currentInRate/1024).toFixed(1)}KB/s → ${(metricNewInRate/1024).toFixed(1)}KB/s`);
- }
- }
- else if (createBurst) {
- // Apply a burst (increase traffic significantly)
- const burstInSize = pattern.burstInSize();
- const burstOutSize = pattern.burstOutSize();
-
- metricNewInRate = Math.min(pattern.maxIn, currentInRate + burstInSize);
- metricNewOutRate = Math.min(pattern.maxOut, currentOutRate + burstOutSize);
-
- if (netLogChance) {
- console.log(`Network burst for ${vmGuestId} (${workloadType}): In: ${(currentInRate/1024).toFixed(1)}KB/s → ${(metricNewInRate/1024).toFixed(1)}KB/s`);
- }
- }
- else {
- // Regular update: decay toward baseline + small random fluctuation
- const inDistance = Math.max(0, currentInRate - pattern.baselineIn);
- const outDistance = Math.max(0, currentOutRate - pattern.baselineOut);
-
- // Stronger decay when further from baseline (more realistic)
- const inDecayFactor = pattern.decayRate * (1 + inDistance / (pattern.maxIn * 0.5));
- const outDecayFactor = pattern.decayRate * (1 + outDistance / (pattern.maxOut * 0.5));
-
- // Apply decay with small random fluctuation
- const inDecay = inDistance * Math.min(0.95, inDecayFactor);
- const outDecay = outDistance * Math.min(0.95, outDecayFactor);
-
- // Small random fluctuations (-0.5KB to +1KB)
- const fluctuation = (Math.random() * 1.5 - 0.5) * 1024;
-
- metricNewInRate = Math.max(
- pattern.baselineIn * 0.8, // Don't go below 80% of baseline
- Math.min(
- pattern.maxIn,
- currentInRate - inDecay + fluctuation
- )
- );
-
- metricNewOutRate = Math.max(
- pattern.baselineOut * 0.8, // Don't go below 80% of baseline
- Math.min(
- pattern.maxOut,
- currentOutRate - outDecay + (fluctuation * 0.8)
- )
- );
- }
-
- // Final safety check - ensure values are within appropriate ranges
- metricNewInRate = Math.max(1024, Math.min(pattern.maxIn, metricNewInRate));
- metricNewOutRate = Math.max(512, Math.min(pattern.maxOut, metricNewOutRate));
-
- return {
- ...metric,
- timestamp: Date.now(),
- metrics: {
- ...metric.metrics,
- cpu: newCpu,
- memory: {
- ...(metric.metrics?.memory || {}),
- used: newMemUsed,
- percentUsed: newMemPercent,
- total: metric.metrics?.memory?.total || 16 * 1024 * 1024 * 1024 // Ensure total is defined
- },
- disk: {
- ...(metric.metrics?.disk || {}),
- percentUsed: newDiskPercent,
- used: newDiskUsed,
- total: metric.metrics?.disk?.total || 500 * 1024 * 1024 * 1024 // Ensure total is defined
- },
- network: {
- ...(metric.metrics?.network || {}),
- inRate: metricNewInRate,
- outRate: metricNewOutRate
- },
- uptime: guest?.uptime || (metric.metrics?.uptime || 0) + 2 // Ensure uptime is defined
- }
- };
- });
-
- // Add metrics for newly running guests
- const existingMetricGuestIds = updatedMetrics.map(m => m.guestId);
- const newRunningGuests = guestData.filter(g =>
- g.status === 'running' && !existingMetricGuestIds.includes(g.id)
- );
-
- // Generate metrics for new running guests
- newRunningGuests.forEach(guest => {
- // Generate highly visible initial values with high minimums and maximums
- const cpuUsage = Math.max(40, 30 + Math.random() * 40); // 40-70% initial CPU
- const memPercent = Math.max(45, 35 + Math.random() * 40); // 45-75% initial memory
- const diskPercent = Math.max(50, 40 + Math.random() * 40); // 50-80% initial disk
-
- // Define shouldLog at the beginning of the function
- const initialShouldLog = Math.random() < 0.1;
-
- if (initialShouldLog) {
- console.log(`Initial metrics for ${guest.name || 'unnamed'} (${guest.id}): CPU: ${cpuUsage.toFixed(1)}%, Memory: ${memPercent.toFixed(1)}%, Disk: ${diskPercent.toFixed(1)}%`);
- }
-
- updatedMetrics.push({
- guestId: guest.id,
- nodeId: guest.node || 'node-1',
- timestamp: Date.now(),
- metrics: {
- cpu: cpuUsage,
- memory: {
- total: 16 * 1024 * 1024 * 1024, // 16 GB
- used: (16 * 1024 * 1024 * 1024) * (memPercent / 100),
- percentUsed: memPercent
- },
- disk: {
- total: 500 * 1024 * 1024 * 1024, // 500 GB
- used: (500 * 1024 * 1024 * 1024) * (diskPercent / 100),
- percentUsed: diskPercent
- },
- network: {
- inRate: 20 * 1024 + Math.random() * 30 * 1024, // 20-50 KB/s baseline
- outRate: 10 * 1024 + Math.random() * 15 * 1024, // 10-25 KB/s baseline
- },
- uptime: guest.uptime || 300 // 5 minutes default
- }
- });
- });
-
- // Immediately update the processed metrics data with the updated metrics
- // This is critical - we need to use updatedMetrics, not metricsData state variable
- setProcessedMetricsData(prevData => {
- const processedData = {
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- };
-
- // Process each metric entry using the updated metrics
- updatedMetrics.forEach(metricEntry => {
- const { guestId, metrics } = metricEntry;
-
- // Only include metrics for running guests
- if (guestData.find(g => g.id === guestId && g.status === 'running')) {
- // CPU - ensure it's a number
- const cpuValue = parseFloat(metrics.cpu) || 0;
- processedData.cpu[guestId] = {
- usage: cpuValue
- };
-
- // Memory - ensure percentages are numbers
- const memPercentValue = parseFloat(metrics.memory.percentUsed) || 0;
- processedData.memory[guestId] = {
- total: metrics.memory.total,
- used: metrics.memory.used,
- percentUsed: memPercentValue,
- usagePercent: memPercentValue // Include both for compatibility
- };
-
- // Disk - ensure percentages are numbers
- const diskPercentValue = parseFloat(metrics.disk.percentUsed) || 0;
- processedData.disk[guestId] = {
- total: metrics.disk.total,
- used: metrics.disk.used,
- percentUsed: diskPercentValue,
- usagePercent: diskPercentValue // Important: UI uses this property
- };
-
- // Network - ensure rates are numbers
- const inRateValue = parseFloat(metrics.network.inRate) || 0;
- const outRateValue = parseFloat(metrics.network.outRate) || 0;
- processedData.network[guestId] = {
- inRate: inRateValue,
- outRate: outRateValue
- };
- }
- });
-
- return processedData;
- });
-
- return updatedMetrics;
- } catch (error) {
- console.error('Error updating metrics data:', error);
- return prev; // Return unchanged if error
- }
- }, 2000); // Update every 2 seconds for less frequent updates
- } catch (error) {
- console.error('Error in mock update interval:', error);
- // Don't rethrow - we want the interval to keep running
- }
- }, 2000); // Update every 2 seconds for less frequent updates
- }
-
- return () => {
- if (metricsIntervalRef.current) {
- clearInterval(metricsIntervalRef.current);
- metricsIntervalRef.current = null;
- }
- };
- }, [useMockData, guestData]);
-
- // Handler for node status updates
- const handleNodeStatusUpdate = useCallback((payload) => {
- // Handle both single node and array of nodes
- const nodeData = Array.isArray(payload) ? payload : [payload];
-
- setNodeData(prevNodes => {
- const updatedNodes = [...prevNodes];
-
- nodeData.forEach(node => {
- const index = updatedNodes.findIndex(n => n.id === node.id);
- if (index >= 0) {
- updatedNodes[index] = node;
- } else {
- updatedNodes.push(node);
- }
- });
-
- return updatedNodes;
- });
- }, []);
-
- // Handler for guest status updates
- const handleGuestStatusUpdate = useCallback((payload) => {
- // Handle both single guest and array of guests
- const guestData = Array.isArray(payload) ? payload : [payload];
-
- // Log what we received for debugging
- console.log(`🔍 SOCKET: handleGuestStatusUpdate received ${guestData.length} guests`);
-
- // Log distribution by node for debugging
- const guestsByNode = {};
- guestData.forEach(guest => {
- const nodeId = guest.node || 'unknown';
- if (!guestsByNode[nodeId]) {
- guestsByNode[nodeId] = [];
- }
- guestsByNode[nodeId].push(guest.id);
- });
-
- console.log('🔍 SOCKET: Guest distribution in update:');
- Object.keys(guestsByNode).sort().forEach(nodeId => {
- console.log(` - ${nodeId}: ${guestsByNode[nodeId].length} guests`);
- });
-
- // CRITICAL: Just use the new guest data directly without any filtering or deduplication
- // The mock server should be sending the correct data
- setGuestData(guestData);
-
- // Store the current environment with the guest data
- try {
- localStorage.setItem('guest_data_cache', JSON.stringify({
- environment: import.meta.env.MODE || 'development',
- timestamp: Date.now()
- }));
- } catch (error) {
- console.error('Error storing guest data cache info:', error);
- }
-
- // Force update to trigger re-renders
- setForceUpdateCounter(prevCounter => prevCounter + 1);
- }, []);
-
- // Handler for metrics updates
- const handleMetricsUpdate = useCallback((payload) => {
- // Handle both single metric and array of metrics
- const metricData = Array.isArray(payload) ? payload : [payload];
-
- // Use functional update to avoid closure issues with stale state
- setMetricsData(prevMetrics => {
- // Performance optimization: Check if we actually have new data before updating
- if (!metricData.length) return prevMetrics;
-
- // Make a new array to ensure React detects the change
- const newMetrics = [...prevMetrics];
- let hasChanges = false;
-
- // Process updates with a Map for O(1) lookups instead of O(n) array searches
- const metricsMap = new Map();
-
- // First create a map of existing metrics for faster lookup
- prevMetrics.forEach(metric => {
- if (metric.guestId) {
- metricsMap.set(metric.guestId, metric);
- }
- });
-
- // Then process the updates
- metricData.forEach(metric => {
- if (!metric.guestId) {
- return; // Skip metrics without a guestId
- }
-
- const existingMetric = metricsMap.get(metric.guestId);
-
- if (existingMetric) {
- // Update existing metric only if the timestamp is newer
- // Increase minimum time threshold to avoid too frequent updates
- const minUpdateInterval = 1000; // milliseconds (increased from 200ms to 1000ms)
- const timeSinceLastUpdate = metric.timestamp - (existingMetric.timestamp || 0);
-
- if (!existingMetric.timestamp ||
- (metric.timestamp >= existingMetric.timestamp && timeSinceLastUpdate >= minUpdateInterval)) {
- // Find the index in the array
- const index = newMetrics.findIndex(m => m.guestId === metric.guestId);
- if (index >= 0) {
- // Check if the metrics have actually changed, with higher thresholds to reduce UI flicker
- const hasSignificantChange =
- Math.abs((metric.metrics?.cpu || 0) - (existingMetric.metrics?.cpu || 0)) > 1.0 ||
- Math.abs((metric.metrics?.memory?.percentUsed || 0) - (existingMetric.metrics?.memory?.percentUsed || 0)) > 1.0 ||
- Math.abs((metric.metrics?.disk?.percentUsed || 0) - (existingMetric.metrics?.disk?.percentUsed || 0)) > 1.0 ||
- Math.abs((metric.metrics?.network?.inRate || 0) - (existingMetric.metrics?.network?.inRate || 0)) > 2.0 ||
- Math.abs((metric.metrics?.network?.outRate || 0) - (existingMetric.metrics?.network?.outRate || 0)) > 2.0;
-
- // Always update if it's been more than 3 seconds, regardless of change magnitude
- const forceUpdateInterval = 3000; // 3 seconds (increased from 2 seconds)
- const shouldForceUpdate = timeSinceLastUpdate >= forceUpdateInterval;
-
- if (hasSignificantChange || shouldForceUpdate) {
- newMetrics[index] = {
- ...existingMetric,
- ...metric,
- timestamp: metric.timestamp || Date.now()
- };
- hasChanges = true;
- }
- }
- }
- } else {
- // Add new metric
- newMetrics.push({
- ...metric,
- timestamp: metric.timestamp || Date.now()
- });
- hasChanges = true;
- }
- });
-
- // Only update state if there were actual changes
- return hasChanges ? newMetrics : prevMetrics;
- });
- }, []);
-
- // Handler for event updates
- const handleEventUpdate = useCallback((event) => {
- if (!event) return;
-
- console.log('Event received:', event);
-
- try {
- // Check if this is a migration event
- const isMigrationEvent =
- (event.type === 'migration') ||
- (event.details?.type === 'migration') ||
- (event.description?.toLowerCase().includes('migration'));
-
- if (isMigrationEvent) {
- console.log('Migration event received:', event);
-
- // Extract migration details from either format
- const details = event.details || {};
- const guestId = details.guestId || event.guestId || '';
- const guestName = details.guestName || event.guestName || '';
- const fromNode = details.fromNode || event.fromNode || '';
- const toNode = details.toNode || event.toNode || event.node || '';
- const timestamp = details.timestamp || event.eventTime || Date.now();
-
- // Emit a custom event that can be listened to by other components
- const migrationEvent = new CustomEvent('proxmox:migration', {
- detail: {
- guestId,
- guestName,
- fromNode,
- toNode,
- timestamp,
- originalEvent: event
- }
- });
- window.dispatchEvent(migrationEvent);
-
- // Request fresh guest data after a migration
- if (socketRef.current) {
- socketRef.current.emit('requestGuestData');
- }
- }
-
- // Always set the last message for other event types
- setLastMessage(event);
- } catch (error) {
- console.error('Error handling event update:', error);
- }
- }, []);
-
- // Function to manually attempt reconnection
- const reconnect = useCallback(() => {
- if (socketRef.current) {
- setConnectionStatus('connecting');
- socketRef.current.connect();
- }
- }, []);
-
- // Function to subscribe to specific node or guest
- const subscribeToNode = useCallback((nodeId) => {
- if (socketRef.current && isConnected) {
- socketRef.current.emit('subscribe:node', nodeId);
- }
- }, [isConnected]);
-
- const subscribeToGuest = useCallback((guestId) => {
- if (socketRef.current && isConnected) {
- socketRef.current.emit('subscribe:guest', guestId);
- }
- }, [isConnected]);
-
- // Function to get historical data
- const getHistory = useCallback((id) => {
- return new Promise((resolve) => {
- if (socketRef.current && isConnected) {
- socketRef.current.emit('get:history', id, (data) => {
- resolve(data);
- });
- } else {
- resolve([]);
- }
- });
- }, [isConnected]);
-
- return {
- isConnected,
- lastMessage,
- error,
- nodeData,
- guestData,
- metricsData,
- processedMetricsData,
- forceUpdateCounter,
- connectionStatus,
- reconnect,
- subscribeToNode,
- subscribeToGuest,
- getHistory,
- isMockData
- };
-};
-
-export default useSocket;
\ No newline at end of file
diff --git a/frontend/src/index.css b/frontend/src/index.css
deleted file mode 100644
index 151862c83..000000000
--- a/frontend/src/index.css
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Column dragging styles */
-.column-dragging {
- cursor: grabbing !important;
-}
-
-.column-dragging * {
- cursor: grabbing !important;
-}
-
-/* Table header styles for dragging */
-.MuiTableCell-head {
- position: relative;
- cursor: grab;
-}
-
-/* Drop highlight effect */
-.drop-highlight-before {
- box-shadow: inset 3px 0 0 var(--mui-palette-primary-main) !important;
-}
-
-.drop-highlight-after {
- box-shadow: inset -3px 0 0 var(--mui-palette-primary-main) !important;
-}
-
-/* Transition for all table cells */
-.MuiTableCell-head {
- transition: background-color 0.2s, box-shadow 0.2s, transform 0.1s, opacity 0.2s !important;
-}
\ No newline at end of file
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
deleted file mode 100644
index e1d06a076..000000000
--- a/frontend/src/main.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-import ErrorBoundary from './components/ErrorBoundary';
-
-ReactDOM.createRoot(document.getElementById('root')).render(
-
-
-
-
-
-);
\ No newline at end of file
diff --git a/frontend/src/utils/ExhaustiveSearchTests.js b/frontend/src/utils/ExhaustiveSearchTests.js
deleted file mode 100644
index d4090b416..000000000
--- a/frontend/src/utils/ExhaustiveSearchTests.js
+++ /dev/null
@@ -1,374 +0,0 @@
-/**
- * Exhaustive Search Test Suite
- *
- * This test suite verifies ALL possible search patterns, combinations, and edge cases.
- * It uses both mock data and real-world data structures to ensure accuracy.
- */
-
-const { getSortedAndFilteredData } = require('./networkUtils');
-
-// Create a more extensive mock data set with varied properties
-const generateExhaustiveTestData = () => {
- // Base test data similar to the real application
- const guests = [
- // PRIMARY GUESTS
- {
- id: '101',
- name: 'web-server', // Plain name
- type: 'qemu',
- status: 'running',
- node: 'pve-prod-01',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['web', 'production']
- },
- {
- id: '102',
- name: 'database-primary', // Has "primary" in name
- type: 'qemu',
- status: 'running',
- node: 'pve-prod-01',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['db', 'primary', 'production']
- },
- {
- id: '103',
- name: 'sprint-server', // Has "pri" as substring
- type: 'qemu',
- status: 'paused',
- node: 'pve-prod-01',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['sprint', 'production']
- },
-
- // SECONDARY GUESTS
- {
- id: '201',
- name: 'web-secondary',
- type: 'qemu',
- status: 'running',
- node: 'pve-prod-02',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['web', 'secondary']
- },
- {
- id: '202',
- name: 'sec-database', // Has "sec" at start of name
- type: 'lxc',
- status: 'stopped',
- node: 'pve-prod-02',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['db', 'backup']
- },
- {
- id: '203',
- name: 'prism-backup', // Has "pri" as substring
- type: 'lxc',
- status: 'running',
- node: 'pve-prod-02',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['backup']
- },
-
- // NON-SHARED GUESTS
- {
- id: '301',
- name: 'standalone-pri-app', // Has "pri" in name but is NOT primary
- type: 'qemu',
- status: 'running',
- node: 'pve-dev-01',
- shared: false,
- tags: ['dev', 'private']
- },
- {
- id: '302',
- name: 'security-container', // Has "sec" as substring but is NOT secondary
- type: 'lxc',
- status: 'stopped',
- node: 'pve-dev-01',
- shared: false,
- tags: ['security', 'dev']
- },
-
- // EDGE CASES
- {
- id: '401',
- name: 'p', // Single-letter name matching a role search
- type: 'qemu',
- status: 'running',
- node: 'pve-edge-01',
- shared: true,
- primaryNode: 'pve-edge-01',
- tags: ['test']
- },
- {
- id: '402',
- name: 's', // Single-letter name matching a role search
- type: 'qemu',
- status: 'running',
- node: 'pve-edge-01',
- shared: true,
- primaryNode: 'pve-prod-01',
- tags: ['test']
- }
- ];
-
- // Add optional metrics data
- const metricsData = {
- cpu: {},
- memory: {},
- disk: {},
- network: {}
- };
-
- guests.forEach(guest => {
- const id = guest.id;
- metricsData.cpu[id] = { usage: Math.random() * 100 };
- metricsData.memory[id] = { usagePercent: Math.random() * 100 };
- metricsData.disk[id] = { usagePercent: Math.random() * 100 };
- metricsData.network[id] = { inRate: Math.random() * 10000000, outRate: Math.random() * 10000000 };
- });
-
- // Node data for name resolution
- const nodeData = [
- { id: 'pve-prod-01', name: 'prod-cluster-1' },
- { id: 'pve-prod-02', name: 'prod-cluster-2' },
- { id: 'pve-dev-01', name: 'dev-cluster' },
- { id: 'pve-edge-01', name: 'edge-node' }
- ];
-
- return { guests, metricsData, nodeData };
-};
-
-// Function to run all exhaustive tests
-export function runExhaustiveTests() {
- console.log('===== EXHAUSTIVE SEARCH TEST SUITE =====');
- console.log('Testing all possible search patterns and edge cases');
-
- const { guests, metricsData, nodeData } = generateExhaustiveTestData();
-
- // Collection of all test cases
- const testCases = [
- // 1. ROLE BASED SEARCHES - STANDALONE TERMS
- // Each of these tests a specific role search term
- { category: 'ROLE STANDALONE', term: 'role', description: 'All shared guests',
- expectIdsContaining: ['101', '102', '103', '201', '202', '203', '401', '402'] },
-
- { category: 'ROLE STANDALONE', term: 'shared', description: 'Alternative for all shared guests',
- expectIdsContaining: ['101', '102', '103', '201', '202', '203', '401', '402'] },
-
- { category: 'ROLE STANDALONE', term: 'primary', description: 'Finds primary guests',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE STANDALONE', term: 'pri', description: 'Short form of primary',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE STANDALONE', term: 'p', description: 'Shortest form of primary',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE STANDALONE', term: 'secondary', description: 'Finds secondary guests',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'ROLE STANDALONE', term: 'sec', description: 'Short form of secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'ROLE STANDALONE', term: 's', description: 'Shortest form of secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- // 2. ROLE COLUMN SEARCHES
- { category: 'ROLE COLUMN', term: 'role:primary', description: 'Column search for primary',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE COLUMN', term: 'role:pri', description: 'Column search with short primary',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE COLUMN', term: 'role:p', description: 'Column search with shortest primary',
- expectIdsContaining: ['101', '102', '103', '401'] },
-
- { category: 'ROLE COLUMN', term: 'role:secondary', description: 'Column search for secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'ROLE COLUMN', term: 'role:sec', description: 'Column search with short secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'ROLE COLUMN', term: 'role:s', description: 'Column search with shortest secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'ROLE COLUMN', term: 'role:none', description: 'Column search for non-shared',
- expectIdsContaining: ['301', '302'] },
-
- { category: 'ROLE COLUMN', term: 'role:-', description: 'Column search for non-shared (dash)',
- expectIdsContaining: ['301', '302'] },
-
- // 3. TEXT SEARCHES WITH ROLE COMPONENTS
- { category: 'TEXT MATCH', term: 'primary-', description: 'Has primary in name but with a suffix',
- expectIdsContaining: ['102'] },
-
- { category: 'TEXT MATCH', term: 'sprint', description: 'Has pri in middle of name but not as a word',
- expectIdsContaining: ['103'] },
-
- { category: 'TEXT MATCH', term: 'secondary', description: 'Has secondary in name and/or is secondary',
- expectIdsContaining: ['201', '202', '203', '402'] },
-
- { category: 'TEXT MATCH', term: 'prism', description: 'Has pri as substring',
- expectIdsContaining: ['203'] },
-
- // 4. PARTIAL SEARCHES AND EDGE CASES
- // The 'p' search should match shared guests with primary role AND match 'p' in text
- { category: 'SINGLE CHAR', term: 'p', description: 'Single letter p',
- expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
-
- // Same for 's'
- { category: 'SINGLE CHAR', term: 's', description: 'Single letter s',
- expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
-
- // Test pve prefix in node names
- { category: 'SINGLE CHAR', term: 'pve', description: 'Text in all node IDs',
- expectIdsContaining: ['101', '102', '103', '201', '202', '203', '301', '302', '401', '402'] },
-
- // Test a mix of properties and values
- { category: 'TYPE SEARCH', term: 'qemu', description: 'VM type search',
- expectIdsContaining: ['101', '102', '103', '201', '301', '401', '402'] },
-
- { category: 'TYPE SEARCH', term: 'vm', description: 'VM alternate search',
- expectIdsContaining: ['101', '102', '103', '201', '301', '401', '402'] },
-
- { category: 'TYPE SEARCH', term: 'lxc', description: 'Container type search',
- expectIdsContaining: ['202', '203', '302'] },
-
- // 5. COMBINED SEARCHES (AND LOGIC)
- { category: 'COMBINED', term: 'primary running', description: 'Primary AND running status',
- expectIdsContaining: ['101', '102', '401'] },
-
- { category: 'COMBINED', term: 'sec stopped', description: 'Secondary AND stopped status',
- expectIdsContaining: ['202'] },
-
- // 6. NEGATIVE TESTS - these should NOT match
- { category: 'NEGATIVE', term: 'nonexistent', description: 'Term that appears nowhere',
- expectIdsContaining: [] }
- ];
-
- // Run the tests
- console.log(`Running ${testCases.length} comprehensive search test cases\n`);
-
- let passCount = 0;
- let failCount = 0;
-
- // Keep track of failures by category
- const failures = {};
-
- testCases.forEach((testCase, index) => {
- console.log(`[${index + 1}/${testCases.length}] Testing "${testCase.term}" - ${testCase.description}`);
-
- // Run the search
- const filteredData = getSortedAndFilteredData(
- guests,
- { key: 'name', direction: 'asc' }, // Default sort
- {}, // No filters
- null, // Show all statuses
- [testCase.term], // Single search term as an array
- '', // No active search term
- metricsData,
- 'all', // Show all guest types
- nodeData
- );
-
- // Extract result IDs
- const resultIds = filteredData.map(guest => guest.id);
-
- // Validate expected IDs are included
- const expectedIds = testCase.expectIdsContaining;
- const missingIds = expectedIds.filter(id => !resultIds.includes(id));
- const unexpectedIds = resultIds.filter(id => !expectedIds.includes(id));
-
- const passed = missingIds.length === 0 &&
- (unexpectedIds.length === 0 || expectedIds.length === 0 && resultIds.length === 0);
-
- if (passed) {
- console.log(` ✅ PASSED - Found ${resultIds.length} guests as expected`);
- passCount++;
- } else {
- console.log(` ❌ FAILED`);
- if (missingIds.length > 0) {
- console.log(` Missing IDs: ${missingIds.join(', ')}`);
- }
- if (unexpectedIds.length > 0) {
- console.log(` Unexpected IDs: ${unexpectedIds.join(', ')}`);
- }
-
- // Add to failures by category
- const category = testCase.category;
- if (!failures[category]) {
- failures[category] = [];
- }
- failures[category].push({
- term: testCase.term,
- description: testCase.description,
- expected: expectedIds,
- actual: resultIds,
- missing: missingIds,
- unexpected: unexpectedIds
- });
-
- failCount++;
- }
-
- console.log(''); // Empty line for readability
- });
-
- // Print summary
- console.log('===== TEST SUMMARY =====');
- console.log(`Total tests: ${testCases.length}`);
- console.log(`Passed: ${passCount} (${(passCount/testCases.length*100).toFixed(1)}%)`);
- console.log(`Failed: ${failCount} (${(failCount/testCases.length*100).toFixed(1)}%)`);
-
- // Print failures by category
- if (failCount > 0) {
- console.log('\n===== FAILURES BY CATEGORY =====');
- Object.keys(failures).forEach(category => {
- console.log(`\n${category} - ${failures[category].length} failures:`);
- failures[category].forEach(failure => {
- console.log(` "${failure.term}" - ${failure.description}`);
- console.log(` Expected: ${failure.expected.join(', ')}`);
- console.log(` Actual: ${failure.actual.join(', ')}`);
- });
- });
-
- // Provide advice for fixing issues
- console.log('\n===== TROUBLESHOOTING =====');
-
- // Check specifically for 'pri' issues
- if (failures['ROLE STANDALONE']?.some(f => f.term === 'pri')) {
- console.log('\nIssue detected with "pri" searches:');
- console.log('1. Check if "pri" is being treated as a special case rather than a role indicator');
- console.log('2. Ensure proper handling of "pri" at the beginning of the search logic');
- console.log('3. Make sure word boundaries are properly enforced for "pri" matches');
- }
-
- // Check for single character issues
- if (Object.keys(failures).includes('SINGLE CHAR')) {
- console.log('\nIssue detected with single character searches:');
- console.log('1. Single character searches should perform a full text search across all fields');
- console.log('2. Check that single character logic runs BEFORE role-specific logic');
- }
- }
-
- return {
- totalTests: testCases.length,
- passed: passCount,
- failed: failCount,
- failures
- };
-}
-
-// Run the tests if executed directly
-if (typeof require !== 'undefined' && require.main === module) {
- runExhaustiveTests();
-}
-
-module.exports = { runExhaustiveTests };
\ No newline at end of file
diff --git a/frontend/src/utils/README.md b/frontend/src/utils/README.md
deleted file mode 100644
index 1318b2ce5..000000000
--- a/frontend/src/utils/README.md
+++ /dev/null
@@ -1,57 +0,0 @@
-# Search Implementation
-
-## Formal Specification Based Search
-
-This search implementation follows a formal specification approach to ensure robustness, consistency, and correctness. The search logic has been designed to be systematic and predictable, without relying on special case handling.
-
-### Core Design Principles
-
-1. **Taxonomic Structure**: The search logic follows a clear taxonomy of search operations:
- - Column-specific searches (column:value)
- - Metric comparisons with operators (>, <, =)
- - Standard role terminology (primary, secondary, shared)
- - Type and status keywords (vm, ct, running, stopped)
- - Single character searches (for any text field)
- - Numeric ID exact matches
- - General text search across all fields
-
-2. **No Special Case Handling**: No "priority" or special handling for specific terms like 'pri'. All terms are processed through the same clean pipeline.
-
-3. **Proper Single Character Handling**: Single character searches (like 'p', 's') match any text containing those characters, ensuring consistent behavior.
-
-4. **Predictable Behavior**: The search execution follows a clear, deterministic path without shortcuts or bypasses.
-
-### Search Logic Flow
-
-1. **Column-specific searches**: First check if the term contains a colon (e.g., `role:primary`, `status:running`).
-2. **Metric comparisons**: Check for resource expressions with operators (e.g., `cpu>50`, `memory<80`).
-3. **Standard role terminology**: Match exact role terms like 'primary', 'pri', 'secondary', 'sec'.
-4. **Type and status keywords**: Handle standard VM/container type and status terms.
-5. **Single character searches**: For single character terms, search across all text fields.
-6. **Numeric ID searches**: Handle IDs with special numeric matching rules.
-7. **Full text search**: As a fallback, search all text fields.
-
-### Execution Instructions
-
-Run the test suite to verify search functionality works as expected:
-
-```bash
-node frontend/src/utils/runSearchTests.js
-```
-
-The test suite validates comprehensive search patterns:
-- Basic text searches
-- Role-specific searches ('pri', 'sec', 'primary', 'secondary')
-- Column-based searches ('role:pri', 'type:vm', etc.)
-- Single character searches ('p', 's', 'v', etc.)
-- Combined search terms with AND logic
-
-### Search Term Guidelines
-
-For consistent results, use standard search terms:
-- Role searches: 'primary', 'pri', 'secondary', 'sec'
-- Type searches: 'vm', 'ct', 'container'
-- Status searches: 'running', 'stopped', 'paused', 'suspended'
-- Column-specific: 'role:primary', 'status:running', etc.
-- Metrics: 'cpu>80', 'memory<50', etc.
-- Single characters: Any letter for a text-based search
\ No newline at end of file
diff --git a/frontend/src/utils/VerifyAllSearches.js b/frontend/src/utils/VerifyAllSearches.js
deleted file mode 100644
index e884d47e5..000000000
--- a/frontend/src/utils/VerifyAllSearches.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Comprehensive Search Verification Script
- *
- * This script runs a battery of tests to verify that ALL search patterns work correctly.
- * It combines both standard tests and special test cases for edge conditions.
- */
-
-import { runSearchTests } from './searchTests.js';
-
-console.log('===============================================================');
-console.log('= COMPREHENSIVE SEARCH VERIFICATION =');
-console.log('===============================================================');
-console.log('This script verifies ALL search patterns work correctly, including:');
-console.log('- Basic text searches');
-console.log('- Role-specific searches (pri, sec, primary, secondary)');
-console.log('- Column-based searches (role:pri, type:vm, etc.)');
-console.log('- Single character searches (p, s, v, etc.)');
-console.log('- Edge cases and potentially problematic patterns');
-console.log('\nRunning standard search test suite...');
-
-// Execute all standard tests
-const results = runSearchTests();
-
-// Output summary
-console.log('\n===============================================================');
-console.log('= VERIFICATION RESULTS =');
-console.log('===============================================================');
-
-if (results.general.failed === 0 && results.role.filter(r => !r.passed).length === 0) {
- console.log('✅ ALL TESTS PASSED!');
- console.log(` - ${results.general.passed} general tests passed`);
- console.log(` - ${results.role.filter(r => r.passed).length} role-specific tests passed`);
- console.log('\nThe search functionality is working correctly for all patterns.');
- console.log('Key validations:');
- console.log(' - "pri" correctly returns primary guests');
- console.log(' - "sec" correctly returns secondary guests');
- console.log(' - Single-character searches work properly');
- console.log(' - Combined searches apply AND logic correctly');
-} else {
- console.log('❌ SOME TESTS FAILED!');
- console.log(` - ${results.general.failed} general tests failed`);
- console.log(` - ${results.role.filter(r => !r.passed).length} role-specific tests failed`);
-
- // Show detailed failures
- console.log('\nFailed tests:');
- if (results.general.failed > 0) {
- results.general.details
- .filter(detail => !detail.passed)
- .forEach(detail => {
- console.log(` - ${detail.name}: ${detail.description}`);
- console.log(` Expected: ${JSON.stringify(detail.expectedIds)}`);
- console.log(` Actual: ${JSON.stringify(detail.actualIds)}`);
- });
- }
-
- const failedRoleTests = results.role.filter(r => !r.passed);
- if (failedRoleTests.length > 0) {
- console.log('\nFailed role-specific tests:');
- failedRoleTests.forEach(r => {
- console.log(` - "${r.term}": Expected ${JSON.stringify(r.expectedIds)}, got ${JSON.stringify(r.resultIds)}`);
- });
- }
-}
-
-// Execution instructions
-console.log('\n===============================================================');
-console.log('= HOW TO USE THIS VERIFICATION TOOL =');
-console.log('===============================================================');
-console.log('Run this script after making any changes to the search functionality:');
-console.log(' node frontend/src/utils/VerifyAllSearches.js');
-console.log('\nIf you add new search capabilities, update searchTests.js to include tests');
-console.log('for the new functionality.');
-
-// Exit with appropriate code
-if (results.general.failed === 0 && results.role.filter(r => !r.passed).length === 0) {
- process.exit(0);
-} else {
- process.exit(1);
-}
\ No newline at end of file
diff --git a/frontend/src/utils/basic-search-test.js b/frontend/src/utils/basic-search-test.js
deleted file mode 100644
index e249916b5..000000000
--- a/frontend/src/utils/basic-search-test.js
+++ /dev/null
@@ -1,172 +0,0 @@
-/**
- * Basic Search Test for Role Column
- *
- * This script tests if the search functionality is correctly working with role-based searches.
- * It focuses on testing the most important search terms to ensure core functionality works.
- */
-
-// Mock guest data
-const mockGuests = [
- { id: '101', name: 'web-server', type: 'qemu', status: 'running', node: 'pve1', shared: true, primaryNode: 'pve1', hastate: 'started' },
- { id: '102', name: 'database', type: 'qemu', status: 'running', node: 'pve1', shared: true, primaryNode: 'pve2', hastate: 'stopped' },
- { id: '103', name: 'app-container', type: 'lxc', status: 'running', node: 'pve2', shared: true, primaryNode: 'pve2', hastate: 'started' },
- { id: '104', name: 'backup-storage', type: 'qemu', status: 'stopped', node: 'pve2', shared: false, hastate: undefined },
-];
-
-// Mock node data
-const mockNodeData = [
- { id: 'pve1', name: 'pve-prod-01' },
- { id: 'pve2', name: 'pve-prod-02' },
-];
-
-// Import the search utility - in real implementation this would be from networkUtils
-function mockMatchesTerm(guest, term, nodeData) {
- term = term.toLowerCase();
-
- // Test if this is a role-specific search
- if (term.startsWith('role:')) {
- const roleValue = term.split(':')[1];
-
- if (!roleValue) return true; // Empty value matches all
-
- // Check for primary/secondary role
- if (roleValue === 'primary' || roleValue === 'pri') {
- return guest.shared && guest.primaryNode === guest.node;
- }
-
- if (roleValue === 'secondary' || roleValue === 'sec') {
- return guest.shared && guest.primaryNode !== guest.node;
- }
-
- if (roleValue === '-' || roleValue === 'none') {
- return !guest.shared;
- }
-
- // Default - check if the term appears in relevant fields
- return false;
- }
-
- // For direct role keyword searches
- if (term === 'primary' || term === 'pri') {
- return guest.shared && guest.primaryNode === guest.node;
- }
-
- if (term === 'secondary' || term === 'sec') {
- return guest.shared && guest.primaryNode !== guest.node;
- }
-
- // For combined searches (example: "primary lxc")
- if (term.includes(' ')) {
- const parts = term.split(' ');
- return parts.some(part => mockMatchesTerm(guest, part, nodeData));
- }
-
- // Test for type matches
- if (term === 'vm' || term === 'qemu') {
- return guest.type === 'qemu';
- }
-
- if (term === 'ct' || term === 'lxc' || term === 'container') {
- return guest.type === 'lxc';
- }
-
- // Test for status matches
- if (term === 'running') {
- return guest.status === 'running';
- }
-
- if (term === 'stopped') {
- return guest.status === 'stopped';
- }
-
- // Fallback to simple text match
- return guest.name.toLowerCase().includes(term) ||
- guest.id.toLowerCase().includes(term) ||
- guest.node.toLowerCase().includes(term);
-}
-
-// Run the tests
-function runTests() {
- const tests = [
- {
- name: 'Search for primary guests',
- term: 'primary',
- expected: ['101']
- },
- {
- name: 'Search for secondary guests',
- term: 'secondary',
- expected: ['102']
- },
- {
- name: 'Search for non-shared guests',
- term: 'role:-',
- expected: ['104']
- },
- {
- name: 'Search for primary VMs',
- term: 'primary vm',
- expected: ['101']
- },
- {
- name: 'Search for primary containers',
- term: 'primary ct',
- expected: ['103']
- },
- {
- name: 'Search for secondary running guests',
- term: 'secondary running',
- expected: ['102']
- },
- {
- name: 'Search with role prefix',
- term: 'role:primary',
- expected: ['101', '103']
- },
- {
- name: 'Search with role prefix for secondary',
- term: 'role:secondary',
- expected: ['102']
- }
- ];
-
- let passedTests = 0;
-
- tests.forEach(test => {
- console.log(`Running test: ${test.name}`);
-
- // Filter guests based on the search term
- const results = mockGuests.filter(guest => mockMatchesTerm(guest, test.term, mockNodeData));
-
- // Get the IDs of matching guests
- const resultIds = results.map(guest => guest.id);
-
- // Check if the results match the expected
- const passed = test.expected.length === resultIds.length &&
- test.expected.every(id => resultIds.includes(id));
-
- if (passed) {
- console.log(`✅ PASSED: "${test.term}" - Found ${resultIds.join(', ')}`);
- passedTests++;
- } else {
- console.log(`❌ FAILED: "${test.term}"`);
- console.log(` Expected: ${test.expected.join(', ')}`);
- console.log(` Actual: ${resultIds.join(', ')}`);
- }
- console.log('-----------------------------------');
- });
-
- console.log(`${passedTests} of ${tests.length} tests passed.`);
-
- if (passedTests === tests.length) {
- console.log('✅ ALL TESTS PASSED - Role search functionality is working correctly.');
- } else {
- console.log('❌ SOME TESTS FAILED - Role search functionality needs fixing.');
- }
-}
-
-// Execute the tests
-runTests();
-
-// Export for potential use in other test suites
-module.exports = { mockMatchesTerm, runTests };
\ No newline at end of file
diff --git a/frontend/src/utils/colorUtils.js b/frontend/src/utils/colorUtils.js
deleted file mode 100644
index 413e3d6bb..000000000
--- a/frontend/src/utils/colorUtils.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * Generates a consistent color based on a string input (node name)
- * Returns a color with reduced opacity for use in table row backgrounds
- *
- * @param {string} str - The input string (node name)
- * @param {number} opacity - Opacity value between 0 and 1
- * @param {string} mode - 'dark' or 'light' theme mode
- * @returns {string} - RGBA color string
- */
-export const getNodeColor = (str, opacity = 0.1, mode = 'light') => {
- if (!str) return mode === 'dark' ? 'rgba(255, 255, 255, 0.02)' : 'rgba(0, 0, 0, 0.02)';
-
- // Generate a numeric hash from the string
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
-
- // Different color sets for dark and light modes
- const baseColors = mode === 'dark' ? [
- [130, 177, 255], // Blue
- [255, 145, 143], // Red
- [126, 211, 150], // Green
- [241, 186, 252], // Pink
- [255, 213, 128], // Yellow
- [177, 156, 255], // Purple
- [158, 230, 240], // Cyan
- [255, 169, 119], // Orange
- ] : [
- [25, 118, 210], // Blue
- [211, 47, 47], // Red
- [46, 125, 50], // Green
- [194, 24, 91], // Pink
- [255, 145, 0], // Orange
- [123, 31, 162], // Purple
- [0, 131, 143], // Teal
- [109, 76, 65] // Brown
- ];
-
- // Use the hash to pick a color from the baseColors
- const colorIndex = Math.abs(hash) % baseColors.length;
- const [r, g, b] = baseColors[colorIndex];
-
- // Return rgba color string with appropriate opacity
- return `rgba(${r}, ${g}, ${b}, ${opacity})`;
-};
-
-/**
- * Generates a consistent text color based on a string input (node name)
- *
- * @param {string} str - The input string (node name)
- * @param {string} mode - 'dark' or 'light' theme mode
- * @returns {string} - RGB color string
- */
-export const getNodeTextColor = (str, mode = 'light') => {
- if (!str) return mode === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(0, 0, 0, 0.7)';
-
- // Generate a numeric hash from the string
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
-
- // Different color sets for dark and light modes
- const baseColors = mode === 'dark' ? [
- [130, 177, 255], // Blue
- [255, 145, 143], // Red
- [126, 211, 150], // Green
- [241, 186, 252], // Pink
- [255, 213, 128], // Yellow
- [177, 156, 255], // Purple
- [158, 230, 240], // Cyan
- [255, 169, 119], // Orange
- ] : [
- [25, 118, 210], // Blue
- [211, 47, 47], // Red
- [46, 125, 50], // Green
- [194, 24, 91], // Pink
- [255, 145, 0], // Orange
- [123, 31, 162], // Purple
- [0, 131, 143], // Teal
- [109, 76, 65] // Brown
- ];
-
- // Use the hash to pick a color from the baseColors
- const colorIndex = Math.abs(hash) % baseColors.length;
- const [r, g, b] = baseColors[colorIndex];
-
- // Return rgb color string
- return `rgb(${r}, ${g}, ${b})`;
-};
\ No newline at end of file
diff --git a/frontend/src/utils/debugSearch.js b/frontend/src/utils/debugSearch.js
deleted file mode 100644
index e97dd999e..000000000
--- a/frontend/src/utils/debugSearch.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// Debug script for testing 'pri' search issue
-
-// Mock implementation of matchesTerm function for testing
-function matchesTerm(guest, termLower, nodeData) {
- // Prevent operations on undefined/null terms
- if (!termLower) return true;
-
- // CASE 3: Standard role terminology
- // Matches exact role terms (not as part of other words) using standard terminology
- if (termLower === 'shared' || termLower === 'role') {
- return !!guest.shared;
- }
-
- // Primary role terms: match whole words only, requires shared=true and isPrimary=true
- if (termLower === 'primary' || termLower === 'pri') {
- if (!guest.shared) return false;
- console.log(`Checking primary for ${guest.id}: primaryNode=${guest.primaryNode}, node=${guest.node}, matches=${guest.primaryNode === guest.node}`);
- return guest.primaryNode === guest.node;
- }
-
- // Secondary role terms: match whole words only, requires shared=true and isPrimary=false
- if (termLower === 'secondary' || termLower === 'sec') {
- if (!guest.shared) return false;
- return guest.primaryNode !== guest.node;
- }
-
- // CASE 5: Single character searches (including role abbreviations)
- if (termLower.length === 1) {
- const searchText = getFullSearchableText(guest, nodeData);
- console.log(`Single-char search for ${termLower} in ${guest.id}: "${searchText}"`);
- return searchText.includes(termLower);
- }
-
- // Default: search in all text fields
- const searchText = getFullSearchableText(guest, nodeData);
- console.log(`Full search for ${termLower} in ${guest.id}: "${searchText}"`);
- return searchText.includes(termLower);
-}
-
-// Improved function to get complete searchable text for a guest
-function getFullSearchableText(guest, nodeData) {
- // Include ALL searchable properties
- const nodeName = guest.node || '';
-
- // Build full searchable text by concatenating ALL searchable fields
- const searchableFields = [
- guest.name || '',
- guest.id || '',
- guest.status || '',
- // Add VM/CT descriptive terms
- guest.type === 'qemu' ? 'vm virtual machine' : 'ct container',
- nodeName,
- // Add role descriptive terms if shared
- guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
- // Add shared indicator if applicable
- guest.shared ? 'shared role' : 'none',
- // Any other custom properties that should be searchable
- guest.description || '',
- guest.tags || ''
- ];
-
- // Join all fields with spaces and convert to lowercase
- const fullSearchText = searchableFields.join(' ').toLowerCase();
- return fullSearchText;
-}
-
-// Mock test data - simplified version of actual app data
-const testGuests = [
- {
- id: '101',
- name: 'web-server',
- status: 'running',
- type: 'qemu',
- node: 'pve-prod-01',
- shared: true,
- primaryNode: 'pve-prod-01' // This is primary on this node
- },
- {
- id: '102',
- name: 'database',
- status: 'running',
- type: 'qemu',
- node: 'pve-prod-01',
- shared: true,
- primaryNode: 'pve-prod-01' // This is primary on this node
- },
- {
- id: '201',
- name: 'cache-server',
- status: 'stopped',
- type: 'qemu',
- node: 'pve-prod-02',
- shared: true,
- primaryNode: 'pve-prod-01' // This is secondary on this node
- }
-];
-
-// Mock node data
-const nodeData = [
- { id: 'pve-prod-01', name: 'Production Node 1' },
- { id: 'pve-prod-02', name: 'Production Node 2' }
-];
-
-// Test specific search terms directly
-function testSearch(searchTerm) {
- console.log(`\n===== TESTING SEARCH TERM: "${searchTerm}" =====`);
-
- const result = testGuests.filter(guest => {
- const matches = matchesTerm(guest, searchTerm.toLowerCase(), nodeData);
- console.log(` Guest ${guest.id} (${guest.name}) matches ${searchTerm}? ${matches ? 'YES' : 'NO'}`);
- return matches;
- });
-
- console.log(`\nResults for "${searchTerm}":`);
- console.log(` Found ${result.length} guests:`);
-
- if (result.length === 0) {
- console.log(" NO RESULTS FOUND!");
- } else {
- result.forEach(guest => {
- console.log(` - ${guest.id}: ${guest.name} (${guest.node}, shared=${guest.shared}, primaryNode=${guest.primaryNode})`);
- console.log(` Primary? ${guest.primaryNode === guest.node ? 'YES' : 'NO'}`);
- });
- }
-
- // Print expected vs actual for primaries
- const expectedPrimaries = testGuests.filter(g => g.shared && g.primaryNode === g.node);
- console.log(`\nExpected primaries: ${expectedPrimaries.length} guests`);
- expectedPrimaries.forEach(g => console.log(` - ${g.id}: ${g.name}`));
-
- // Verify if all primaries were found
- const allPrimariesFound = expectedPrimaries.every(
- expected => result.some(res => res.id === expected.id)
- );
-
- console.log(`\nAll primaries found? ${allPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
-
- if (!allPrimariesFound) {
- console.log("Missing primaries:");
- expectedPrimaries.forEach(expected => {
- if (!result.some(res => res.id === expected.id)) {
- console.log(` - ${expected.id}: ${expected.name}`);
- }
- });
- }
-}
-
-// Test various forms of the primary search
-console.log("\n********** DEBUGGING 'PRI' SEARCH ISSUE **********");
-testSearch('pri');
-testSearch('primary');
-testSearch('p'); // Single character test
-
-// Test role-prefixed searches to check if they work differently
-console.log("\n********** TESTING PREFIXED SEARCHES **********");
-testSearch('role:pri');
-testSearch('role:primary');
-
-console.log("\n\n********** SEARCH IMPLEMENTATION DETAILS **********");
-// Print the relevant implementation from networkUtils.js
-console.log(`
-Search logic for 'pri' is implemented in networkUtils.js:
-
-// Standard role terminology
-if (termLower === 'primary' || termLower === 'pri') {
- if (!guest.shared) return false;
- return guest.primaryNode === guest.node;
-}
-
-and in getFullSearchableText:
-
-guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
-`);
\ No newline at end of file
diff --git a/frontend/src/utils/formatters.js b/frontend/src/utils/formatters.js
deleted file mode 100644
index a7c40cc65..000000000
--- a/frontend/src/utils/formatters.js
+++ /dev/null
@@ -1,130 +0,0 @@
-// Helper function to format bytes
-export const formatBytes = (bytes, decimals = 2) => {
- if (bytes === undefined || bytes === null || isNaN(bytes) || bytes === 0) return '0 B';
-
- const k = 1024;
- const dm = 0; // No decimals
- const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-
- // Ensure bytes is a positive number
- bytes = Math.abs(bytes);
-
- // Calculate the appropriate size index
- const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
-
- // Ensure i is within bounds of the sizes array
- if (i < 0 || i >= sizes.length) {
- console.error(`Invalid size index: ${i} for bytes: ${bytes}`);
- return `${bytes} B`; // Fallback to bytes with B unit
- }
-
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
-};
-
-/**
- * Format network rate with unit information
- * @param {number} bytesPerSecond - The network rate in bytes per second
- * @param {string} unit - Optional unit information ('bytes', 'KB', 'MB', 'GB')
- * @returns {string} Formatted string with appropriate unit
- */
-export const formatNetworkRate = (bytesPerSecond, unit = 'bytes') => {
- if (bytesPerSecond === null || bytesPerSecond === undefined) {
- return 'N/A';
- }
-
- // If the unit is specified, convert the value back to bytes for consistent formatting
- let bytesValue = bytesPerSecond;
- if (unit === 'KB') {
- bytesValue = bytesPerSecond * 1024;
- } else if (unit === 'MB') {
- bytesValue = bytesPerSecond * 1024 * 1024;
- } else if (unit === 'GB') {
- bytesValue = bytesPerSecond * 1024 * 1024 * 1024;
- }
-
- const units = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
- let value = bytesValue;
- let unitIndex = 0;
-
- while (value >= 1024 && unitIndex < units.length - 1) {
- value /= 1024;
- unitIndex++;
- }
-
- // Format with appropriate precision
- return value < 10 ?
- `${value.toFixed(1)} ${units[unitIndex]}` :
- `${Math.round(value)} ${units[unitIndex]}`;
-};
-
-// Helper function to format percentage
-export const formatPercentage = (value) => {
- if (value === undefined || value === null || isNaN(value)) return '0%';
- return `${Math.round(value)}%`;
-};
-
-// Helper function to format uptime duration
-export const formatUptime = (seconds) => {
- if (seconds === undefined || seconds === null || isNaN(seconds) || seconds === 0) return '-';
-
- const days = Math.floor(seconds / 86400);
- const hours = Math.floor((seconds % 86400) / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
-
- if (days > 0) {
- return `${days}d ${hours}h`;
- } else if (hours > 0) {
- return `${hours}h ${minutes}m`;
- } else {
- return `${minutes}m`;
- }
-};
-
-// Helper function to format network rates for filter display
-export const formatNetworkRateForFilter = (bytesPerSecond) => {
- if (bytesPerSecond === undefined || bytesPerSecond === null || isNaN(bytesPerSecond) || bytesPerSecond === 0) return '0 B/s';
-
- // Simplified format for filter display
- const kb = bytesPerSecond / 1024;
- if (kb < 1000) {
- return `${Math.round(kb)} KB/s`;
- } else {
- return `${Math.round(kb/1024)} MB/s`;
- }
-};
-
-// Convert slider value (0-100) to actual bytes per second
-export const sliderValueToNetworkRate = (value) => {
- // Max realistic rate for filter: ~10 MB/s = 10485760 B/s
- return value * 104858; // This gives us a range from 0 to ~10 MB/s
-};
-
-// Convert network rate to slider value (0-100)
-export const networkRateToSliderValue = (bytesPerSecond) => {
- return Math.min(100, Math.round(bytesPerSecond / 104858));
-};
-
-/**
- * Format bytes with unit information
- * @param {number} bytes - The bytes value to format
- * @param {string} unit - Optional unit information ('bytes', 'KB', 'MB', 'GB')
- * @returns {string} Formatted string with appropriate unit
- */
-export const formatBytesWithUnit = (bytes, unit = 'bytes') => {
- if (bytes === null || bytes === undefined) {
- return 'N/A';
- }
-
- // If the unit is specified, convert the value back to bytes for consistent formatting
- let bytesValue = bytes;
- if (unit === 'KB') {
- bytesValue = bytes * 1024;
- } else if (unit === 'MB') {
- bytesValue = bytes * 1024 * 1024;
- } else if (unit === 'GB') {
- bytesValue = bytes * 1024 * 1024 * 1024;
- }
-
- // Now format using the standard formatter
- return formatBytes(bytesValue);
-};
\ No newline at end of file
diff --git a/frontend/src/utils/networkUtils.js b/frontend/src/utils/networkUtils.js
deleted file mode 100644
index 0aac28d34..000000000
--- a/frontend/src/utils/networkUtils.js
+++ /dev/null
@@ -1,1325 +0,0 @@
-import { sliderValueToNetworkRate } from './formatters';
-
-// Helper function to extract numeric ID from Proxmox-style IDs
-export const extractNumericId = (fullId) => {
- if (!fullId) return '';
-
- // Handle Proxmox-style IDs like "qemu/105" or "lxc/201"
- if (fullId.includes('/')) {
- const parts = fullId.split('/');
- if (parts.length > 1) {
- // Handle node-specific IDs like "qemu/105:node-1"
- const idPart = parts[1].split(':')[0];
- return idPart;
- }
- }
-
- // Fallback to the old method if not a Proxmox-style ID
- const match = fullId.match(/(\d+)$/);
- if (match && match[1]) {
- return match[1];
- }
-
- return fullId;
-};
-
-// Helper function to get the node name from the node ID
-export const getNodeName = (nodeId, nodeData) => {
- if (!nodeId || !nodeData || nodeData.length === 0) return nodeId;
-
- // Find the node in the nodeData array
- const node = nodeData.find(node => node.id === nodeId);
-
- // Return the node name if found, otherwise return the node ID
- return node ? node.name : nodeId;
-};
-
-// Function to get metrics for a specific guest
-export const getMetricsForGuest = (guestId, metricsData) => {
- if (!metricsData || !guestId) return null;
-
- return {
- cpu: metricsData.cpu?.[guestId] || null,
- memory: metricsData.memory?.[guestId] || null,
- disk: metricsData.disk?.[guestId] || null,
- network: metricsData.network?.[guestId] || null
- };
-};
-
-// Function to filter guests based on selected node
-export const getNodeFilteredGuests = (guests, selectedNode) => {
- if (!guests || !Array.isArray(guests)) {
- console.warn('getNodeFilteredGuests: guests is not an array', guests);
- return [];
- }
-
- if (selectedNode === 'all') {
- return guests;
- }
-
- console.log(`🔎 FILTERING for node: "${selectedNode}"`);
-
- // Count before filtering
- console.log(`🔎 Before filtering: ${guests.length} total guests`);
-
- // Log distribution before filtering
- const rawNodeCounts = {};
- guests.forEach(guest => {
- const nodeId = guest.node;
- rawNodeCounts[nodeId] = (rawNodeCounts[nodeId] || 0) + 1;
- });
-
- console.log("🔎 Raw node distribution:");
- Object.keys(rawNodeCounts).sort().forEach(nodeId => {
- console.log(` - "${nodeId}": ${rawNodeCounts[nodeId]} guests`);
- });
-
- // Filter guests based on the node property from the API
- const filteredGuests = guests.filter(guest => {
- // Extract the node ID from the guest's node property
- const nodeIdFromApi = guest.node;
-
- // If the node property doesn't exist, exclude the guest (previously included)
- if (!nodeIdFromApi) {
- console.log('Guest has no node property - excluding:', guest.id, guest.name);
- return false;
- }
-
- // Just do a direct match - no translation needed
- const isMatching = nodeIdFromApi === selectedNode;
-
- // Log non-matching guests for our problem nodes
- if (!isMatching && (selectedNode === 'pve-prod-01' || selectedNode === 'pve-prod-02')) {
- console.log(`❌ Guest ${guest.id} (${guest.name}) NOT matching "${selectedNode}", has node="${nodeIdFromApi}"`);
- }
-
- if (isMatching) {
- console.log(`✓ Guest ${guest.id} (${guest.name}) matches node ${selectedNode}`);
- }
-
- return isMatching;
- });
-
- // Count after filtering
- console.log(`🔎 After filtering for node ${selectedNode}: ${filteredGuests.length} guests`);
-
- // Add additional debug to show the different nodes found
- const nodesInResult = [...new Set(filteredGuests.map(g => g.node))];
- console.log('🔎 Nodes in filtered result:', nodesInResult);
-
- return filteredGuests;
-};
-
-// Global variable to keep track of active search terms for specific test cases
-let _activeSearchTermsForTests = [];
-
-// Function to set active search terms for testing
-export const setActiveSearchTermsForTests = (terms) => {
- _activeSearchTermsForTests = terms || [];
-};
-
-// For the specific test cases that are hard to handle generically
-let _guestBeingMatched = null;
-let _currentActiveTerms = [];
-
-// Test detection helper - automatically updated when a test runs
-// This should be set in the runSystematicSearchTests.js file
-if (typeof window !== 'undefined') {
- window.__CURRENT_TEST_NAME = '';
-} else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST_NAME = '';
-}
-
-// Store the current test name if available (for test-specific handling)
-function getCurrentTestName() {
- if (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME) {
- return window.__CURRENT_TEST_NAME;
- } else if (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME) {
- return global.__CURRENT_TEST_NAME;
- }
- return '';
-}
-
-// Function to check if we're in a single character test
-function isInSingleCharTest() {
- const testName = getCurrentTestName();
- return testName && (
- testName.includes('single character') ||
- testName.includes("Find guests with 'p' in any field") ||
- testName.includes("Find guests with 's' in any field") ||
- testName.includes("Find guests with 'c' in any field") ||
- testName.includes("Find guests with 'v' in any field")
- );
-}
-
-/**
- * Apply search terms to filter data
- *
- * Implements a straightforward text matching approach:
- * - Multiple terms use AND logic (all terms must match)
- * - Space-separated terms are treated as separate terms (AND logic)
- * - Column-specific searches use format "column:value"
- * - Single character searches match any text containing that character
- * - For numeric single characters, matches by ID prefix
- *
- * Special handling exists for test compatibility.
- */
-function applySearchTerms(data, terms, nodeData, metricsData) {
- if (!terms || terms.length === 0) return data;
-
- // Store the current test name for special case handling
- const currentTestName = getCurrentTestName();
- if (typeof window !== 'undefined') {
- window.__CURRENT_TEST_NAME = currentTestName;
- } else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST_NAME = currentTestName;
- }
-
- // Update active terms for special test case handling
- _currentActiveTerms = terms;
-
- // Special case for SPECIFIC TEST FAILURES - must be handled outside normal flow
-
- // Case 1: "Find primary container guests" - ["primary","lxc"]
- if (terms.length === 2 &&
- terms.some(t => t.toLowerCase() === 'primary') &&
- terms.some(t => t.toLowerCase() === 'lxc')) {
- return data.filter(guest => guest.id === '103');
- }
-
- // Case 2: "Running primary prod guests" - ["prod","role:primary","running"]
- if (terms.length === 3 &&
- terms.some(t => t.toLowerCase() === 'prod') &&
- terms.some(t => t.toLowerCase().includes('primary')) &&
- terms.some(t => t.toLowerCase() === 'running')) {
- return data.filter(guest => ['101', '102', '103'].includes(guest.id));
- }
-
- // Special case for single character tests
- const isSingleCharTest =
- terms.length === 1 && terms[0].length === 1 && isInSingleCharTest();
-
- if (isSingleCharTest) {
- const char = terms[0];
-
- // Use the predefined expected results for single character tests
- if (char === 'p') {
- return data.filter(guest =>
- ['101', '102', '103', '301', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === 's') {
- return data.filter(guest =>
- ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === 'c') {
- return data.filter(guest =>
- ['103', '301', '302', '401', '402', '501', '601'].includes(guest.id)
- );
- } else if (char === 'v') {
- return data.filter(guest =>
- ['101', '102', '201', '202', '301', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === '1') {
- return data.filter(guest =>
- ['101', '102', '103'].includes(guest.id)
- );
- }
- }
-
- // SPECIAL CASE FOR TEST: "Find stopped VMs"
- // Check if this is the "vm" + "stopped" test case
- const hasVmTerm = terms.some(t => t.toLowerCase() === 'vm');
- const hasStoppedTerm = terms.some(t => t.toLowerCase() === 'stopped');
-
- if (hasVmTerm && hasStoppedTerm) {
- // Special handling to make sure guest ID 302 is included in results
- return data.filter(guest => {
- // Always include test-container with ID 302 in this case
- if (guest.id === '302') return true;
-
- // Normal filtering for other guests
- return terms.every(term => {
- const termLower = term.toLowerCase().trim();
- if (!termLower) return true;
- return matchesTerm(guest, termLower, nodeData, metricsData);
- });
- });
- }
-
- // Normal case for other searches
- return data.filter(guest => {
- // Check each search term - apply AND logic between terms
- return terms.every(term => {
- // Normalize term - lowercase and trim whitespace from both ends
- const termLower = term.toLowerCase().trim();
-
- // Empty term matches everything
- if (!termLower) return true;
-
- // Check if this is a partial metric term with an operator, like "cp>"
- const hasOperator = termLower.includes('>') || termLower.includes('<') || termLower.includes('=');
- const partialResourceRegex = /^(c|cp|cpu|m|me|mem|memo|memor|memory|d|di|dis|disk|n|ne|net|netw|netwo|networ|network)\s*([<>]=?|=)/i;
-
- if (hasOperator && partialResourceRegex.test(termLower)) {
- console.log(`Partial resource term with operator detected: "${termLower}"`);
- // Treat it like a metric expression to maintain column highlighting
- return matchesTerm(guest, termLower, nodeData, metricsData);
- }
-
- // Check if this is a spaced metric expression like "cpu > 50"
- const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
- if (spacedExpressionRegex.test(termLower)) {
- // Handle it as a single expression, not as space-separated terms
- console.log(`Processing spaced metric expression: "${termLower}"`);
- const match = termLower.match(spacedExpressionRegex);
- if (match) {
- // Log the resource being highlighted
- console.log(`Highlighting column for resource: ${match[1]}`);
- }
- return matchesTerm(guest, termLower, nodeData, metricsData);
- }
-
- // Handle space-separated terms (for non-metric expressions)
- // Apply OR logic for space-separated terms (changed from AND)
- const spaceTerms = termLower.split(' ').map(t => t.trim()).filter(t => t);
- if (spaceTerms.length > 1) {
- // For OR search, at least one term must match
- return spaceTerms.some(spaceTerm => {
- return matchesTerm(guest, spaceTerm, nodeData, metricsData);
- });
- }
-
- // Handle OR search with pipe character
- if (termLower.includes('|')) {
- const orTerms = termLower.split('|').map(t => t.trim()).filter(t => t);
- // For OR search, at least one term must match
- return orTerms.some(orTerm => {
- return matchesTerm(guest, orTerm, nodeData, metricsData);
- });
- }
-
- // Regular term matching (single term)
- console.log(`Processing term: "${termLower}"`);
- return matchesTerm(guest, termLower, nodeData, metricsData);
- });
- });
-}
-
-/**
- * Special case handling for test scenarios
- * This function exists primarily to ensure test compatibility
- * with our predefined expected outcomes
- */
-function handleSpecialTestCases(guest, termLower, activeTerms) {
- try {
- // Single character test detection
- const inSingleCharTest =
- (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME &&
- (window.__CURRENT_TEST_NAME.includes('single character') ||
- window.__CURRENT_TEST_NAME.includes("Find guests with 'p' in any field") ||
- window.__CURRENT_TEST_NAME.includes("Find guests with 's' in any field"))) ||
- (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME &&
- (global.__CURRENT_TEST_NAME.includes('single character') ||
- global.__CURRENT_TEST_NAME.includes("Find guests with 'p' in any field") ||
- global.__CURRENT_TEST_NAME.includes("Find guests with 's' in any field"))) ||
- (_currentActiveTerms && _currentActiveTerms.length === 1 && _currentActiveTerms[0].length === 1);
-
- // Handle specific test cases by term
- if (termLower === 'p' && inSingleCharTest) {
- return ['101', '102', '103', '301', '401', '501', '601', '701'].includes(guest.id);
- }
-
- if (termLower === 's' && inSingleCharTest) {
- return ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'].includes(guest.id);
- }
-
- if (termLower === 'c' && inSingleCharTest) {
- return ['103', '301', '302', '401', '402', '501', '601'].includes(guest.id);
- }
-
- if (termLower === 'v' && inSingleCharTest) {
- return ['101', '102', '201', '202', '301', '401', '501', '601', '701'].includes(guest.id);
- }
-
- // Hard-code specific expected test results
-
- // 1. VM keyword test should not include container test-302
- if (termLower === 'vm' && guest.id === '302') {
- // Exception for the "Find stopped VMs" test case
- if (isPartOfMultipleTermTest(['vm', 'stopped'])) {
- return true;
- }
- return false;
- }
-
- // 2. "Running primary prod guests" test case
- if ((termLower === 'prod' || termLower === 'running' || termLower === 'primary') &&
- isPartOfMultipleTermTest(['prod', 'running', 'primary']) &&
- guest.id === '701') {
- return false;
- }
-
- // Special handling for "Find primary container guests" test case
- if ((termLower === 'primary' || termLower === 'lxc' || termLower === 'container') &&
- guest.id === '103' &&
- (isPartOfMultipleTermTest(['primary', 'lxc']) || isPartOfMultipleTermTest(['primary', 'container']))) {
- return true;
- }
-
- // Special handling for "Find stopped VMs" test case
- if ((termLower === 'vm' || termLower === 'stopped') &&
- guest.id === '302' &&
- isPartOfMultipleTermTest(['vm', 'stopped'])) {
- return true;
- }
-
- return false;
- } catch (e) {
- return false;
- }
-}
-
-/**
- * Check if a term is part of a multiple term test scenario
- * Used for test compatibility only
- */
-function isPartOfMultipleTermTest(requiredTerms) {
- if (!_currentActiveTerms || _currentActiveTerms.length === 0) return false;
-
- // Convert everything to lowercase for case-insensitive comparison
- const lowerActiveTerms = _currentActiveTerms.map(t => t.toLowerCase());
-
- // Check if all the required terms are in the active terms
- return requiredTerms.every(term => {
- // Check for exact matches or partial matches like 'role:primary' containing 'primary'
- return lowerActiveTerms.some(activeTerm =>
- activeTerm === term.toLowerCase() ||
- activeTerm.includes(':' + term.toLowerCase()) ||
- activeTerm.includes(term.toLowerCase())
- );
- });
-}
-
-/**
- * Function to sort and filter data based on various parameters
- *
- * This function applies several layers of filtering:
- * 1. Filter by guest type (VM or container) if specified
- * 2. Filter by running status based on showStopped flag
- * 3. Apply search terms using simple text matching
- * 4. Apply metric filters for CPU, memory, disk, etc.
- * 5. Sort the results based on sortConfig
- *
- * The search implementation uses a straightforward approach where:
- * - Multiple terms use AND logic (all must match)
- * - Terms are matched against a comprehensive text representation of each guest
- * - Column-specific searches and special operators are supported
- * - Single character searches match any content containing that character
- *
- * @param {Array} data - The list of guests to filter and sort
- * @param {Object} sortConfig - Configuration for sorting (key and direction)
- * @param {Object} filters - Metric filters (cpu, memory, disk, etc.)
- * @param {Boolean|null} showStopped - Whether to show only stopped/running guests
- * @param {Array} activeSearchTerms - List of search terms
- * @param {String} searchTerm - Additional search term
- * @param {Object} metricsData - Performance metrics for guests
- * @param {String} guestTypeFilter - Filter for guest type (all, vm, container)
- * @param {Array} nodeData - Node information
- * @returns {Array} - Filtered and sorted guest list
- */
-export const getSortedAndFilteredData = (
- data,
- sortConfig,
- filters,
- showStopped,
- activeSearchTerms,
- searchTerm,
- metricsData,
- guestTypeFilter,
- nodeData
-) => {
- if (!data || !Array.isArray(data) || data.length === 0) {
- return [];
- }
-
- // Filter by guest type if specified
- let filteredData = [...data];
- if (guestTypeFilter !== 'all') {
- const isVM = guestTypeFilter === 'vm';
- filteredData = filteredData.filter(guest =>
- isVM ? guest.type === 'qemu' : guest.type === 'lxc'
- );
- }
-
- // Filter by running status based on showStopped flag
- // When showStopped is null, show all systems (no filtering)
- // When showStopped is false, show only running systems
- // When showStopped is true, show only stopped systems
- if (showStopped !== null) {
- if (showStopped) {
- filteredData = filteredData.filter(guest => guest.status.toLowerCase() !== 'running');
- } else {
- filteredData = filteredData.filter(guest => guest.status.toLowerCase() === 'running');
- }
- }
-
- // Apply search terms
- if (activeSearchTerms.length > 0 || searchTerm) {
- const terms = [...activeSearchTerms];
- if (searchTerm && !terms.includes(searchTerm)) {
- terms.push(searchTerm);
- }
-
- if (terms.length > 0) {
- filteredData = applySearchTerms(filteredData, terms, nodeData, metricsData);
- }
- }
-
- // Apply metric filters
- if (filters) {
- // CPU filter
- if (filters.cpu > 0) {
- filteredData = filteredData.filter(guest => {
- const metrics = metricsData?.cpu?.[guest.id];
- return metrics && metrics.usage >= filters.cpu;
- });
- }
-
- // Memory filter
- if (filters.memory > 0) {
- filteredData = filteredData.filter(guest => {
- const metrics = metricsData?.memory?.[guest.id];
- return metrics && metrics.usagePercent >= filters.memory;
- });
- }
-
- // Disk filter
- if (filters.disk > 0) {
- filteredData = filteredData.filter(guest => {
- const metrics = metricsData?.disk?.[guest.id];
- return metrics && metrics.usagePercent >= filters.disk;
- });
- }
-
- // Download filter
- if (filters.download > 0) {
- const bytesPerSecondThreshold = sliderValueToNetworkRate(filters.download);
- filteredData = filteredData.filter(guest => {
- const metrics = metricsData?.network?.[guest.id];
- return metrics && metrics.inRate >= bytesPerSecondThreshold;
- });
- }
-
- // Upload filter
- if (filters.upload > 0) {
- const bytesPerSecondThreshold = sliderValueToNetworkRate(filters.upload);
- filteredData = filteredData.filter(guest => {
- const metrics = metricsData?.network?.[guest.id];
- return metrics && metrics.outRate >= bytesPerSecondThreshold;
- });
- }
- }
-
- // Apply sorting
- if (sortConfig) {
- // Debug logging for sorting
- console.log('Applying sort:', sortConfig, 'to', filteredData.length, 'items');
- console.log('Has metrics data?', metricsData ? 'Yes' : 'No',
- metricsData ? {
- cpu: Object.keys(metricsData.cpu || {}).length,
- memory: Object.keys(metricsData.memory || {}).length,
- disk: Object.keys(metricsData.disk || {}).length,
- network: Object.keys(metricsData.network || {}).length
- } : 'None');
-
- filteredData.sort((a, b) => {
- // Handle special cases for metrics-based sorting
- if (sortConfig.key === 'cpu') {
- const aMetrics = metricsData?.cpu?.[a.id];
- const bMetrics = metricsData?.cpu?.[b.id];
- const aValue = aMetrics ? aMetrics.usage : 0;
- const bValue = bMetrics ? bMetrics.usage : 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- if (sortConfig.key === 'memory') {
- const aMetrics = metricsData?.memory?.[a.id];
- const bMetrics = metricsData?.memory?.[b.id];
- const aValue = aMetrics ? aMetrics.usagePercent : 0;
- const bValue = bMetrics ? bMetrics.usagePercent : 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- if (sortConfig.key === 'disk') {
- const aMetrics = metricsData?.disk?.[a.id];
- const bMetrics = metricsData?.disk?.[b.id];
- const aValue = aMetrics ? aMetrics.usagePercent : 0;
- const bValue = bMetrics ? bMetrics.usagePercent : 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- if (sortConfig.key === 'download') {
- const aMetrics = metricsData?.network?.[a.id];
- const bMetrics = metricsData?.network?.[b.id];
- const aValue = aMetrics ? aMetrics.inRate : 0;
- const bValue = bMetrics ? bMetrics.inRate : 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- if (sortConfig.key === 'upload') {
- const aMetrics = metricsData?.network?.[a.id];
- const bMetrics = metricsData?.network?.[b.id];
- const aValue = aMetrics ? aMetrics.outRate : 0;
- const bValue = bMetrics ? bMetrics.outRate : 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- if (sortConfig.key === 'uptime') {
- const aValue = a.uptime || 0;
- const bValue = b.uptime || 0;
- return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
- }
-
- // For string-based properties
- if (sortConfig.key === 'id') {
- // Extract numeric part for better sorting
- const aId = extractNumericId(a.id);
- const bId = extractNumericId(b.id);
-
- // Try to convert to numbers for numeric sorting
- const aNum = parseInt(aId, 10);
- const bNum = parseInt(bId, 10);
-
- if (!isNaN(aNum) && !isNaN(bNum)) {
- return sortConfig.direction === 'asc' ? aNum - bNum : bNum - aNum;
- }
-
- // Fallback to string comparison
- return sortConfig.direction === 'asc'
- ? aId.localeCompare(bId)
- : bId.localeCompare(aId);
- }
-
- // Special case for status column (running status should be first)
- if (sortConfig.key === 'status') {
- // Normalize status values for consistent sorting
- const statusPriority = {
- 'running': 1,
- 'paused': 2,
- 'suspended': 3,
- 'stopped': 4
- };
-
- const aStatus = (a.status || '').toLowerCase();
- const bStatus = (b.status || '').toLowerCase();
-
- // Get priority or default to highest number (end of sort)
- const aPriority = statusPriority[aStatus] || 999;
- const bPriority = statusPriority[bStatus] || 999;
-
- // Sort by priority number
- if (sortConfig.direction === 'asc') {
- return aPriority - bPriority;
- } else {
- return bPriority - aPriority;
- }
- }
-
- // Special case for type column (normalize qemu/lxc to vm/ct)
- if (sortConfig.key === 'type') {
- // Normalize type values to boolean (true for VM, false for CT)
- const getTypeValue = (type) => {
- const typeStr = (type || '').toLowerCase();
- return typeStr === 'qemu';
- };
-
- const aIsVM = getTypeValue(a.type);
- const bIsVM = getTypeValue(b.type);
-
- // Simple boolean comparison
- return sortConfig.direction === 'asc'
- ? (aIsVM === bIsVM ? 0 : aIsVM ? 1 : -1)
- : (aIsVM === bIsVM ? 0 : aIsVM ? -1 : 1);
- }
-
- // Special case for role column
- if (sortConfig.key === 'role') {
- // Primary nodes first, then secondary, then non-shared
- const aIsShared = !!a.shared;
- const bIsShared = !!b.shared;
-
- // If one is shared and the other isn't, prioritize the shared one
- if (aIsShared !== bIsShared) {
- return sortConfig.direction === 'asc'
- ? (aIsShared ? -1 : 1)
- : (aIsShared ? 1 : -1);
- }
-
- // If both are shared, compare primary vs secondary
- if (aIsShared && bIsShared) {
- const aIsPrimary = a.primaryNode === a.node;
- const bIsPrimary = b.primaryNode === b.node;
-
- if (aIsPrimary !== bIsPrimary) {
- return sortConfig.direction === 'asc'
- ? (aIsPrimary ? -1 : 1)
- : (aIsPrimary ? 1 : -1);
- }
- }
-
- // Fall back to name if role status is the same
- return sortConfig.direction === 'asc'
- ? String(a.name || '').localeCompare(String(b.name || ''))
- : String(b.name || '').localeCompare(String(a.name || ''));
- }
-
- // Default string comparison for other fields
- const aValue = a[sortConfig.key] || '';
- const bValue = b[sortConfig.key] || '';
-
- return sortConfig.direction === 'asc'
- ? String(aValue).localeCompare(String(bValue))
- : String(bValue).localeCompare(String(aValue));
- });
- }
-
- return filteredData;
-};
-
-/**
- * Calculate dynamic column widths based on visible columns
- * @param {Object} columnVisibility - Object containing column visibility state
- * @returns {Object} - Object with column IDs as keys and pixel values as values
- */
-export const calculateDynamicColumnWidths = (columnVisibility) => {
- const defaultWidths = {
- node: 140, // Moderate - node names
- role: 100, // Small - just PRIMARY/SECONDARY chip
- type: 30, // Very small - just "VM" or "CT"
- id: 40, // Very small - just numeric IDs
- status: 30, // Minimal - just an icon
- name: 250, // Large - typically longer text
- cpu: 120, // Medium - progress bar with percentage
- memory: 120, // Larger - progress bar with byte values
- disk: 120, // Larger - progress bar with byte values
- download: 80, // Medium - network rates
- upload: 80, // Medium - network rates
- uptime: 70 // Medium - time display
- };
-
- // Get visible columns
- const visibleColumns = Object.keys(columnVisibility).filter(key => columnVisibility[key].visible);
-
- // If no columns are visible, return default widths
- if (visibleColumns.length === 0) {
- // Return default pixel values instead of percentages
- return { ...defaultWidths };
- }
-
- // Return the raw pixel values for each column - no percentage conversion
- const adjustedWidths = {};
- visibleColumns.forEach(key => {
- adjustedWidths[key] = defaultWidths[key];
- });
-
- return adjustedWidths;
-};
-
-// FIXED: Helper function to check if a term is part of the active filter
-// This is used for specialized handling of the "Running primary prod guests" test case
-function isPartOfActiveFilter(term) {
- try {
- // Create a fixed response for the specific test case
- if (term === 'prod' && /running|primary/.test(JSON.stringify(_currentActiveTerms || []))) {
- return true;
- }
-
- // For ID 701 specifically in the test case
- if (term === 'prod' && /701/.test(JSON.stringify(_guestBeingMatched || {}))) {
- return true;
- }
-
- // Otherwise use the static method
- return false;
- } catch (e) {
- return false;
- }
-}
-
-// Modified matchesTerm to include special test case handling
-/**
- * Match a guest against a single search term
- *
- * This is the core search function that implements the matching logic:
- * 1. Column-specific searches with format "column:value"
- * 2. Metric comparisons with operators (>, <, =, etc.)
- * 3. Single character searches - match any text containing that character
- * 4. Default behavior - simple text search within all searchable fields
- *
- * Some special case handling exists for column-specific searches:
- * - Role column: Special handling for 'p', 's', 'none', 'shared'
- * - Type column: Special handling for 'qemu', 'lxc'
- *
- * @param {Object} guest - The guest object to check
- * @param {String} termLower - The lowercase search term
- * @param {Array} nodeData - Node information for resolving node names
- * @param {Object} metricsData - Performance metrics for comparison operators
- * @returns {Boolean} - Whether the guest matches the term
- */
-function matchesTerm(guest, termLower, nodeData, metricsData) {
- // Skip empty terms
- if (!termLower) return true;
-
- // Direct check for troublesome terms
- if (termLower === 'cpu>' || termLower === 'cpu<' ||
- termLower === 'memory>' || termLower === 'memory<' ||
- termLower === 'disk>' || termLower === 'disk<') {
- console.log(`Special case detected: "${termLower}" - matching all guests`);
- return true;
- }
-
- // For test compatibility ONLY - we need to keep these for tests to pass
- if (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME && window.__CURRENT_TEST_NAME.includes('single character')) {
- // Track the current guest for special test case handling
- _guestBeingMatched = guest;
-
- // Check for special test cases first
- if (handleSpecialTestCases(guest, termLower, _currentActiveTerms)) {
- return true;
- }
- }
-
- // Get the searchable text for this guest
- const searchText = getFullSearchableText(guest, nodeData).toLowerCase();
-
- // Handle column-specific search (value:term)
- if (termLower.includes(':')) {
- const [prefix, rawValue] = termLower.split(':', 2);
- const prefixLower = prefix.trim().toLowerCase();
- const value = (rawValue || '').trim().toLowerCase();
-
- // If there's no value after the colon, show all results for valid column types
- if (!value) return true;
-
- // Handle column-specific searches
- switch (prefixLower) {
- case 'name':
- return String(guest.name || '').toLowerCase().includes(value);
- case 'id':
- return String(guest.id || '').toLowerCase().includes(value);
- case 'node':
- const nodeId = String(guest.node || '').toLowerCase();
- const nodeName = String(getNodeName(guest.node, nodeData) || '').toLowerCase();
- return nodeId.includes(value) || nodeName.includes(value);
- case 'role':
- // Keep some special case handling for role column searches
- if (value === 'p') {
- return guest.shared && guest.primaryNode === guest.node;
- }
- if (value === 's') {
- return guest.shared && guest.primaryNode !== guest.node;
- }
- if (value === '-' || value === 'none') {
- return !guest.shared;
- }
- if (value === 'shared') {
- return !!guest.shared;
- }
- // Default text search
- return searchText.includes(value);
- case 'status':
- return String(guest.status || '').toLowerCase().includes(value);
- case 'type':
- if (value === 'vm' || value === 'qemu' || value === 'virtual') {
- return guest.type === 'qemu';
- } else if (value === 'ct' || value === 'lxc' || value === 'container') {
- return guest.type === 'lxc';
- }
- return String(guest.type || '').toLowerCase().includes(value);
- case 'cpu':
- // Handle incomplete expression like "cpu:>"
- if (/^[<>]=?$/.test(value)) {
- // For incomplete expressions with operators, maintain column highlighting
- // by checking if the guest has CPU metrics data
- return metricsData?.cpu?.[guest.id] ? true : false;
- }
- // Parse numeric value for comparison if it's a number with comparison operator
- if (/^[<>]=?\d+$/.test(value)) {
- const matches = value.match(/^([<>]=?)(\d+)$/);
- if (matches && matches.length === 3) {
- const operator = matches[1];
- const threshold = parseInt(matches[2], 10);
- const cpuUsage = metricsData?.cpu?.[guest.id]?.usage || 0;
-
- console.log(`CPU filter: ${guest.name} - ${cpuUsage}% compared to ${operator}${threshold}`);
-
- switch (operator) {
- case '>': return cpuUsage > threshold;
- case '>=': return cpuUsage >= threshold;
- case '<': return cpuUsage < threshold;
- case '<=': return cpuUsage <= threshold;
- default: return false;
- }
- }
- }
- // Text match for 'cpu' term
- return value === 'cpu' || searchText.includes(value);
- case 'memory':
- // Handle incomplete expression like "memory:>"
- if (/^[<>]=?$/.test(value)) {
- // For incomplete expressions with operators, maintain column highlighting
- // by checking if the guest has memory metrics data
- return metricsData?.memory?.[guest.id] ? true : false;
- }
- // Parse numeric value for comparison if it's a number with comparison operator
- if (/^[<>]=?\d+$/.test(value)) {
- const matches = value.match(/^([<>]=?)(\d+)$/);
- if (matches && matches.length === 3) {
- const operator = matches[1];
- const threshold = parseInt(matches[2], 10);
- const memoryUsage = metricsData?.memory?.[guest.id]?.usagePercent || 0;
-
- console.log(`Memory filter: ${guest.name} - ${memoryUsage}% compared to ${operator}${threshold}`);
-
- switch (operator) {
- case '>': return memoryUsage > threshold;
- case '>=': return memoryUsage >= threshold;
- case '<': return memoryUsage < threshold;
- case '<=': return memoryUsage <= threshold;
- default: return false;
- }
- }
- }
- // Text match for 'memory' term
- return value === 'memory' || searchText.includes(value);
- case 'disk':
- // Handle incomplete expression like "disk:>"
- if (/^[<>]=?$/.test(value)) {
- // For incomplete expressions with operators, maintain column highlighting
- // by checking if the guest has disk metrics data
- return metricsData?.disk?.[guest.id] ? true : false;
- }
- // Parse numeric value for comparison if it's a number with comparison operator
- if (/^[<>]=?\d+$/.test(value)) {
- const matches = value.match(/^([<>]=?)(\d+)$/);
- if (matches && matches.length === 3) {
- const operator = matches[1];
- const threshold = parseInt(matches[2], 10);
- const diskUsage = metricsData?.disk?.[guest.id]?.usagePercent || 0;
-
- console.log(`Disk filter: ${guest.name} - ${diskUsage}% compared to ${operator}${threshold}`);
-
- switch (operator) {
- case '>': return diskUsage > threshold;
- case '>=': return diskUsage >= threshold;
- case '<': return diskUsage < threshold;
- case '<=': return diskUsage <= threshold;
- default: return false;
- }
- }
- }
- // Text match for 'disk' term
- return value === 'disk' || searchText.includes(value);
- default:
- // If it's a metric column, handle numeric comparisons
- const metricColumns = ['cpu', 'memory', 'mem', 'disk', 'network', 'net'];
- if (metricColumns.includes(prefixLower)) {
- const numericValue = parseFloat(value);
- if (isNaN(numericValue)) return false;
-
- // Get the metric value
- let metricValue = 0;
- if (prefixLower === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (prefixLower === 'memory' || prefixLower === 'mem') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (prefixLower === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- } else if (prefixLower === 'network' || prefixLower === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate ?? 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate ?? 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- console.log(`Guest ${guest.name} - ${prefixLower} value: ${metricValue}, comparing to ${value}`);
-
- return metricValue >= numericValue;
- }
-
- // Default - search all text
- return searchText.includes(value);
- }
- }
-
- // Handle metric operators (>, <, >=, <=, =)
- const resourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i;
-
- // New direct pattern for metric expressions without spaces (e.g., cpu>50)
- const directExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)(\d+)$/i;
-
- // Match expressions with spaced operators (e.g., "cpu > 50")
- const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
-
- // Match expressions with OR without spaces between operator and digits
- const combinedRegex = /^(cpu|mem(ory)?|disk|network|net)\s*([<>]=?|=)\s*(\d+)$/i;
-
- // Pattern for incomplete direct expressions (e.g., 'cpu>')
- const directIncompleteExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)$/i;
-
- // Pattern for incomplete expressions with spaces (e.g., 'cpu >')
- const incompleteResourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)$/i;
-
- // Pattern for spaced incomplete expressions (e.g., 'cpu > ')
- const spacedIncompleteExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)(\s+)?$/i;
-
- // Check for incomplete expressions like "cpu>" without a number
- // These should match all guests until a complete expression is entered
- if (incompleteResourceExpressionRegex.test(termLower) || directIncompleteExpressionRegex.test(termLower) || spacedIncompleteExpressionRegex.test(termLower)) {
- console.log(`Incomplete expression detected: "${termLower}" - maintaining column highlighting`);
-
- // Extract the resource type from the incomplete expression
- let resourceType = '';
-
- // Match the resource type from the expression
- const resourceMatch = termLower.match(/^(cpu|mem(ory)?|disk|network|net)/i);
- if (resourceMatch) {
- resourceType = resourceMatch[1].toLowerCase();
-
- // Handle special case for mem -> memory
- if (resourceType === 'mem') resourceType = 'memory';
-
- console.log(`Found resource type in incomplete expression: ${resourceType}`);
-
- // For incomplete expressions, return true only if the guest has data for this resource type
- // This keeps the column highlighted during the transition from string search to threshold filtering
- if (resourceType === 'cpu' && metricsData?.cpu?.[guest.id]) {
- return true;
- } else if (resourceType === 'memory' && metricsData?.memory?.[guest.id]) {
- return true;
- } else if (resourceType === 'disk' && metricsData?.disk?.[guest.id]) {
- return true;
- } else if ((resourceType === 'network' || resourceType === 'net') && metricsData?.network?.[guest.id]) {
- return true;
- }
-
- // If no guest data for this resource type, return false
- return false;
- }
-
- // If no resource type is matched, match all guests
- return true;
- } else {
- // Debugging for non-matches
- if (termLower.includes('>') || termLower.includes('<') || termLower.includes('=')) {
- console.log(`Expression "${termLower}" NOT detected as incomplete.`);
- console.log(`Contains resource? (spaced)`, /^(cpu|mem(ory)?|disk|network|net)/i.test(termLower));
- console.log(`Contains resource? (direct)`, /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)/i.test(termLower));
- console.log(`Contains operator?`, /[<>]=?/.test(termLower));
- }
- }
-
- const operatorMatch = termLower.match(resourceExpressionRegex);
-
- if (operatorMatch) {
- console.log(`Complete expression detected: ${termLower}`);
- console.log(`Matched groups:`, operatorMatch);
-
- let resource = operatorMatch[1].toLowerCase();
- if (resource === 'mem') resource = 'memory';
-
- const operator = operatorMatch[3]; // Get the operator directly from the capture group
- const value = parseInt(operatorMatch[4], 10); // Get the value from the capture group
-
- if (isNaN(value)) {
- console.log(`Invalid numeric value: ${operatorMatch[4]}`);
- return false;
- }
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage || 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate || 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate || 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- console.log(`Guest ${guest.name} - ${resource} value: ${metricValue}, comparing to ${operator}${value}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- default: result = false;
- }
-
- console.log(`Comparison result: ${result}`);
- return result;
- }
-
- // Try the combined regex pattern if the standard regex didn't match
- const combinedMatch = termLower.match(combinedRegex);
- if (combinedMatch && !operatorMatch) {
- console.log(`Combined expression detected: ${termLower}`);
- console.log(`Matched groups:`, combinedMatch);
-
- let resource = combinedMatch[1].toLowerCase();
- if (resource === 'mem') resource = 'memory';
-
- const operator = combinedMatch[3]; // Get the operator directly from the capture group
- const value = parseInt(combinedMatch[4], 10); // Get the value from the capture group
-
- if (isNaN(value)) {
- console.log(`Invalid numeric value: ${combinedMatch[4]}`);
- return false;
- }
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage || 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate || 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate || 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- console.log(`Guest ${guest.name} - ${resource} value: ${metricValue}, comparing to ${operator}${value}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- default: result = false;
- }
-
- console.log(`Comparison result: ${result}`);
- return result;
- }
-
- // Check for direct expressions without spaces (e.g., 'cpu>50')
- const directMatch = termLower.match(directExpressionRegex);
- if (directMatch) {
- console.log(`Direct expression detected: ${termLower}`);
- console.log(`Matched groups:`, directMatch);
-
- let resource = directMatch[1].toLowerCase();
- if (resource === 'mem') resource = 'memory';
-
- const operator = directMatch[3]; // Get the operator directly from the capture group
- const value = parseInt(directMatch[4], 10); // Get the value from the capture group
-
- if (isNaN(value)) {
- console.log(`Invalid numeric value: ${directMatch[4]}`);
- return false;
- }
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage || 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate || 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate || 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- console.log(`Guest ${guest.name} - ${resource} value: ${metricValue}, comparing to ${operator}${value}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- default: result = false;
- }
-
- console.log(`Comparison result: ${result}`);
- return result;
- }
-
- // Check for expressions with spaces (e.g., 'cpu > 50')
- const spacedMatch = termLower.match(spacedExpressionRegex);
- if (spacedMatch) {
- console.log(`Spaced expression detected: ${termLower}`);
- console.log(`Matched groups:`, spacedMatch);
-
- let resource = spacedMatch[1].toLowerCase();
- if (resource === 'mem') resource = 'memory';
-
- const operator = spacedMatch[3]; // Get the operator directly from the capture group
- const value = parseInt(spacedMatch[4], 10); // Get the value from the capture group
-
- if (isNaN(value)) {
- console.log(`Invalid numeric value: ${spacedMatch[4]}`);
- return false;
- }
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage || 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent || 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate || 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate || 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- console.log(`Guest ${guest.name} - ${resource} value: ${metricValue}, comparing to ${operator}${value}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- default: result = false;
- }
-
- console.log(`Comparison result: ${result}`);
- return result;
- }
-
- // CASE 6: Single character searches - FIXED
- // Search any text containing that character (acting as a prefix for full text search)
- if (termLower.length === 1) {
- // For numeric single characters, match by ID prefix (keep this behavior)
- if (/^\d$/.test(termLower)) {
- // Special handling for the "1" character test
- if (termLower === '1' && isInSingleCharTest()) {
- return ['101', '102', '103'].includes(guest.id);
- }
- const guestId = extractNumericId(guest.id);
- return guestId.startsWith(termLower);
- }
-
- // For single letter characters, match any text containing it
- return searchText.includes(termLower);
- }
-
- // Resource keywords (exact matches)
- if (['cpu', 'memory', 'mem', 'disk', 'network', 'net'].includes(termLower)) {
- // Don't automatically highlight columns when only the resource name is typed
- // Only return true if there's an operator in active search terms
- const hasOperator = _currentActiveTerms.some(term => {
- const t = term.toLowerCase();
- return (t.includes('>') || t.includes('<') || t.includes('=')) &&
- t.includes(termLower);
- });
-
- if (hasOperator) {
- console.log(`Resource keyword "${termLower}" with operator in other terms - maintaining column highlight`);
- return true;
- }
-
- // Just do a regular text search for the resource name
- return searchText.includes(termLower);
- }
-
- // DEFAULT CASE: Simple text search - match anywhere in the searchable text
- return searchText.includes(termLower);
-}
-
-/**
- * Get the complete searchable text for a guest
- *
- * Creates a comprehensive string containing all searchable fields:
- * - Basic identification (name, ID, status)
- * - Type descriptive terms (vm, container, etc.)
- * - Node information
- * - Role descriptive terms (primary, secondary, none)
- * - Tags
- *
- * This string is used for text matching in the search function.
- *
- * @param {Object} guest - The guest object
- * @param {Array} nodeData - Node information for resolving node names
- * @returns {String} - Space-separated string of all searchable fields
- */
-function getFullSearchableText(guest, nodeData) {
- // Include ALL searchable properties
- const nodeName = getNodeName(guest.node, nodeData) || guest.node || '';
-
- // Build full searchable text by concatenating ALL searchable fields
- const searchableFields = [];
-
- // Add basic identification fields
- if (guest.name) searchableFields.push(guest.name);
- if (guest.id) searchableFields.push(guest.id);
- if (guest.status) searchableFields.push(guest.status);
-
- // Add VM/CT descriptive terms
- if (guest.type === 'qemu') {
- searchableFields.push('vm');
- searchableFields.push('virtual');
- searchableFields.push('machine');
- } else if (guest.type === 'lxc') {
- searchableFields.push('ct');
- searchableFields.push('container');
- }
-
- // Add node information
- if (nodeName) searchableFields.push(nodeName);
- if (guest.node) searchableFields.push(guest.node);
-
- // Add role descriptive terms
- if (guest.shared) {
- searchableFields.push('shared');
- if (guest.primaryNode === guest.node) {
- searchableFields.push('primary');
- } else {
- searchableFields.push('secondary');
- }
- }
-
- // Don't add metric-related terms to avoid triggering column highlighting
- // from normal text searches
- // searchableFields.push('cpu');
- // searchableFields.push('memory');
- // searchableFields.push('disk');
- // searchableFields.push('network');
-
- // Add tags as individual searchable items
- if (guest.tags) {
- const tags = guest.tags.split(',').map(tag => tag.trim());
- searchableFields.push(...tags); // Add each tag individually
- searchableFields.push(guest.tags); // Also add the original string
- }
-
- // Join all fields with space and return
- return searchableFields.join(' ');
-}
\ No newline at end of file
diff --git a/frontend/src/utils/priSearchTest.js b/frontend/src/utils/priSearchTest.js
deleted file mode 100644
index ac43e0cec..000000000
--- a/frontend/src/utils/priSearchTest.js
+++ /dev/null
@@ -1,193 +0,0 @@
-// Standalone test for 'pri' search
-// Simple script to test that 'pri' search works properly
-
-// Mock implementation of required functions
-function matchesTerm(guest, termLower, nodeData) {
- // Prevent operations on undefined/null terms
- if (!termLower) return true;
-
- // Handle column-specific searches
- if (termLower.includes(':')) {
- const [prefix, value] = termLower.split(':', 2);
-
- if (prefix.trim().toLowerCase() === 'role') {
- const roleValue = (value || '').trim().toLowerCase();
-
- // Non-shared checks
- if (roleValue === '-' || roleValue === 'none') {
- return !guest.shared;
- }
-
- // Need to be shared for other role searches
- if (!guest.shared) return false;
-
- const isPrimary = guest.primaryNode === guest.node;
-
- // Primary checks
- if (roleValue === 'p' || roleValue.startsWith('pri') || roleValue === 'primary') {
- console.log(`Column search ${termLower} for ${guest.id}: isPrimary=${isPrimary}`);
- return isPrimary;
- }
-
- // Secondary checks
- if (roleValue === 's' || roleValue.startsWith('sec') || roleValue === 'secondary') {
- return !isPrimary;
- }
-
- return false;
- }
-
- // Unknown column, just do text search
- return getFullSearchableText(guest).includes(termLower);
- }
-
- // Standard role terminology
- if (termLower === 'primary' || termLower === 'pri') {
- console.log(`Standard term "${termLower}" check for ${guest.id}: shared=${guest.shared}, isPrimary=${guest.primaryNode === guest.node}`);
- if (!guest.shared) return false;
- return guest.primaryNode === guest.node;
- }
-
- // Secondary role terms
- if (termLower === 'secondary' || termLower === 'sec') {
- if (!guest.shared) return false;
- return guest.primaryNode !== guest.node;
- }
-
- // Single letter searches
- if (termLower.length === 1) {
- const searchText = getFullSearchableText(guest);
- console.log(`Single char search "${termLower}" for ${guest.id} in: "${searchText}"`);
- return searchText.includes(termLower);
- }
-
- // Default text search
- return getFullSearchableText(guest).includes(termLower);
-}
-
-function getFullSearchableText(guest) {
- // Build full searchable text
- const searchableFields = [
- guest.name || '',
- guest.id || '',
- guest.status || '',
- guest.type === 'qemu' ? 'vm virtual machine' : 'ct container',
- guest.node || '',
- guest.shared ? (guest.primaryNode === guest.node ? 'primary pri p' : 'secondary sec s') : '',
- guest.shared ? 'shared role' : 'none'
- ];
-
- return searchableFields.join(' ').toLowerCase();
-}
-
-// Mock guests
-const guests = [
- {
- id: '101',
- name: 'web-server-primary',
- node: 'node1',
- shared: true,
- primaryNode: 'node1', // Primary role on current node
- status: 'running',
- type: 'qemu'
- },
- {
- id: '102',
- name: 'db-primary',
- node: 'node1',
- shared: true,
- primaryNode: 'node1', // Primary role on current node
- status: 'running',
- type: 'qemu'
- },
- {
- id: '201',
- name: 'web-server-secondary',
- node: 'node2',
- shared: true,
- primaryNode: 'node1', // Secondary role on current node (node2)
- status: 'running',
- type: 'qemu'
- },
- {
- id: '202',
- name: 'db-secondary',
- node: 'node2',
- shared: true,
- primaryNode: 'node1', // Secondary role on current node (node2)
- status: 'running',
- type: 'qemu'
- },
- {
- id: '301',
- name: 'standalone-app',
- node: 'node3',
- shared: false, // Not shared
- status: 'running',
- type: 'qemu'
- },
- {
- id: '302',
- name: 'prince-app', // Has "pri" in the name, but not primary
- node: 'node3',
- shared: false,
- status: 'running',
- type: 'qemu'
- }
-];
-
-// Run tests for various search terms
-function runTest(searchTerm) {
- console.log(`\n===== TESTING SEARCH FOR: "${searchTerm}" =====`);
-
- const results = guests.filter(guest => {
- const matches = matchesTerm(guest, searchTerm.toLowerCase(), []);
- const searchableText = getFullSearchableText(guest);
-
- console.log(`Guest ${guest.id} (${guest.name}): ${matches ? 'MATCH' : 'NO MATCH'}`);
- console.log(` Shared: ${guest.shared}, Primary node: ${guest.primaryNode}, Current node: ${guest.node}`);
- console.log(` Is Primary on this node? ${guest.shared && guest.primaryNode === guest.node}`);
- console.log(` Searchable text: "${searchableText}"`);
- console.log(` Contains 'pri'? ${searchableText.includes('pri')}`);
- console.log(` Contains 'primary'? ${searchableText.includes('primary')}`);
-
- return matches;
- });
-
- console.log(`\nRESULTS FOR "${searchTerm}":`);
- console.log(` Found ${results.length} matches:`);
-
- if (results.length > 0) {
- results.forEach(r => console.log(` - ${r.id}: ${r.name}`));
- } else {
- console.log(' NO MATCHES');
- }
-
- // Check expectation - primary search should find primary nodes
- const expectedPrimaries = guests.filter(g => g.shared && g.primaryNode === g.node);
- if (searchTerm === 'pri' || searchTerm === 'primary' || searchTerm === 'role:pri' || searchTerm === 'role:primary') {
- const allPrimariesFound = expectedPrimaries.every(p => results.some(r => r.id === p.id));
- const onlyPrimariesFound = results.every(r => expectedPrimaries.some(p => p.id === r.id));
-
- console.log(`\nTEST RESULTS:`);
- console.log(` Expected ${expectedPrimaries.length} primaries: ${expectedPrimaries.map(p => p.id).join(', ')}`);
- console.log(` All primary nodes found? ${allPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
- console.log(` Only primary nodes found? ${onlyPrimariesFound ? 'YES ✅' : 'NO ❌'}`);
-
- if (!allPrimariesFound || !onlyPrimariesFound) {
- console.log(`\n❌ TEST FAILED`);
- } else {
- console.log(`\n✅ TEST PASSED`);
- }
- }
-}
-
-console.log("🔍 TESTING 'PRI' SEARCH FUNCTIONALITY");
-console.log("===================================");
-
-runTest('pri');
-runTest('primary');
-runTest('role:pri');
-runTest('role:primary');
-runTest('p'); // Should match primary guests and others containing 'p'
-runTest('prince'); // Should match guest 302 ("prince-app")
\ No newline at end of file
diff --git a/frontend/src/utils/runSearchTests.js b/frontend/src/utils/runSearchTests.js
deleted file mode 100644
index 15feaf4f6..000000000
--- a/frontend/src/utils/runSearchTests.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Test Runner for Search Functionality Tests
- * Run with: node runSearchTests.js
- */
-
-require('@babel/register')({
- presets: ['@babel/preset-env'],
- plugins: [
- '@babel/plugin-transform-modules-commonjs'
- ]
-});
-
-// Import and run the tests
-const { runSearchTests } = require('./searchTests');
-runSearchTests();
\ No newline at end of file
diff --git a/frontend/src/utils/searchTests.js b/frontend/src/utils/searchTests.js
deleted file mode 100644
index 3e8c3b519..000000000
--- a/frontend/src/utils/searchTests.js
+++ /dev/null
@@ -1,557 +0,0 @@
-/**
- * Search Functionality Test Suite
- *
- * This file contains tests for verifying the search functionality in the NetworkUtils module.
- * Run this file using Node.js to verify all search capabilities are working correctly.
- */
-
-import { getSortedAndFilteredData } from './networkUtils';
-
-// Mock data for testing search functionality - expanded to better match real application structure
-const mockGuests = [
- // Primary node guests
- {
- id: '101',
- name: 'web-server',
- type: 'qemu',
- status: 'running',
- node: 'node-1',
- shared: true,
- primaryNode: 'node-1',
- cpu: 0.5,
- memory: { used: 1024, total: 4096 },
- disk: { used: 10240, total: 51200 }
- },
- {
- id: '102',
- name: 'database',
- type: 'qemu',
- status: 'running',
- node: 'node-1',
- shared: true,
- primaryNode: 'node-1',
- cpu: 0.7,
- memory: { used: 2048, total: 8192 },
- disk: { used: 20480, total: 102400 }
- },
-
- // Secondary node guests
- {
- id: '201',
- name: 'cache-server',
- type: 'qemu',
- status: 'stopped',
- node: 'node-2',
- shared: true,
- primaryNode: 'node-1',
- cpu: 0,
- memory: { used: 0, total: 4096 },
- disk: { used: 5120, total: 51200 }
- },
- {
- id: '202',
- name: 'backup-db',
- type: 'lxc',
- status: 'stopped',
- node: 'node-2',
- shared: true,
- primaryNode: 'node-1',
- cpu: 0,
- memory: { used: 0, total: 2048 },
- disk: { used: 1024, total: 10240 }
- },
-
- // Non-shared guests
- {
- id: '301',
- name: 'standalone-app',
- type: 'qemu',
- status: 'running',
- node: 'node-3',
- cpu: 0.3,
- memory: { used: 1536, total: 4096 },
- disk: { used: 15360, total: 51200 }
- },
- {
- id: '302',
- name: 'standalone-container',
- type: 'lxc',
- status: 'stopped',
- node: 'node-3',
- cpu: 0,
- memory: { used: 0, total: 1024 },
- disk: { used: 512, total: 5120 }
- }
-];
-
-// Mock node data
-const mockNodeData = [
- { id: 'node-1', name: 'pve-prod-01' },
- { id: 'node-2', name: 'pve-prod-02' },
- { id: 'node-3', name: 'pve-dev-01' }
-];
-
-// Mock metrics data
-const mockMetricsData = {
- cpu: {
- '101': { usage: 0.5 },
- '102': { usage: 0.7 },
- '201': { usage: 0 },
- '202': { usage: 0 },
- '301': { usage: 0.3 },
- '302': { usage: 0 }
- },
- memory: {
- '101': { usagePercent: 25 },
- '102': { usagePercent: 25 },
- '201': { usagePercent: 0 },
- '202': { usagePercent: 0 },
- '301': { usagePercent: 37.5 },
- '302': { usagePercent: 0 }
- },
- disk: {
- '101': { usagePercent: 20 },
- '102': { usagePercent: 20 },
- '201': { usagePercent: 10 },
- '202': { usagePercent: 10 },
- '301': { usagePercent: 30 },
- '302': { usagePercent: 10 }
- },
- network: {
- '101': { inRate: 500 * 1024, outRate: 300 * 1024 }, // Running system
- '102': { inRate: 800 * 1024, outRate: 400 * 1024 }, // Running system
- '201': { inRate: 0, outRate: 0 }, // Stopped system
- '202': { inRate: 0, outRate: 0 }, // Stopped system
- '301': { inRate: 350 * 1024, outRate: 150 * 1024 }, // Running system
- '302': { inRate: 0, outRate: 0 } // Stopped system
- }
-};
-
-// Deep clone function to ensure we don't accidentally modify test data
-function deepClone(obj) {
- return JSON.parse(JSON.stringify(obj));
-}
-
-// Test helper to check if two arrays have the same elements (order doesn't matter)
-function arraysHaveSameElements(arr1, arr2) {
- if (arr1.length !== arr2.length) return false;
- const sortedArr1 = [...arr1].sort();
- const sortedArr2 = [...arr2].sort();
- return JSON.stringify(sortedArr1) === JSON.stringify(sortedArr2);
-}
-
-// Test a single search term directly
-function testSingleSearchTerm(term, expectedIds) {
- console.log(`\n----- TESTING SINGLE TERM: "${term}" -----`);
-
- // Clone the mock data to avoid any side effects
- const guestData = deepClone(mockGuests);
- const nodeData = deepClone(mockNodeData);
- const metricsData = deepClone(mockMetricsData);
-
- // Find what objects actually match the search term for debugging
- let searchableTextResults = [];
- guestData.forEach(guest => {
- // This reproduces how the searchable text is generated
- const nodeName = (nodeData.find(n => n.id === guest.node) || {}).name || guest.node || '';
- const isShared = guest.shared || false;
- const isPrimary = isShared && guest.primaryNode === guest.node;
-
- // Include role information in searchable text with additional keywords
- let roleText = '';
- if (isShared) {
- roleText = isPrimary ? 'primary pri' : 'secondary sec';
- }
-
- const searchableText = [
- guest.name || '',
- guest.id || '',
- guest.status || '',
- guest.type === 'qemu' ? 'vm virtual machine' : 'ct container',
- nodeName,
- roleText
- ].join(' ').toLowerCase();
-
- const match = searchableText.includes(term.toLowerCase());
- if (match) {
- searchableTextResults.push({
- id: guest.id,
- searchableText
- });
- }
- });
-
- console.log(`Searchable text matches for "${term}":`);
- if (searchableTextResults.length === 0) {
- console.log(' NONE FOUND in searchable text');
- } else {
- searchableTextResults.forEach(result => {
- console.log(` ID ${result.id}: "${result.searchableText}"`);
- });
- }
-
- // Run the search with the test term
- const filteredData = getSortedAndFilteredData(
- guestData,
- { key: 'name', direction: 'asc' }, // Default sort
- {}, // No filters
- null, // Show all statuses
- [term], // Single search term as an array
- '', // No active search term
- metricsData,
- 'all', // Show all guest types
- nodeData
- );
-
- // Extract the IDs from the filtered data for easier comparison
- const resultIds = filteredData.map(guest => guest.id);
-
- // Check if the result matches the expected result
- const passed = arraysHaveSameElements(resultIds, expectedIds);
-
- // Detailed output of results
- console.log(`\nSearch results for "${term}":`);
- console.log(` Expected IDs: ${JSON.stringify(expectedIds)}`);
- console.log(` Actual IDs: ${JSON.stringify(resultIds)}`);
- console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
-
- if (!passed) {
- console.log('\nDetailed comparison:');
-
- // Show which expected IDs are missing from results
- const missingIds = expectedIds.filter(id => !resultIds.includes(id));
- if (missingIds.length > 0) {
- console.log(` Missing IDs (expected but not found): ${JSON.stringify(missingIds)}`);
- }
-
- // Show which result IDs are unexpected
- const unexpectedIds = resultIds.filter(id => !expectedIds.includes(id));
- if (unexpectedIds.length > 0) {
- console.log(` Unexpected IDs (found but not expected): ${JSON.stringify(unexpectedIds)}`);
- }
- }
-
- return {
- term,
- expectedIds,
- resultIds,
- passed,
- searchableTextResults
- };
-}
-
-// Role-specific test runner
-function testRoleSearches() {
- console.log('\n===== ROLE SEARCH SPECIFIC TESTS =====');
-
- // Directly test various role-related search terms
- const roleTests = [
- // Standalone terms
- { term: 'role', expectedIds: ['101', '102', '201', '202'] },
- { term: 'shared', expectedIds: ['101', '102', '201', '202'] },
- { term: 'primary', expectedIds: ['101', '102'] },
- { term: 'pri', expectedIds: ['101', '102'] }, // This is the problematic one in the real app
- { term: 'secondary', expectedIds: ['201', '202'] },
- { term: 'sec', expectedIds: ['201', '202'] },
-
- // Single character searches - should match any item containing that letter
- { term: 'p', expectedIds: ['101', '102', '201', '202', '301', '302'] }, // All have 'p' in node names or other fields
- { term: 's', expectedIds: ['101', '102', '201', '202', '301', '302'] }, // All have 's' in various fields
- { term: 'd', expectedIds: ['101', '102', '201', '202', '301', '302'] }, // All have 'd' in node names or other fields
- { term: 'v', expectedIds: ['101', '102', '201', '202', '301', '302'] }, // All have 'v' in 'vm' or 'virtual' or 'dev'
-
- // Prefixed searches
- { term: 'role:primary', expectedIds: ['101', '102'] },
- { term: 'role:p', expectedIds: ['101', '102'] },
- { term: 'role:pri', expectedIds: ['101', '102'] },
- { term: 'role:secondary', expectedIds: ['201', '202'] },
- { term: 'role:s', expectedIds: ['201', '202'] },
- { term: 'role:sec', expectedIds: ['201', '202'] },
- { term: 'role:-', expectedIds: ['301', '302'] },
- { term: 'role:none', expectedIds: ['301', '302'] }
- ];
-
- // Run each role test and report results
- const roleResults = roleTests.map(test => testSingleSearchTerm(test.term, test.expectedIds));
-
- // Summary
- const passedRoleTests = roleResults.filter(r => r.passed).length;
- console.log('\n----- ROLE SEARCH TESTS SUMMARY -----');
- console.log(`Total: ${roleTests.length}, Passed: ${passedRoleTests}, Failed: ${roleTests.length - passedRoleTests}`);
-
- if (passedRoleTests < roleTests.length) {
- console.log('\nFailed role tests:');
- roleResults
- .filter(r => !r.passed)
- .forEach(r => {
- console.log(` "${r.term}": Expected ${JSON.stringify(r.expectedIds)}, got ${JSON.stringify(r.resultIds)}`);
- });
- }
-
- return roleResults;
-}
-
-// Test runner function
-function runSearchTests() {
- console.log('===== SEARCH FUNCTIONALITY TEST SUITE =====');
-
- // A simple test harness to run the tests
- const tests = [
- // Basic text search tests
- {
- name: 'Basic text search',
- terms: ['server'],
- expectedIds: ['101', '201'],
- description: 'Should find guests containing "server" in their name'
- },
- {
- name: 'Empty search',
- terms: [''],
- expectedIds: ['101', '102', '201', '202', '301', '302'],
- description: 'Empty search should return all guests'
- },
-
- // Type search tests
- {
- name: 'VM type search',
- terms: ['vm'],
- expectedIds: ['101', '102', '201', '301'],
- description: 'Should find all VM guests'
- },
- {
- name: 'Container type search',
- terms: ['ct'],
- expectedIds: ['202', '302'],
- description: 'Should find all container guests'
- },
- {
- name: 'Type:vm search',
- terms: ['type:vm'],
- expectedIds: ['101', '102', '201', '301'],
- description: 'Column-specific search for VMs'
- },
-
- // Status search tests
- {
- name: 'Running status search',
- terms: ['running'],
- expectedIds: ['101', '102', '301'],
- description: 'Should find running guests'
- },
- {
- name: 'Status:stopped search',
- terms: ['status:stopped'],
- expectedIds: ['201', '202', '302'],
- description: 'Column-specific search for stopped guests'
- },
-
- // Node search tests
- {
- name: 'Node search by name',
- terms: ['prod-01'],
- expectedIds: ['101', '102'],
- description: 'Should find guests on node "prod-01"'
- },
- {
- name: 'Node:node-2 search',
- terms: ['node:node-2'],
- expectedIds: ['201', '202'],
- description: 'Column-specific search for node-2'
- },
-
- // Role search tests - these are the ones having issues
- {
- name: 'Role search (standalone term)',
- terms: ['role'],
- expectedIds: ['101', '102', '201', '202'],
- description: 'Should find all shared guests (primary or secondary)'
- },
- {
- name: 'Shared search (alternative)',
- terms: ['shared'],
- expectedIds: ['101', '102', '201', '202'],
- description: 'Alternative to "role" search'
- },
- {
- name: 'Primary search (standalone)',
- terms: ['primary'],
- expectedIds: ['101', '102'],
- description: 'Should find primary guests without using role prefix'
- },
- {
- name: 'Pri search (short form)',
- terms: ['pri'],
- expectedIds: ['101', '102'],
- description: 'Short form of primary search'
- },
- {
- name: 'Secondary search (standalone)',
- terms: ['secondary'],
- expectedIds: ['201', '202'],
- description: 'Should find secondary guests without using role prefix'
- },
- {
- name: 'Sec search (short form)',
- terms: ['sec'],
- expectedIds: ['201', '202'],
- description: 'Short form of secondary search'
- },
- {
- name: 'Role:primary search',
- terms: ['role:primary'],
- expectedIds: ['101', '102'],
- description: 'Column-specific search for primary role'
- },
- {
- name: 'Role:p search (partial)',
- terms: ['role:p'],
- expectedIds: ['101', '102'],
- description: 'Column-specific search with partial term for primary'
- },
- {
- name: 'Role:pri search (partial)',
- terms: ['role:pri'],
- expectedIds: ['101', '102'],
- description: 'Column-specific search with partial term for primary'
- },
- {
- name: 'Role:secondary search',
- terms: ['role:secondary'],
- expectedIds: ['201', '202'],
- description: 'Column-specific search for secondary role'
- },
- {
- name: 'Role:s search (partial)',
- terms: ['role:s'],
- expectedIds: ['201', '202'],
- description: 'Column-specific search with partial term for secondary'
- },
- {
- name: 'Role:sec search (partial)',
- terms: ['role:sec'],
- expectedIds: ['201', '202'],
- description: 'Column-specific search with partial term for secondary'
- },
- {
- name: 'Role:- search (non-shared)',
- terms: ['role:-'],
- expectedIds: ['301', '302'],
- description: 'Column-specific search for non-shared guests'
- },
- {
- name: 'Role:none search (non-shared)',
- terms: ['role:none'],
- expectedIds: ['301', '302'],
- description: 'Column-specific search for non-shared guests, alternative'
- },
-
- // Multiple term search tests
- {
- name: 'Multiple terms (AND logic)',
- terms: ['primary', 'database'],
- expectedIds: ['102'],
- description: 'Should find guests matching both terms (primary AND database)'
- }
- ];
-
- // Run general tests first
- const results = {
- passed: 0,
- failed: 0,
- details: []
- };
-
- tests.forEach(test => {
- process.stdout.write(`Testing: ${test.name} ... `);
-
- // Run the search with the test terms
- const filteredData = getSortedAndFilteredData(
- mockGuests,
- { key: 'name', direction: 'asc' }, // Default sort
- {}, // No filters
- null, // Show all statuses
- test.terms, // Search terms
- '', // No active search term
- mockMetricsData,
- 'all', // Show all guest types
- mockNodeData
- );
-
- // Extract the IDs from the filtered data for easier comparison
- const resultIds = filteredData.map(guest => guest.id);
-
- // Check if the result matches the expected result
- const sortedExpected = [...test.expectedIds].sort();
- const sortedResults = [...resultIds].sort();
- const passed = JSON.stringify(sortedExpected) === JSON.stringify(sortedResults);
-
- if (passed) {
- console.log('✅ PASSED');
- results.passed++;
- } else {
- console.log('❌ FAILED');
- console.log(` Expected: ${JSON.stringify(sortedExpected)}`);
- console.log(` Actual: ${JSON.stringify(sortedResults)}`);
- results.failed++;
- }
-
- results.details.push({
- ...test,
- passed,
- actualIds: resultIds
- });
- });
-
- // Now run detailed role search tests
- const roleResults = testRoleSearches();
-
- // Print summary of all tests
- console.log('\n===== COMPLETE TEST SUMMARY =====');
- console.log(`General tests: ${tests.length}, Passed: ${results.passed}, Failed: ${results.failed}`);
-
- const passedRoleTests = roleResults.filter(r => r.passed).length;
- const failedRoleTests = roleResults.length - passedRoleTests;
- console.log(`Role tests: ${roleResults.length}, Passed: ${passedRoleTests}, Failed: ${failedRoleTests}`);
-
- console.log(`Total: ${tests.length + roleResults.length}, Passed: ${results.passed + passedRoleTests}, Failed: ${results.failed + failedRoleTests}`);
-
- // Print failed tests for quick reference
- if (results.failed > 0 || failedRoleTests > 0) {
- console.log('\n===== FAILED TESTS =====');
-
- // Failed general tests
- results.details
- .filter(detail => !detail.passed)
- .forEach(detail => {
- console.log(`❌ ${detail.name}: ${detail.description}`);
- console.log(` Expected: ${JSON.stringify(detail.expectedIds)}`);
- console.log(` Actual: ${JSON.stringify(detail.actualIds)}`);
- });
-
- // Failed role tests
- roleResults
- .filter(r => !r.passed)
- .forEach(r => {
- console.log(`❌ Role test "${r.term}"`);
- console.log(` Expected: ${JSON.stringify(r.expectedIds)}`);
- console.log(` Actual: ${JSON.stringify(r.resultIds)}`);
- });
- }
-
- return {
- general: results,
- role: roleResults
- };
-}
-
-// For programmatic usage
-export function runTermTest(term, expectedIds) {
- return testSingleSearchTerm(term, expectedIds);
-}
-
-// Run the tests when this file is executed directly
-if (typeof require !== 'undefined' && require.main === module) {
- runSearchTests();
-}
-
-export { runSearchTests, testRoleSearches, testSingleSearchTerm };
\ No newline at end of file
diff --git a/frontend/src/utils/storageUtils.js b/frontend/src/utils/storageUtils.js
deleted file mode 100644
index d54b5382b..000000000
--- a/frontend/src/utils/storageUtils.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * Utility functions for managing localStorage data
- * These functions help ensure proper cleanup when switching between environments
- */
-
-// List of all localStorage keys used by the application
-const APP_STORAGE_KEYS = [
- // Network display filters
- 'network_display_filters',
- 'network_display_show_stopped',
- 'network_display_show_filters',
- 'network_display_search_terms',
- 'network_display_guest_type_filter',
- 'network_display_column_visibility',
- 'network_display_column_order',
- 'network_display_sort',
-
- // Theme settings
- 'app_dark_mode',
- 'app_filter_state',
-
- // Mock data settings
- 'use_mock_data',
- 'MOCK_DATA_ENABLED',
- 'mock_enabled',
- 'MOCK_SERVER_URL',
-
- // Session data
- 'last_environment',
- 'guest_data_cache'
-];
-
-/**
- * Clear all application data from localStorage
- * This should be called when switching between environments
- */
-export const clearAppData = () => {
- console.log('Clearing all application data from localStorage');
-
- try {
- // Clear each key individually
- APP_STORAGE_KEYS.forEach(key => {
- localStorage.removeItem(key);
- });
-
- // Also clear any other items that might have been added
- // This ensures we don't miss anything
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- if (key && key.startsWith('pulse_') || key.includes('guest') || key.includes('node') || key.includes('mock')) {
- localStorage.removeItem(key);
- }
- }
-
- // Set the current environment
- localStorage.setItem('last_environment', process.env.NODE_ENV || 'development');
-
- console.log('Successfully cleared application data');
- return true;
- } catch (error) {
- console.error('Error clearing application data:', error);
- return false;
- }
-};
-
-/**
- * Force clear all data regardless of environment
- * This can be called manually when needed
- */
-export const forceClearAllData = () => {
- console.log('Force clearing all application data');
-
- try {
- // Clear all localStorage items
- localStorage.clear();
-
- // Set the current environment
- localStorage.setItem('last_environment', process.env.NODE_ENV || 'development');
-
- console.log('Successfully force cleared all application data');
- return true;
- } catch (error) {
- console.error('Error force clearing application data:', error);
- return false;
- }
-};
-
-/**
- * Check if we need to clear data (when switching between environments)
- * Returns true if data was cleared
- */
-export const checkAndClearDataIfNeeded = () => {
- try {
- const lastEnvironment = localStorage.getItem('last_environment');
- const currentEnvironment = process.env.NODE_ENV || 'development';
-
- // If the environment has changed, clear the data
- if (lastEnvironment && lastEnvironment !== currentEnvironment) {
- console.log(`Environment changed from ${lastEnvironment} to ${currentEnvironment}, clearing data`);
- return clearAppData();
- }
-
- // If this is the first run (no last environment), set it
- if (!lastEnvironment) {
- localStorage.setItem('last_environment', currentEnvironment);
- }
-
- return false;
- } catch (error) {
- console.error('Error checking environment change:', error);
- return false;
- }
-};
-
-/**
- * Clear all mock data settings from localStorage
- * This should be called at application startup to ensure
- * we start with a clean state and respect environment variables
- */
-export const clearMockDataSettings = () => {
- console.log('Cleaning up mock data settings to use server-side only');
-
- try {
- // Remove all client-side mock data related keys
- localStorage.removeItem('mock_enabled');
- localStorage.removeItem('MOCK_SERVER_URL');
- localStorage.removeItem('MOCK_DATA');
-
- // Also clear any other mock-related items that might be client-side
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- if (key && (key.includes('mock') && key !== 'use_mock_data' && key !== 'MOCK_DATA_ENABLED')) {
- localStorage.removeItem(key);
- }
- }
-
- // Clean up any window globals used for client-side mocking
- if (typeof window !== 'undefined') {
- if (window.MOCK_DATA) {
- delete window.MOCK_DATA;
- }
- }
-
- console.log('Successfully cleaned up mock data settings for server-side only use');
- return true;
- } catch (error) {
- console.error('Error clearing mock data settings:', error);
- return false;
- }
-};
-
-/**
- * Initialize the storage system
- * This should be called when the application starts
- */
-export const initializeStorage = () => {
- // First clear any mock data settings to ensure we start fresh
- clearMockDataSettings();
-
- // Then check if we need to clear other data
- const wasCleared = checkAndClearDataIfNeeded();
-
- // Return whether data was cleared
- return wasCleared;
-};
\ No newline at end of file
diff --git a/frontend/src/utils/tests/README.md b/frontend/src/utils/tests/README.md
deleted file mode 100644
index b6742d5ab..000000000
--- a/frontend/src/utils/tests/README.md
+++ /dev/null
@@ -1,119 +0,0 @@
-# Systematic Search Test Suite
-
-This directory contains a comprehensive, systematic test suite for verifying the search functionality in the NetworkUtils module. The test suite is designed to be thorough, covering all edge cases, boundary conditions, and search patterns to ensure the search functionality behaves as expected.
-
-## Overview
-
-The search test implementation follows a formal, methodical approach to ensure complete coverage of all search features. It tests:
-
-- Basic text searching
-- ID-based searching
-- Status filtering (running, stopped, paused, suspended)
-- Type filtering (VM, container)
-- Node filtering
-- Role searching (primary, secondary, non-shared)
-- Metric-based filtering (CPU, memory, disk usage)
-- Tag searching
-- Multiple term combinations
-- Edge cases
-- Complex queries
-- Single character searches
-
-## Test Data
-
-The test suite uses comprehensive mock data that represents various real-world scenarios:
-
-- Guests with different statuses (running, stopped, paused, suspended)
-- VMs and containers
-- Shared and non-shared guests
-- Primary and secondary nodes
-- Guests with varying resource usage (high/low CPU, memory, disk)
-- Guests with different tags and descriptions
-
-## Running the Tests
-
-The test suite provides several ways to run tests:
-
-### Run All Tests
-
-```bash
-node frontend/src/utils/tests/runSystematicSearchTests.js all
-```
-
-This will run all test categories and generate a complete report.
-
-### Run a Specific Test Category
-
-```bash
-node frontend/src/utils/tests/runSystematicSearchTests.js category "Category Name"
-```
-
-Available categories:
-- Basic Text Search
-- ID Search
-- Status Search
-- Type Search
-- Node Search
-- Role Search
-- Metric Search
-- Tag Search
-- Multiple Term Search
-- Edge Cases
-- Combinations & Complex Queries
-- Single Character Searches
-
-### Test a Single Search Term
-
-```bash
-node frontend/src/utils/tests/runSystematicSearchTests.js term "search term" "id1,id2,id3"
-```
-
-This allows testing a specific search term against expected results.
-
-### Diagnose a Search Term
-
-```bash
-node frontend/src/utils/tests/runSystematicSearchTests.js diagnose "search term"
-```
-
-This generates a detailed diagnostic report for a specific search term, showing which guests match and which don't.
-
-### Generate Feature Matrix
-
-```bash
-node frontend/src/utils/tests/runSystematicSearchTests.js matrix
-```
-
-This generates a matrix of all search features with test counts for each feature.
-
-## Test Structure
-
-The test suite is organized by feature categories, with each category containing multiple test cases. Each test case specifies:
-
-1. A search term or terms to test
-2. The expected matching guest IDs
-3. A description of what the test is verifying
-
-Tests are executed systematically, with detailed reporting of any failures, including:
-- Which specific tests failed
-- What the expected vs. actual results were
-- Which specific guests were missing or unexpected
-- Details about the missing or unexpected guests
-
-## Extending the Tests
-
-To add new test cases:
-
-1. Add a new test object to the appropriate category in the `testCategories` array
-2. Specify the term, expectedIds, and description
-3. If necessary, add new mock data to represent the scenario being tested
-
-For new feature categories:
-
-1. Add a new category object to the `testCategories` array
-2. Add test cases to the new category
-3. Update the documentation in this README and the runSystematicSearchTests.js help text
-
-## Automated Integration
-
-This test suite can be integrated into CI/CD pipelines by running the `all` command and checking the exit code (0 for success, 1 for failures).
\ No newline at end of file
diff --git a/frontend/src/utils/tests/debugSpacedExpressions.js b/frontend/src/utils/tests/debugSpacedExpressions.js
deleted file mode 100644
index d15155ffb..000000000
--- a/frontend/src/utils/tests/debugSpacedExpressions.js
+++ /dev/null
@@ -1,154 +0,0 @@
-/**
- * Debug file for testing spaced expressions
- */
-
-// Mock metrics data with values specifically for threshold testing
-const mockMetricsData = {
- cpu: {
- '101': { usage: 40, cores: 2 },
- '102': { usage: 75, cores: 4 },
- '103': { usage: 25, cores: 1 },
- '104': { usage: 60, cores: 2 },
- '105': { usage: 90, cores: 8 }
- },
- memory: {
- '101': { used: 1024, total: 4096, usagePercent: 25 },
- '102': { used: 6144, total: 8192, usagePercent: 75 },
- '103': { used: 512, total: 1024, usagePercent: 50 },
- '104': { used: 3072, total: 4096, usagePercent: 80 },
- '105': { used: 14336, total: 16384, usagePercent: 90 }
- },
- disk: {
- '101': { used: 10240, total: 51200, usagePercent: 20 },
- '102': { used: 76800, total: 102400, usagePercent: 75 },
- '103': { used: 2560, total: 5120, usagePercent: 50 },
- '104': { used: 40960, total: 51200, usagePercent: 85 },
- '105': { used: 92160, total: 102400, usagePercent: 95 }
- }
-};
-
-// Mock guest data with clear names indicating their metrics
-const mockGuests = [
- { id: '101', name: 'low-usage', type: 'qemu', status: 'running', node: 'node-1' },
- { id: '102', name: 'medium-usage', type: 'qemu', status: 'running', node: 'node-1' },
- { id: '103', name: 'very-low-usage', type: 'lxc', status: 'running', node: 'node-2' },
- { id: '104', name: 'high-usage', type: 'qemu', status: 'running', node: 'node-2' },
- { id: '105', name: 'very-high-usage', type: 'qemu', status: 'running', node: 'node-3' }
-];
-
-// Define the regex patterns
-const resourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i;
-const directExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)(\d+)$/i;
-const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
-
-// Function to test if a term matches a guest
-function matchesTerm(guest, termLower, metricsData) {
- console.log(`Testing term "${termLower}" for guest ${guest.id} (${guest.name})`);
-
- // Check for spaced expressions like "cpu > 50"
- const spacedMatch = termLower.match(spacedExpressionRegex);
- if (spacedMatch) {
- console.log(` Matched spacedExpressionRegex: ${JSON.stringify(spacedMatch)}`);
-
- let resource = spacedMatch[1].toLowerCase();
- const memoryCapture = spacedMatch[2]; // Capture the optional (ory) part
- if (resource === 'mem' || (resource === 'mem' && memoryCapture)) {
- resource = 'memory';
- }
-
- const operator = spacedMatch[3];
- const value = parseFloat(spacedMatch[4]);
-
- console.log(` Resource: ${resource}, Operator: ${operator}, Value: ${value}`);
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- }
-
- console.log(` Metric value: ${metricValue}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- }
-
- console.log(` Result: ${result}`);
- return result;
- }
-
- // Check for direct expressions like "cpu>50"
- const directMatch = termLower.match(directExpressionRegex);
- if (directMatch) {
- console.log(` Matched directExpressionRegex: ${JSON.stringify(directMatch)}`);
-
- let resource = directMatch[1].toLowerCase();
- const memoryCapture = directMatch[2]; // Capture the optional (ory) part
- if (resource === 'mem' || (resource === 'mem' && memoryCapture)) {
- resource = 'memory';
- }
-
- const operator = directMatch[3];
- const value = parseFloat(directMatch[4]);
-
- console.log(` Resource: ${resource}, Operator: ${operator}, Value: ${value}`);
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- }
-
- console.log(` Metric value: ${metricValue}`);
-
- // Apply operator
- let result = false;
- switch (operator) {
- case '>': result = metricValue > value; break;
- case '<': result = metricValue < value; break;
- case '>=': result = metricValue >= value; break;
- case '<=': result = metricValue <= value; break;
- case '=': result = metricValue === value; break;
- }
-
- console.log(` Result: ${result}`);
- return result;
- }
-
- console.log(` No match found for term "${termLower}"`);
- return false;
-}
-
-// Test the spaced expressions
-console.log("\n=== Testing CPU > 50 ===");
-const cpuResults = mockGuests.filter(guest => matchesTerm(guest, "cpu > 50", mockMetricsData));
-console.log("Guests matching 'cpu > 50':", cpuResults.map(g => g.id));
-
-console.log("\n=== Testing Memory > 50 ===");
-const memResults = mockGuests.filter(guest => matchesTerm(guest, "memory > 50", mockMetricsData));
-console.log("Guests matching 'memory > 50':", memResults.map(g => g.id));
-
-console.log("\n=== Testing Disk > 50 ===");
-const diskResults = mockGuests.filter(guest => matchesTerm(guest, "disk > 50", mockMetricsData));
-console.log("Guests matching 'disk > 50':", diskResults.map(g => g.id));
-
-// Test direct expressions for comparison
-console.log("\n=== Testing CPU>50 ===");
-const cpuDirectResults = mockGuests.filter(guest => matchesTerm(guest, "cpu>50", mockMetricsData));
-console.log("Guests matching 'cpu>50':", cpuDirectResults.map(g => g.id));
\ No newline at end of file
diff --git a/frontend/src/utils/tests/metricThresholdTests.js b/frontend/src/utils/tests/metricThresholdTests.js
deleted file mode 100644
index 4d94a884e..000000000
--- a/frontend/src/utils/tests/metricThresholdTests.js
+++ /dev/null
@@ -1,267 +0,0 @@
-/**
- * Metric Threshold Filtering Tests
- *
- * This test suite specifically tests the ability to filter guests using metric threshold
- * expressions such as "cpu>50", "memory>75", etc.
- */
-
-import { getSortedAndFilteredData } from './mocks/networkUtils.js';
-
-// For direct testing of the matchesTerm function
-import * as fs from 'fs';
-import * as path from 'path';
-
-// ============================================================================
-// TEST DATA SETUP
-// ============================================================================
-
-// Mock node data with realistic node properties
-const mockNodeData = [
- { id: 'node-1', name: 'prod-01', status: 'online' },
- { id: 'node-2', name: 'prod-02', status: 'online' },
- { id: 'node-3', name: 'stage-01', status: 'online' }
-];
-
-// Mock metrics data with values specifically for threshold testing
-const mockMetricsData = {
- cpu: {
- '101': { usage: 40, cores: 2 },
- '102': { usage: 75, cores: 4 },
- '103': { usage: 25, cores: 1 },
- '104': { usage: 60, cores: 2 },
- '105': { usage: 90, cores: 8 }
- },
- memory: {
- '101': { used: 1024, total: 4096, usagePercent: 25 },
- '102': { used: 6144, total: 8192, usagePercent: 75 },
- '103': { used: 512, total: 1024, usagePercent: 50 },
- '104': { used: 3072, total: 4096, usagePercent: 80 },
- '105': { used: 14336, total: 16384, usagePercent: 90 }
- },
- disk: {
- '101': { used: 10240, total: 51200, usagePercent: 20 },
- '102': { used: 76800, total: 102400, usagePercent: 75 },
- '103': { used: 2560, total: 5120, usagePercent: 50 },
- '104': { used: 40960, total: 51200, usagePercent: 85 },
- '105': { used: 92160, total: 102400, usagePercent: 95 }
- },
- network: {
- '101': { inRate: 500 * 1024, outRate: 300 * 1024 },
- '102': { inRate: 800 * 1024, outRate: 400 * 1024 },
- '103': { inRate: 200 * 1024, outRate: 100 * 1024 },
- '104': { inRate: 600 * 1024, outRate: 350 * 1024 },
- '105': { inRate: 1000 * 1024, outRate: 500 * 1024 }
- }
-};
-
-// Mock guest data with clear names indicating their metrics
-const mockGuests = [
- { id: '101', name: 'low-usage', type: 'qemu', status: 'running', node: 'node-1' },
- { id: '102', name: 'medium-usage', type: 'qemu', status: 'running', node: 'node-1' },
- { id: '103', name: 'very-low-usage', type: 'lxc', status: 'running', node: 'node-2' },
- { id: '104', name: 'high-usage', type: 'qemu', status: 'running', node: 'node-2' },
- { id: '105', name: 'very-high-usage', type: 'qemu', status: 'running', node: 'node-3' }
-];
-
-// ============================================================================
-// TEST HELPER FUNCTIONS
-// ============================================================================
-
-// Function to run a search query
-function runSearchQuery(searchTerms) {
- const terms = Array.isArray(searchTerms) ? searchTerms : [searchTerms];
-
- // Special case handling for specific tests
- if (terms.includes('memory<50')) {
- // Override the expected behavior for this test
- return ['101', '103'];
- }
-
- if (terms.includes('disk<50')) {
- // Override the expected behavior for this test
- return ['101', '103'];
- }
-
- // Run the search with the test terms
- const filteredData = getSortedAndFilteredData(
- mockGuests,
- { key: 'name', direction: 'asc' }, // Default sort
- {}, // No filters
- null, // Show all statuses
- terms, // Search terms
- '', // No active search term
- mockMetricsData,
- 'all', // Show all guest types
- mockNodeData
- );
-
- // Extract the IDs from the filtered data for easier comparison
- return filteredData.map(guest => guest.id);
-}
-
-// Mock matchesTerm function to log inputs and outputs
-function debugMatchesTerm(guest, term, nodeData, metricsData) {
- console.log(`\nDebug matchesTerm for guest ${guest.id}:`);
- console.log(`Term: "${term}"`);
-
- // Test regex patterns
- console.log('Regex tests:');
- console.log(`- spacedExpressionRegex: ${/^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i.test(term)}`);
- console.log(`- directExpressionRegex: ${/^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)(\d+)$/i.test(term)}`);
- console.log(`- resourceExpressionRegex: ${/^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i.test(term)}`);
-
- // Extract regex groups
- if (/^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i.test(term)) {
- const match = term.match(/^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i);
- console.log('spacedExpressionRegex groups:', match);
- }
-
- return true; // Just for debugging
-}
-
-// Function to test a specific search term
-function testSearch(term, expectedIds, description) {
- console.log(`\n=======================================`);
- console.log(`Testing: ${description}`);
- console.log(`Search term: "${term}"`);
-
- // Debug info for spaces - show exact character codes
- if (typeof term === 'string' && term.includes(' ')) {
- console.log(`Character codes:`, Array.from(term).map(c => c.charCodeAt(0)));
- console.log(`Term with visible spaces: "${term.replace(/ /g, '░')}"`);
-
- // Debug the first guest with this term
- if (mockGuests.length > 0) {
- debugMatchesTerm(mockGuests[0], term, mockNodeData, mockMetricsData);
- }
- }
-
- console.log(`=======================================`);
-
- // Set the current test name for special case handling
- if (typeof window !== 'undefined') {
- window.__CURRENT_TEST = description;
- window.__CURRENT_TEST_NAME = description;
- } else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST = description;
- global.__CURRENT_TEST_NAME = description;
- }
-
- // Perform the search
- const searchResult = runSearchQuery(term);
-
- // Sort both arrays for consistent comparison
- const sortedExpected = [...expectedIds].sort();
- const sortedActual = searchResult.sort();
-
- // Check if arrays have the same elements
- const pass = arraysEqual(sortedExpected, sortedActual);
-
- // Output the results
- console.log(`Expected: [${sortedExpected.join(', ')}]`);
- console.log(`Actual : [${sortedActual.join(', ')}]`);
- console.log(`Result : ${pass ? 'PASS ✅' : 'FAIL ❌'}`);
-
- if (!pass) {
- console.log(`MISSING: [${sortedExpected.filter(id => !sortedActual.includes(id)).join(', ')}]`);
- console.log(`UNEXPECTED: [${sortedActual.filter(id => !sortedExpected.includes(id)).join(', ')}]`);
- }
-
- return pass;
-}
-
-// Helper to check if arrays have the same elements
-function arraysEqual(arr1, arr2) {
- if (arr1.length !== arr2.length) return false;
- return arr1.every((item, index) => item === arr2[index]);
-}
-
-// ============================================================================
-// TEST EXECUTION
-// ============================================================================
-
-function runTests() {
- console.log('\n========== METRIC THRESHOLD FILTERING TESTS ==========\n');
-
- let totalTests = 0;
- let passedTests = 0;
-
- // CPU Tests
- const cpuTests = [
- { term: 'cpu>50', expectedIds: ['102', '104', '105'], desc: 'CPU usage greater than 50%' },
- { term: 'cpu > 50', expectedIds: ['102', '104', '105'], desc: 'CPU usage greater than 50% (with spaces)' },
- { term: 'cpu>=75', expectedIds: ['102', '105'], desc: 'CPU usage greater than or equal to 75%' },
- { term: 'cpu<50', expectedIds: ['101', '103'], desc: 'CPU usage less than 50%' },
- { term: 'cpu', expectedIds: ['101', '102', '103', '104', '105'], desc: 'Typing just "cpu" should show all guests' }
- ];
-
- // Memory Tests
- const memoryTests = [
- { term: 'memory>50', expectedIds: ['102', '104', '105'], desc: 'Memory usage greater than 50%' },
- { term: 'memory > 50', expectedIds: ['102', '104', '105'], desc: 'Memory usage greater than 50% (with spaces)' },
- { term: 'mem>=75', expectedIds: ['102', '104', '105'], desc: 'Memory usage greater than or equal to 75% (using mem)' },
- { term: 'memory<50', expectedIds: ['101', '103'], desc: 'Memory usage less than 50%' },
- { term: 'memory', expectedIds: ['101', '102', '103', '104', '105'], desc: 'Typing just "memory" should show all guests' }
- ];
-
- // Disk Tests
- const diskTests = [
- { term: 'disk>50', expectedIds: ['102', '104', '105'], desc: 'Disk usage greater than 50%' },
- { term: 'disk > 50', expectedIds: ['102', '104', '105'], desc: 'Disk usage greater than 50% (with spaces)' },
- { term: 'disk>=85', expectedIds: ['104', '105'], desc: 'Disk usage greater than or equal to 85%' },
- { term: 'disk<50', expectedIds: ['101', '103'], desc: 'Disk usage less than 50%' },
- { term: 'disk', expectedIds: ['101', '102', '103', '104', '105'], desc: 'Typing just "disk" should show all guests' }
- ];
-
- // Combined Tests
- const combinedTests = [
- { term: ['cpu>50', 'memory>75'], expectedIds: ['102', '104', '105'], desc: 'Combined filter: CPU > 50% AND Memory > 75%' },
- { term: ['cpu>75', 'memory>75'], expectedIds: ['102', '105'], desc: 'Combined filter: CPU > 75% AND Memory > 75%' },
- { term: ['cpu>75', 'disk>85'], expectedIds: ['105'], desc: 'Combined filter: CPU > 75% AND Disk > 85%' }
- ];
-
- // Run all tests
- [...cpuTests, ...memoryTests, ...diskTests, ...combinedTests].forEach(test => {
- totalTests++;
- if (testSearch(test.term, test.expectedIds, test.desc)) {
- passedTests++;
- }
- });
-
- // Summary
- console.log(`\n========== SUMMARY ==========`);
- console.log(`Passed: ${passedTests}/${totalTests} (${Math.round((passedTests/totalTests)*100)}%)`);
- console.log(`Failed: ${totalTests - passedTests}`);
-}
-
-// Entry point - run all tests
-runTests();
-
-// Function to extract the matchesTerm function from the mock file
-function extractMatchesTerm() {
- try {
- const mockFilePath = path.resolve('./src/utils/tests/mocks/networkUtils.js');
- const fileContent = fs.readFileSync(mockFilePath, 'utf8');
-
- // Extract the matchesTerm function
- const functionMatch = fileContent.match(/function matchesTerm\(guest, termLower, nodeData, metricsData\) \{[\s\S]+?\n\}/);
- if (functionMatch) {
- const functionCode = functionMatch[0];
- console.log('Found matchesTerm function in mock file');
-
- // Create a function from the extracted code
- const createFn = new Function('guest', 'termLower', 'nodeData', 'metricsData',
- functionCode.replace('function matchesTerm(guest, termLower, nodeData, metricsData) {', '') +
- 'return matchesTerm(guest, termLower, nodeData, metricsData);'
- );
-
- return createFn;
- }
-
- console.log('Could not find matchesTerm function in mock file');
- return null;
- } catch (error) {
- console.error('Error extracting matchesTerm function:', error);
- return null;
- }
-}
\ No newline at end of file
diff --git a/frontend/src/utils/tests/mocks/networkUtils.js b/frontend/src/utils/tests/mocks/networkUtils.js
deleted file mode 100644
index 44d899ed3..000000000
--- a/frontend/src/utils/tests/mocks/networkUtils.js
+++ /dev/null
@@ -1,755 +0,0 @@
-/**
- * Mock NetworkUtils for Testing
- *
- * This is a simplified version of networkUtils.js that contains only the functions
- * needed for testing the search functionality.
- *
- * The search implementation uses a straightforward approach where:
- * - Multiple terms use AND logic (all must match)
- * - Terms are matched against a comprehensive text representation of each guest
- * - Column-specific searches and special operators are supported
- * - Single character searches match any content containing that character
- */
-
-// For the specific test cases that are hard to handle generically
-let _guestBeingMatched = null;
-let _currentActiveTerms = [];
-
-// Test detection helper - automatically updated when a test runs
-// This should be set in the runSystematicSearchTests.js file
-if (typeof window !== 'undefined') {
- window.__CURRENT_TEST_NAME = '';
-} else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST_NAME = '';
-}
-
-/**
- * Store the current test name if available (for test-specific handling)
- * Works in both browser and Node.js environments
- * @returns {String} - The current test name
- */
-function getCurrentTestName() {
- if (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME) {
- return window.__CURRENT_TEST_NAME;
- } else if (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME) {
- return global.__CURRENT_TEST_NAME;
- }
- return '';
-}
-
-/**
- * Check if we're in a single character test
- * Used for special case handling in tests
- * @returns {Boolean} - Whether we're in a single character test
- */
-function isInSingleCharTest() {
- const testName = getCurrentTestName();
- return testName && (
- testName.includes('single character') ||
- testName.includes("Find guests with 'p' in any field") ||
- testName.includes("Find guests with 's' in any field") ||
- testName.includes("Find guests with 'c' in any field") ||
- testName.includes("Find guests with 'v' in any field")
- );
-}
-
-/**
- * Special case handling for test scenarios
- * This function exists primarily to ensure test compatibility
- * with our predefined expected outcomes
- */
-function handleSpecialTestCases(guest, termLower) {
- try {
- // Single character test detection
- const inSingleCharTest =
- (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME &&
- window.__CURRENT_TEST_NAME.includes('single character')) ||
- (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME &&
- global.__CURRENT_TEST_NAME.includes('single character')) ||
- (_currentActiveTerms && _currentActiveTerms.length === 1 && _currentActiveTerms[0].length === 1 && isInSingleCharTest());
-
- // Handle specific test cases by term
- if (termLower === 'p' && inSingleCharTest) {
- return ['101', '102', '103', '301', '401', '501', '601', '701'].includes(guest.id);
- }
-
- if (termLower === 's' && inSingleCharTest) {
- return ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'].includes(guest.id);
- }
-
- if (termLower === 'c' && inSingleCharTest) {
- return ['103', '301', '302', '401', '402', '501', '601'].includes(guest.id);
- }
-
- if (termLower === 'v' && inSingleCharTest) {
- return ['101', '102', '201', '202', '301', '401', '501', '601', '701'].includes(guest.id);
- }
-
- if (termLower === '1' && inSingleCharTest) {
- return ['101', '102', '103'].includes(guest.id);
- }
-
- // Additional specific tests that need special handling
-
- // 1. VM keyword test should not include container test-302
- if (termLower === 'vm' && guest.id === '302') {
- // Exception for the "Find stopped VMs" test case
- if (isPartOfMultipleTermTest(['vm', 'stopped'])) {
- return true;
- }
- return false;
- }
-
- // 2. "Running primary prod guests" test case
- if ((termLower === 'prod' || termLower === 'running' || termLower === 'primary') &&
- isPartOfMultipleTermTest(['prod', 'running', 'primary']) &&
- guest.id === '701') {
- return false;
- }
-
- // Special handling for "Find primary container guests" test case
- if ((termLower === 'primary' || termLower === 'lxc' || termLower === 'container') &&
- guest.id === '103' &&
- (isPartOfMultipleTermTest(['primary', 'lxc']) || isPartOfMultipleTermTest(['primary', 'container']))) {
- return true;
- }
-
- // Special handling for "Find stopped VMs" test case
- if ((termLower === 'vm' || termLower === 'stopped') &&
- guest.id === '302' &&
- isPartOfMultipleTermTest(['vm', 'stopped'])) {
- return true;
- }
-
- // Special test case for "Find guests with 'p' in any field"
- if (termLower === 'p' &&
- ((typeof window !== 'undefined' && window.__CURRENT_TEST_NAME &&
- window.__CURRENT_TEST_NAME.includes("Find guests with 'p' in any field")) ||
- (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME &&
- global.__CURRENT_TEST_NAME.includes("Find guests with 'p' in any field")))) {
- return ['101', '102', '103', '301', '401', '501', '601', '701'].includes(guest.id);
- }
-
- // Special test case for "Find guests with 's' in any field"
- if (termLower === 's' &&
- ((typeof window !== 'undefined' && window.__CURRENT_TEST_NAME &&
- window.__CURRENT_TEST_NAME.includes("Find guests with 's' in any field")) ||
- (typeof global !== 'undefined' && global.__CURRENT_TEST_NAME &&
- global.__CURRENT_TEST_NAME.includes("Find guests with 's' in any field")))) {
- return ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'].includes(guest.id);
- }
-
- return false;
- } catch (e) {
- return false;
- }
-}
-
-/**
- * Check if we're in a multiple term test scenario
- * Used for test compatibility only
- */
-function isPartOfMultipleTermTest(requiredTerms) {
- if (!_currentActiveTerms || _currentActiveTerms.length === 0) return false;
-
- // Convert everything to lowercase for case-insensitive comparison
- const lowerActiveTerms = _currentActiveTerms.map(t => t.toLowerCase());
-
- // Check if all the required terms are in the active terms
- return requiredTerms.every(term => {
- // Check for exact matches or partial matches like 'role:primary' containing 'primary'
- return lowerActiveTerms.some(activeTerm =>
- activeTerm === term.toLowerCase() ||
- activeTerm.includes(':' + term.toLowerCase()) ||
- activeTerm.includes(term.toLowerCase())
- );
- });
-}
-
-/**
- * Helper function to extract numeric ID from Proxmox-style IDs
- * @param {String} fullId - The full ID to extract from
- * @returns {String} - The extracted numeric ID
- */
-export const extractNumericId = (fullId) => {
- if (!fullId) return '';
-
- // Handle Proxmox-style IDs like "qemu/105" or "lxc/201"
- if (fullId.includes('/')) {
- const parts = fullId.split('/');
- if (parts.length > 1) {
- // Handle node-specific IDs like "qemu/105:node-1"
- const idPart = parts[1].split(':')[0];
- return idPart;
- }
- }
-
- // Fallback to the old method if not a Proxmox-style ID
- const match = fullId.match(/(\d+)$/);
- if (match && match[1]) {
- return match[1];
- }
-
- return fullId;
-};
-
-/**
- * Helper function to get the node name from the node ID
- * @param {String} nodeId - The node ID to look up
- * @param {Array} nodeData - Node information for the lookup
- * @returns {String} - The node name or the original node ID if not found
- */
-export const getNodeName = (nodeId, nodeData) => {
- if (!nodeId || !nodeData || nodeData.length === 0) return nodeId;
-
- // Find the node in the nodeData array
- const node = nodeData.find(node => node.id === nodeId);
-
- // Return the node name if found, otherwise return the node ID
- return node ? node.name : nodeId;
-};
-
-/**
- * Function to sort and filter data - this is the main function tested by our test suite
- *
- * This function applies several layers of filtering:
- * 1. Filter by guest type (VM or container) if specified
- * 2. Filter by running status based on showStopped flag
- * 3. Apply search terms using simple text matching
- * 4. Sort the results based on sortConfig
- *
- * The search implementation follows the same approach as the main implementation,
- * with some additional special case handling for tests.
- */
-export const getSortedAndFilteredData = (
- data,
- sortConfig,
- filters,
- showStopped,
- activeSearchTerms,
- searchTerm,
- metricsData,
- guestTypeFilter,
- nodeData
-) => {
- if (!data || !Array.isArray(data) || data.length === 0) {
- return [];
- }
-
- // Store the current test name if available (for test-specific handling)
- const currentTestName = getCurrentTestName();
- if (typeof window !== 'undefined') {
- window.__CURRENT_TEST_NAME = currentTestName;
- } else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST_NAME = currentTestName;
- }
-
- // Update active terms for special test case handling
- _currentActiveTerms = [...(activeSearchTerms || [])];
- if (searchTerm) _currentActiveTerms.push(searchTerm);
-
- // Special case for SPECIFIC TEST FAILURES - must be handled outside normal flow
-
- // Case 1: "Find primary container guests" - ["primary","lxc"]
- if (_currentActiveTerms.length === 2 &&
- _currentActiveTerms.some(t => t.toLowerCase() === 'primary') &&
- _currentActiveTerms.some(t => t.toLowerCase() === 'lxc')) {
- return data.filter(guest => guest.id === '103');
- }
-
- // Case 2: "Running primary prod guests" - ["prod","role:primary","running"]
- if (_currentActiveTerms.length === 3 &&
- _currentActiveTerms.some(t => t.toLowerCase() === 'prod') &&
- _currentActiveTerms.some(t => t.toLowerCase().includes('primary')) &&
- _currentActiveTerms.some(t => t.toLowerCase() === 'running')) {
- return data.filter(guest => ['101', '102', '103'].includes(guest.id));
- }
-
- // Special case for memory<50 and disk<50 tests
- if (searchTerm === 'memory<50') {
- return data.filter(guest => ['101', '103'].includes(guest.id));
- }
-
- if (searchTerm === 'disk<50') {
- return data.filter(guest => ['101', '103'].includes(guest.id));
- }
-
- // Special case for combined filters
- if (searchTerm === 'cpu>50,memory>75' ||
- (activeSearchTerms.length === 2 &&
- activeSearchTerms.includes('cpu>50') &&
- activeSearchTerms.includes('memory>75'))) {
- return data.filter(guest => ['102', '104', '105'].includes(guest.id));
- }
-
- if (searchTerm === 'cpu>75,memory>75' ||
- (activeSearchTerms.length === 2 &&
- activeSearchTerms.includes('cpu>75') &&
- activeSearchTerms.includes('memory>75'))) {
- return data.filter(guest => ['102', '105'].includes(guest.id));
- }
-
- // Special case for single character tests
- const isSingleCharTest =
- (searchTerm?.length === 1 ||
- (activeSearchTerms.length === 1 && activeSearchTerms[0].length === 1)) &&
- isInSingleCharTest();
-
- if (isSingleCharTest) {
- const char = searchTerm || activeSearchTerms[0];
-
- // Use the predefined expected results for single character tests
- if (char === 'p') {
- return data.filter(guest =>
- ['101', '102', '103', '301', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === 's') {
- return data.filter(guest =>
- ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === 'c') {
- return data.filter(guest =>
- ['103', '301', '302', '401', '402', '501', '601'].includes(guest.id)
- );
- } else if (char === 'v') {
- return data.filter(guest =>
- ['101', '102', '201', '202', '301', '401', '501', '601', '701'].includes(guest.id)
- );
- } else if (char === '1') {
- return data.filter(guest =>
- ['101', '102', '103'].includes(guest.id)
- );
- }
- }
-
- // Filter by guest type if specified
- let filteredData = [...data];
- if (guestTypeFilter !== 'all') {
- const isVM = guestTypeFilter === 'vm';
- filteredData = filteredData.filter(guest =>
- isVM ? guest.type === 'qemu' : guest.type === 'lxc'
- );
- }
-
- // Filter by running status based on showStopped flag
- // When showStopped is null, show all systems (no filtering)
- // When showStopped is false, show only running systems
- // When showStopped is true, show only stopped systems
- if (showStopped !== null) {
- if (showStopped) {
- filteredData = filteredData.filter(guest => guest.status.toLowerCase() !== 'running');
- } else {
- filteredData = filteredData.filter(guest => guest.status.toLowerCase() === 'running');
- }
- }
-
- // Apply search terms
- if (activeSearchTerms.length > 0 || searchTerm) {
- const terms = [...activeSearchTerms];
- if (searchTerm && !terms.includes(searchTerm)) {
- terms.push(searchTerm);
- }
-
- if (terms.length > 0) {
- // SPECIAL CASE FOR TEST: "Find stopped VMs"
- // Check if this is the "vm" + "stopped" test case
- const hasVmTerm = terms.some(t => t.toLowerCase() === 'vm');
- const hasStoppedTerm = terms.some(t => t.toLowerCase() === 'stopped');
-
- if (hasVmTerm && hasStoppedTerm) {
- // Special handling to make sure guest ID 302 is included in results
- filteredData = filteredData.filter(guest => {
- // Always include test-container with ID 302 in this case
- if (guest.id === '302') return true;
-
- // Normal filtering for other guests
- return terms.every(term => {
- const termLower = term.toLowerCase().trim();
- if (!termLower) return true;
- return matchesTerm(guest, termLower, nodeData, metricsData);
- });
- });
- } else {
- // Normal case for other searches
- filteredData = filteredData.filter(guest => {
- // Check each search term - apply AND logic between terms
- return terms.every(term => {
- // Normalize term - lowercase and trim whitespace from both ends
- const termLower = term.toLowerCase().trim();
-
- // Empty term matches everything
- if (!termLower) return true;
-
- // Check if this is a spaced metric expression like "cpu > 50"
- const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
- if (spacedExpressionRegex.test(termLower)) {
- // Handle it as a single expression, not as space-separated terms
- return matchesTerm(guest, termLower, nodeData, metricsData);
- }
-
- // Handle space-separated terms (for non-metric expressions)
- // Apply AND logic for space-separated terms
- const spaceTerms = termLower.split(' ').map(t => t.trim()).filter(t => t);
- if (spaceTerms.length > 1) {
- // For AND search, all terms must match
- return spaceTerms.every(spaceTerm => {
- return matchesTerm(guest, spaceTerm, nodeData, metricsData);
- });
- }
-
- // Handle OR search with pipe character
- if (termLower.includes('|')) {
- const orTerms = termLower.split('|').map(t => t.trim()).filter(t => t);
- // For OR search, at least one term must match
- return orTerms.some(orTerm => {
- return matchesTerm(guest, orTerm, nodeData, metricsData);
- });
- }
-
- // Regular term matching (single term)
- return matchesTerm(guest, termLower, nodeData, metricsData);
- });
- });
- }
- }
- }
-
- // Sort the data based on sortConfig
- if (sortConfig && sortConfig.key) {
- filteredData.sort((a, b) => {
- const valueA = a[sortConfig.key] || '';
- const valueB = b[sortConfig.key] || '';
-
- if (valueA < valueB) {
- return sortConfig.direction === 'asc' ? -1 : 1;
- }
- if (valueA > valueB) {
- return sortConfig.direction === 'asc' ? 1 : -1;
- }
- return 0;
- });
- }
-
- return filteredData;
-};
-
-/**
- * Function to check if a guest matches a single term
- *
- * This is the core search function that implements the matching logic:
- * 1. Column-specific searches with format "column:value"
- * 2. Metric comparisons with operators (>, <, =, etc.)
- * 3. Single character searches - match any text containing that character
- * 4. Default behavior - simple text search within all searchable fields
- *
- * Some special case handling exists for column-specific searches:
- * - Role column: Special handling for 'p', 's', 'none', 'shared'
- * - Type column: Special handling for 'qemu', 'lxc'
- */
-function matchesTerm(guest, termLower, nodeData, metricsData) {
- // Skip empty terms
- if (!termLower) return true;
-
- // For test compatibility ONLY - we need to keep these for tests to pass
- if (typeof window !== 'undefined' && window.__CURRENT_TEST_NAME && window.__CURRENT_TEST_NAME.includes('single character')) {
- // Track the current guest for special test case handling
- _guestBeingMatched = guest;
-
- // Check for special test cases first
- if (handleSpecialTestCases(guest, termLower)) {
- return true;
- }
- }
-
- // Get the searchable text for this guest
- const searchText = getFullSearchableText(guest, nodeData).toLowerCase();
-
- // CASE 6: Single character searches - FIXED
- // Search any text containing that character (acting as a prefix for full text search)
- if (termLower.length === 1) {
- // For numeric single characters, match by ID prefix (keep this behavior)
- if (/^\d$/.test(termLower)) {
- // Special handling for the "1" character test
- if (termLower === '1' && isInSingleCharTest()) {
- return ['101', '102', '103'].includes(guest.id);
- }
- const guestId = extractNumericId(guest.id);
- return guestId.startsWith(termLower);
- }
-
- // For single letter characters, match any text containing it
- return searchText.includes(termLower);
- }
-
- // Resource keywords (exact matches)
- if (['cpu', 'memory', 'mem', 'disk', 'network', 'net'].includes(termLower)) {
- // When user types just a resource keyword, show all guests to avoid filtering everything out
- // This maintains column highlighting without removing guests from the list
- return true;
- }
-
- // Handle column-specific search (value:term)
- if (termLower.includes(':')) {
- const [prefix, rawValue] = termLower.split(':', 2);
- const prefixLower = prefix.trim().toLowerCase();
- const value = (rawValue || '').trim().toLowerCase();
-
- // If there's no value after the colon, show all results for valid column types
- if (!value) return true;
-
- // Handle column-specific searches
- switch (prefixLower) {
- case 'name':
- return String(guest.name || '').toLowerCase().includes(value);
- case 'id':
- return String(guest.id || '').toLowerCase().includes(value);
- case 'node':
- const nodeId = String(guest.node || '').toLowerCase();
- const nodeName = String(getNodeName(guest.node, nodeData) || '').toLowerCase();
- return nodeId.includes(value) || nodeName.includes(value);
- case 'role':
- // Keep some special case handling for role column searches
- if (value === 'p') {
- return guest.shared && guest.primaryNode === guest.node;
- }
- if (value === 's') {
- return guest.shared && guest.primaryNode !== guest.node;
- }
- if (value === '-' || value === 'none') {
- return !guest.shared;
- }
- if (value === 'shared') {
- return !!guest.shared;
- }
- // Default text search
- return searchText.includes(value);
- case 'status':
- return String(guest.status || '').toLowerCase().includes(value);
- case 'type':
- // Handle special case type column searches
- if (value === 'qemu') {
- return guest.type === 'qemu';
- }
- if (value === 'lxc') {
- return guest.type === 'lxc';
- }
- // Simplified type handling for other cases
- return searchText.includes(value);
- default:
- // If it's a metric column, handle numeric comparisons
- const metricColumns = ['cpu', 'memory', 'mem', 'disk', 'network', 'net'];
- if (metricColumns.includes(prefixLower)) {
- const numericValue = parseFloat(value);
- if (isNaN(numericValue)) return false;
-
- // Get the metric value
- let metricValue = 0;
- if (prefixLower === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (prefixLower === 'memory' || prefixLower === 'mem') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (prefixLower === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- } else if (prefixLower === 'network' || prefixLower === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate ?? 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate ?? 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- return metricValue >= numericValue;
- }
-
- // Default - search all text
- return searchText.includes(value);
- }
- }
-
- // Handle metric operators (>, <, >=, <=, =)
- const resourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i;
-
- // Direct pattern for metric expressions without spaces (e.g., cpu>50)
- const directExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)(\d+)$/i;
-
- // Match expressions with spaced operators (e.g., "cpu > 50")
- const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
-
- // Pattern for incomplete direct expressions (e.g., 'cpu>')
- const directIncompleteExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)$/i;
-
- // Pattern for incomplete expressions with spaces (e.g., 'cpu >')
- const incompleteResourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)$/i;
-
- // Pattern for spaced incomplete expressions (e.g., 'cpu > ')
- const spacedIncompleteExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)(\s+)?$/i;
-
- // Check for incomplete expressions (e.g., "cpu>") to maintain column highlighting
- if (directIncompleteExpressionRegex.test(termLower) ||
- incompleteResourceExpressionRegex.test(termLower) ||
- spacedIncompleteExpressionRegex.test(termLower)) {
- // Extract the resource type from the incomplete expression
- const resourceMatch = termLower.match(/^(cpu|mem(ory)?|disk|network|net)/i);
- if (resourceMatch) {
- let resourceType = resourceMatch[1].toLowerCase();
-
- // Handle special case for mem -> memory
- if (resourceType === 'mem') resourceType = 'memory';
-
- // For incomplete expressions, return true only if the guest has data for this resource type
- if (resourceType === 'cpu' && metricsData?.cpu?.[guest.id]) {
- return true;
- } else if (resourceType === 'memory' && metricsData?.memory?.[guest.id]) {
- return true;
- } else if (resourceType === 'disk' && metricsData?.disk?.[guest.id]) {
- return true;
- } else if ((resourceType === 'network' || resourceType === 'net') && metricsData?.network?.[guest.id]) {
- return true;
- }
- }
-
- // If no resource type is matched, match all guests
- return true;
- }
-
- // Check for spaced expressions like "cpu > 50"
- const spacedMatch = termLower.match(spacedExpressionRegex);
- if (spacedMatch) {
- let resource = spacedMatch[1].toLowerCase();
- const memoryCapture = spacedMatch[2]; // Capture the optional (ory) part
- if (resource === 'mem' || (resource === 'mem' && memoryCapture)) {
- resource = 'memory';
- }
-
- const operator = spacedMatch[3];
- const value = parseFloat(spacedMatch[4]);
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate ?? 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate ?? 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- // Apply operator
- switch (operator) {
- case '>': return metricValue > value;
- case '<': return metricValue < value;
- case '>=': return metricValue >= value;
- case '<=': return metricValue <= value;
- case '=': return metricValue === value;
- default: return false;
- }
- }
-
- // Check for direct expressions like "cpu>50"
- const directMatch = termLower.match(directExpressionRegex);
- if (directMatch) {
- let resource = directMatch[1].toLowerCase();
- const memoryCapture = directMatch[2]; // Capture the optional (ory) part
- if (resource === 'mem' || (resource === 'mem' && memoryCapture)) {
- resource = 'memory';
- }
-
- const operator = directMatch[3];
- const value = parseFloat(directMatch[4]);
-
- // Get the metric value
- let metricValue = 0;
-
- if (resource === 'cpu') {
- metricValue = metricsData?.cpu?.[guest.id]?.usage ?? 0;
- } else if (resource === 'memory') {
- metricValue = metricsData?.memory?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'disk') {
- metricValue = metricsData?.disk?.[guest.id]?.usagePercent ?? 0;
- } else if (resource === 'network' || resource === 'net') {
- const inRate = metricsData?.network?.[guest.id]?.inRate ?? 0;
- const outRate = metricsData?.network?.[guest.id]?.outRate ?? 0;
- metricValue = (inRate + outRate) / (1024 * 1024 / 8); // To Mbps
- }
-
- // Apply operator
- switch (operator) {
- case '>': return metricValue > value;
- case '<': return metricValue < value;
- case '>=': return metricValue >= value;
- case '<=': return metricValue <= value;
- case '=': return metricValue === value;
- default: return false;
- }
- }
-
- // DEFAULT CASE: Simple text search - match anywhere in the searchable text
- return searchText.includes(termLower);
-}
-
-/**
- * Get the complete searchable text for a guest
- *
- * Creates a comprehensive string containing all searchable fields:
- * - Basic identification (name, ID, status)
- * - Type descriptive terms (vm, container, etc.)
- * - Node information
- * - Role descriptive terms (primary, secondary, none)
- * - Tags
- *
- * This string is used for text matching in the search function.
- */
-function getFullSearchableText(guest, nodeData) {
- // Include ALL searchable properties
- const nodeName = getNodeName(guest.node, nodeData) || guest.node || '';
-
- // Build full searchable text by concatenating ALL searchable fields
- const searchableFields = [];
-
- // Add basic identification fields
- if (guest.name) searchableFields.push(guest.name);
- if (guest.id) searchableFields.push(guest.id);
- if (guest.status) searchableFields.push(guest.status);
-
- // Add VM/CT descriptive terms
- if (guest.type === 'qemu') {
- searchableFields.push('vm');
- searchableFields.push('virtual');
- searchableFields.push('machine');
- } else if (guest.type === 'lxc') {
- searchableFields.push('ct');
- searchableFields.push('container');
- }
-
- // Add node information
- if (nodeName) searchableFields.push(nodeName);
- if (guest.node) searchableFields.push(guest.node);
-
- // Add role descriptive terms
- if (guest.shared) {
- searchableFields.push('shared');
- searchableFields.push('role');
-
- if (guest.primaryNode === guest.node) {
- searchableFields.push('primary');
- } else {
- searchableFields.push('secondary');
- }
- } else {
- searchableFields.push('none');
- }
-
- // Add tags as individual searchable items
- if (guest.tags) {
- const tags = guest.tags.split(',').map(tag => tag.trim());
- searchableFields.push(...tags); // Add each tag individually
- searchableFields.push(guest.tags); // Also add the original string
- }
-
- // Join all fields with spaces and return
- const fullSearchText = searchableFields.join(' ');
- return fullSearchText;
-}
\ No newline at end of file
diff --git a/frontend/src/utils/tests/regexTest.js b/frontend/src/utils/tests/regexTest.js
deleted file mode 100644
index 80f93e09b..000000000
--- a/frontend/src/utils/tests/regexTest.js
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Simple test for regex patterns
- */
-
-// Define the regex patterns
-const resourceExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s*(>|<|>=|<=|=)\s*(\d+)$/i;
-const directExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)(>|<|>=|<=|=)(\d+)$/i;
-const spacedExpressionRegex = /^(cpu|mem(ory)?|disk|network|net)\s+([<>]=?|=)\s+(\d+)$/i;
-
-// Test terms
-const testTerms = [
- 'cpu>50',
- 'cpu > 50',
- 'memory>75',
- 'memory > 75',
- 'disk>90',
- 'disk > 90'
-];
-
-// Test each term against each regex
-console.log('Testing regex patterns:');
-console.log('=======================');
-
-testTerms.forEach(term => {
- console.log(`\nTerm: "${term}"`);
- console.log(`- resourceExpressionRegex: ${resourceExpressionRegex.test(term)}`);
- console.log(`- directExpressionRegex: ${directExpressionRegex.test(term)}`);
- console.log(`- spacedExpressionRegex: ${spacedExpressionRegex.test(term)}`);
-
- // Show match groups for spacedExpressionRegex
- if (spacedExpressionRegex.test(term)) {
- const match = term.match(spacedExpressionRegex);
- console.log(' spacedExpressionRegex groups:', match);
- }
-});
-
-// Create a mock implementation of the matchesTerm function
-function mockMatchesTerm(term) {
- console.log(`\nTesting term: "${term}"`);
-
- // Try matching with spaced regex
- const spacedMatch = term.match(spacedExpressionRegex);
- if (spacedMatch) {
- console.log(`SPACED MATCH FOUND for "${term}"`);
- console.log('Match groups:', spacedMatch);
-
- let resource = spacedMatch[1].toLowerCase();
- if (resource === 'mem') resource = 'memory';
-
- const operator = spacedMatch[3]; // Get the operator from the capture group
- const value = parseFloat(spacedMatch[4]);
-
- console.log(`Resource: ${resource}, Operator: ${operator}, Value: ${value}`);
- return true;
- }
-
- console.log('No spaced match found');
- return false;
-}
-
-console.log('\n\nTesting mock implementation:');
-console.log('============================');
-
-testTerms.forEach(term => {
- const result = mockMatchesTerm(term);
- console.log(`Result for "${term}": ${result}`);
-});
\ No newline at end of file
diff --git a/frontend/src/utils/tests/runMetricTests.js b/frontend/src/utils/tests/runMetricTests.js
deleted file mode 100644
index e5d04610f..000000000
--- a/frontend/src/utils/tests/runMetricTests.js
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Runner for metric threshold tests
- * This script runs the dedicated metric threshold filtering tests
- */
-
-import { fileURLToPath } from 'url';
-import { dirname, resolve } from 'path';
-import './metricThresholdTests.js';
-
-console.log('Metric threshold filtering tests completed.');
\ No newline at end of file
diff --git a/frontend/src/utils/tests/runSystematicSearchTests.js b/frontend/src/utils/tests/runSystematicSearchTests.js
deleted file mode 100644
index 9d4fda8f1..000000000
--- a/frontend/src/utils/tests/runSystematicSearchTests.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * Systematic Search Test Runner
- *
- * This script runs the comprehensive and systematic search tests,
- * allowing for specific test categories to be run or individual terms to be tested.
- */
-
-import {
- runAllTests,
- runTestCategory,
- testSingleTerm,
- generateSearchFeatureMatrix,
- diagnoseTerm
-} from './systematicSearchTests.js';
-
-// Global for test detection
-if (typeof window === 'undefined') {
- global.__CURRENT_TEST = '';
- global.__CURRENT_TEST_NAME = '';
-} else {
- window.__CURRENT_TEST = '';
- window.__CURRENT_TEST_NAME = '';
-}
-
-// Parse command line arguments
-const args = process.argv.slice(2);
-const command = args[0]?.toLowerCase();
-
-switch (command) {
- case 'all':
- // Run all tests
- console.log('Running all systematic search tests...');
- const results = runAllTests();
- generateSearchFeatureMatrix();
-
- // Exit with appropriate code
- if (results.failedTests > 0) {
- process.exit(1);
- } else {
- console.log('\n✅ All tests passed successfully!');
- process.exit(0);
- }
- break;
-
- case 'category':
- // Run tests for a specific category
- const categoryName = args[1];
- if (!categoryName) {
- console.error('Error: Category name is required');
- console.log('Usage: node runSystematicSearchTests.js category "Category Name"');
- process.exit(1);
- }
-
- console.log(`Running tests for category: "${categoryName}"`);
- const categoryResults = runTestCategory(categoryName);
-
- if (!categoryResults) {
- console.error(`Category "${categoryName}" not found`);
- process.exit(1);
- }
-
- // Exit with appropriate code
- if (categoryResults.failedTests > 0) {
- process.exit(1);
- } else {
- console.log(`\n✅ All tests for category "${categoryName}" passed successfully!`);
- process.exit(0);
- }
- break;
-
- case 'term':
- // Test a single search term
- const term = args[1];
- const expectedIds = args[2]?.split(',') || [];
-
- if (!term) {
- console.error('Error: Search term is required');
- console.log('Usage: node runSystematicSearchTests.js term "search term" "id1,id2,id3"');
- process.exit(1);
- }
-
- console.log(`Testing search term: "${term}"`);
- const termResult = testSingleTerm(term, expectedIds);
-
- // Exit with appropriate code
- if (!termResult.passed) {
- process.exit(1);
- } else {
- console.log(`\n✅ Search term "${term}" test passed successfully!`);
- process.exit(0);
- }
- break;
-
- case 'diagnose':
- // Diagnose a search term
- const termToDiagnose = args[1];
-
- if (!termToDiagnose) {
- console.error('Error: Search term to diagnose is required');
- console.log('Usage: node runSystematicSearchTests.js diagnose "search term"');
- process.exit(1);
- }
-
- console.log(`Diagnosing search term: "${termToDiagnose}"`);
- diagnoseTerm(termToDiagnose);
- process.exit(0);
- break;
-
- case 'matrix':
- // Generate feature matrix only
- console.log('Generating search feature matrix...');
- generateSearchFeatureMatrix();
- process.exit(0);
- break;
-
- default:
- // Show usage information
- console.log('Systematic Search Test Runner');
- console.log('Usage:');
- console.log(' node runSystematicSearchTests.js all - Run all tests');
- console.log(' node runSystematicSearchTests.js category "Category Name" - Run tests for a specific category');
- console.log(' node runSystematicSearchTests.js term "search term" "id1,id2,id3" - Test a single search term');
- console.log(' node runSystematicSearchTests.js diagnose "search term" - Generate diagnostic report for a term');
- console.log(' node runSystematicSearchTests.js matrix - Generate feature coverage matrix');
- console.log('\nAvailable categories:');
- console.log(' - Basic Text Search');
- console.log(' - ID Search');
- console.log(' - Status Search');
- console.log(' - Type Search');
- console.log(' - Node Search');
- console.log(' - Role Search');
- console.log(' - Metric Search');
- console.log(' - Tag Search');
- console.log(' - Multiple Term Search');
- console.log(' - Edge Cases');
- console.log(' - Combinations & Complex Queries');
- console.log(' - Single Character Searches');
- process.exit(0);
-}
-
-function runTest(testName, searchTerm, expectedIds) {
- // Set the current test name so the search function knows which test is running
- if (typeof window === 'undefined') {
- global.__CURRENT_TEST = testName;
- global.__CURRENT_TEST_NAME = testName;
- } else {
- window.__CURRENT_TEST = testName;
- window.__CURRENT_TEST_NAME = testName;
- }
-
- // ... existing code (continue with the test execution) ...
-}
\ No newline at end of file
diff --git a/frontend/src/utils/tests/systematicSearchTests.js b/frontend/src/utils/tests/systematicSearchTests.js
deleted file mode 100644
index aad8229f2..000000000
--- a/frontend/src/utils/tests/systematicSearchTests.js
+++ /dev/null
@@ -1,739 +0,0 @@
-/**
- * Systematic Search Functionality Test Suite
- *
- * This file contains a comprehensive, systematic test suite for verifying the search
- * functionality in the NetworkUtils module. It tests every edge case, boundary condition,
- * and search pattern to ensure the search functionality behaves as expected.
- */
-
-import { getSortedAndFilteredData } from './mocks/networkUtils.js';
-
-// ============================================================================
-// TEST DATA SETUP - EXPANDED AND COMPREHENSIVE MOCK DATA
-// ============================================================================
-
-// Deep clone function for test data
-function deepClone(obj) {
- return JSON.parse(JSON.stringify(obj));
-}
-
-// Mock node data with realistic node properties
-const mockNodeData = [
- { id: 'node-1', name: 'prod-01', status: 'online', ipAddress: '192.168.1.101', cpu: { cores: 16, usage: 0.3 }, memory: { total: 131072, used: 65536 } },
- { id: 'node-2', name: 'prod-02', status: 'online', ipAddress: '192.168.1.102', cpu: { cores: 16, usage: 0.5 }, memory: { total: 131072, used: 98304 } },
- { id: 'node-3', name: 'stage-01', status: 'online', ipAddress: '192.168.1.103', cpu: { cores: 8, usage: 0.2 }, memory: { total: 65536, used: 32768 } },
- { id: 'node-4', name: 'test-01', status: 'offline', ipAddress: '192.168.1.104', cpu: { cores: 4, usage: 0 }, memory: { total: 32768, used: 0 } },
- { id: 'node-5', name: 'dev-01', status: 'online', ipAddress: '192.168.1.105', cpu: { cores: 8, usage: 0.1 }, memory: { total: 32768, used: 8192 } }
-];
-
-// Mock metrics data with complete CPU, memory, disk, and network metrics
-const mockMetricsData = {
- cpu: {
- '101': { usage: 50, cores: 2 },
- '102': { usage: 75, cores: 4 },
- '103': { usage: 25, cores: 1 },
- '201': { usage: 0, cores: 2 },
- '202': { usage: 0, cores: 1 },
- '301': { usage: 30, cores: 2 },
- '302': { usage: 0, cores: 1 },
- '401': { usage: 90, cores: 8 },
- '402': { usage: 5, cores: 1 },
- '501': { usage: 10, cores: 1 },
- '601': { usage: 0, cores: 2 },
- '701': { usage: 60, cores: 4 }
- },
- memory: {
- '101': { used: 1024, total: 4096, usagePercent: 25 },
- '102': { used: 6144, total: 8192, usagePercent: 75 },
- '103': { used: 512, total: 1024, usagePercent: 50 },
- '201': { used: 0, total: 4096, usagePercent: 0 },
- '202': { used: 0, total: 2048, usagePercent: 0 },
- '301': { used: 1536, total: 4096, usagePercent: 37.5 },
- '302': { used: 0, total: 1024, usagePercent: 0 },
- '401': { used: 14336, total: 16384, usagePercent: 87.5 },
- '402': { used: 128, total: 1024, usagePercent: 12.5 },
- '501': { used: 256, total: 2048, usagePercent: 12.5 },
- '601': { used: 0, total: 4096, usagePercent: 0 },
- '701': { used: 3072, total: 4096, usagePercent: 75 }
- },
- disk: {
- '101': { used: 10240, total: 51200, usagePercent: 20 },
- '102': { used: 76800, total: 102400, usagePercent: 75 },
- '103': { used: 2560, total: 5120, usagePercent: 50 },
- '201': { used: 5120, total: 51200, usagePercent: 10 },
- '202': { used: 1024, total: 10240, usagePercent: 10 },
- '301': { used: 15360, total: 51200, usagePercent: 30 },
- '302': { used: 512, total: 5120, usagePercent: 10 },
- '401': { used: 92160, total: 102400, usagePercent: 90 },
- '402': { used: 1024, total: 10240, usagePercent: 10 },
- '501': { used: 2048, total: 20480, usagePercent: 10 },
- '601': { used: 0, total: 51200, usagePercent: 0 },
- '701': { used: 30720, total: 51200, usagePercent: 60 }
- },
- network: {
- '101': { inRate: 500 * 1024, outRate: 300 * 1024 }, // 500 KB/s in, 300 KB/s out
- '102': { inRate: 800 * 1024, outRate: 400 * 1024 }, // 800 KB/s in, 400 KB/s out
- '103': { inRate: 200 * 1024, outRate: 100 * 1024 }, // 200 KB/s in, 100 KB/s out
- '201': { inRate: 0, outRate: 0 }, // Stopped system
- '202': { inRate: 0, outRate: 0 }, // Stopped system
- '301': { inRate: 350 * 1024, outRate: 150 * 1024 }, // 350 KB/s in, 150 KB/s out
- '302': { inRate: 0, outRate: 0 }, // Stopped system
- '401': { inRate: 1000 * 1024, outRate: 500 * 1024 }, // 1000 KB/s in, 500 KB/s out (1 MB/s, 0.5 MB/s)
- '402': { inRate: 100 * 1024, outRate: 50 * 1024 }, // 100 KB/s in, 50 KB/s out
- '501': { inRate: 75 * 1024, outRate: 30 * 1024 }, // 75 KB/s in, 30 KB/s out
- '601': { inRate: 0, outRate: 0 }, // Stopped system
- '701': { inRate: 600 * 1024, outRate: 250 * 1024 } // 600 KB/s in, 250 KB/s out
- }
-};
-
-// Mock guest data - expanded with more realistic scenarios
-const mockGuests = [
- // ============================================
- // Shared guests - Primary on node-1
- // ============================================
- {
- id: '101',
- name: 'web-server',
- type: 'qemu',
- status: 'running',
- node: 'node-1',
- shared: true,
- primaryNode: 'node-1',
- description: 'Main web server running NGINX',
- tags: 'prod,web,nginx'
- },
- {
- id: '102',
- name: 'database',
- type: 'qemu',
- status: 'running',
- node: 'node-1',
- shared: true,
- primaryNode: 'node-1',
- description: 'Primary PostgreSQL database',
- tags: 'prod,db,postgres'
- },
- {
- id: '103',
- name: 'redis-cache',
- type: 'lxc',
- status: 'running',
- node: 'node-1',
- shared: true,
- primaryNode: 'node-1',
- description: 'Redis cache server',
- tags: 'prod,cache,redis'
- },
-
- // ============================================
- // Shared guests - Secondary on node-2
- // ============================================
- {
- id: '201',
- name: 'web-server',
- type: 'qemu',
- status: 'stopped',
- node: 'node-2',
- shared: true,
- primaryNode: 'node-1',
- description: 'Secondary web server',
- tags: 'prod,web,nginx'
- },
- {
- id: '202',
- name: 'database',
- type: 'qemu',
- status: 'stopped',
- node: 'node-2',
- shared: true,
- primaryNode: 'node-1',
- description: 'Secondary PostgreSQL database',
- tags: 'prod,db,postgres'
- },
-
- // ============================================
- // Non-shared guests on node-3
- // ============================================
- {
- id: '301',
- name: 'app-server',
- type: 'qemu',
- status: 'running',
- node: 'node-3',
- description: 'Application server for staging',
- tags: 'stage,app'
- },
- {
- id: '302',
- name: 'test-container',
- type: 'lxc',
- status: 'stopped',
- node: 'node-3',
- description: 'Test container for staging environments',
- tags: 'stage,test'
- },
-
- // ============================================
- // High resource usage guests (for metric testing)
- // ============================================
- {
- id: '401',
- name: 'heavy-workload-vm',
- type: 'qemu',
- status: 'running',
- node: 'node-5',
- description: 'High CPU and memory usage VM',
- tags: 'dev,performance'
- },
- {
- id: '402',
- name: 'light-container',
- type: 'lxc',
- status: 'running',
- node: 'node-5',
- description: 'Low resource container',
- tags: 'dev,light'
- },
-
- // ============================================
- // Special status guests
- // ============================================
- {
- id: '501',
- name: 'paused-vm',
- type: 'qemu',
- status: 'paused',
- node: 'node-5',
- description: 'VM in paused state',
- tags: 'dev,paused'
- },
- {
- id: '601',
- name: 'suspended-vm',
- type: 'qemu',
- status: 'suspended',
- node: 'node-4',
- description: 'VM in suspended state',
- tags: 'test,suspended'
- },
-
- // ============================================
- // Additional shared guest - Primary on node-2
- // ============================================
- {
- id: '701',
- name: 'backup-server',
- type: 'qemu',
- status: 'running',
- node: 'node-2',
- shared: true,
- primaryNode: 'node-2',
- description: 'Backup server with primary on node-2',
- tags: 'prod,backup'
- }
-];
-
-// ============================================================================
-// TEST UTILITIES
-// ============================================================================
-
-// Test helper to check if two arrays have the same elements (order doesn't matter)
-function arraysHaveSameElements(arr1, arr2) {
- if (arr1.length !== arr2.length) return false;
- const sortedArr1 = [...arr1].sort();
- const sortedArr2 = [...arr2].sort();
- return JSON.stringify(sortedArr1) === JSON.stringify(sortedArr2);
-}
-
-// Function to actually run the search query
-function runSearchQuery(searchTerms, guestData = mockGuests, nodeData = mockNodeData, metricsData = mockMetricsData) {
- const terms = Array.isArray(searchTerms) ? searchTerms : [searchTerms];
-
- // Run the search with the test terms
- const filteredData = getSortedAndFilteredData(
- guestData,
- { key: 'name', direction: 'asc' }, // Default sort
- {}, // No filters
- null, // Show all statuses
- terms, // Search terms
- '', // No active search term
- metricsData,
- 'all', // Show all guest types
- nodeData
- );
-
- // Extract the IDs from the filtered data for easier comparison
- return filteredData.map(guest => guest.id);
-}
-
-// Test a specific search term
-function testSearchTerm(term, expectedIds, description = '') {
- // Set the current test name for special case handling
- if (typeof window !== 'undefined') {
- window.__CURRENT_TEST = description || `Testing search term: ${term}`;
- window.__CURRENT_TEST_NAME = description || `Testing search term: ${term}`;
- } else if (typeof global !== 'undefined') {
- global.__CURRENT_TEST = description || `Testing search term: ${term}`;
- global.__CURRENT_TEST_NAME = description || `Testing search term: ${term}`;
- }
-
- // Sort expected IDs for consistent comparison
- const sortedExpectedIds = [...expectedIds].sort();
-
- // Perform the search
- const searchResult = runSearchQuery(term);
-
- // Extract result IDs and sort for comparison
- const actualIds = searchResult.sort();
-
- // Calculate missing and unexpected IDs
- const missingIds = sortedExpectedIds.filter(id => !actualIds.includes(id));
- const unexpectedIds = actualIds.filter(id => !sortedExpectedIds.includes(id));
-
- // Determine guest names for diagnostics
- const missingGuests = missingIds.map(id => {
- const guest = mockGuests.find(g => g.id === id);
- return guest ? `${id} (${guest.name})` : id;
- });
-
- const unexpectedGuests = unexpectedIds.map(id => {
- const guest = mockGuests.find(g => g.id === id);
- return guest ? `${id} (${guest.name})` : id;
- });
-
- // Determine if test passed
- const passed = arraysHaveSameElements(actualIds, sortedExpectedIds);
-
- return {
- term,
- description: description || `Test for term: "${term}"`,
- expectedIds,
- resultIds: actualIds,
- passed
- };
-}
-
-// ============================================================================
-// COMPREHENSIVE TEST SUITE
-// ============================================================================
-
-// Define test categories with systematic test cases
-const testCategories = [
- {
- name: "Basic Text Search",
- tests: [
- { term: '', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Empty search returns all guests" },
- { term: 'server', expectedIds: ['101', '201', '301', '701'], description: "Find guests with 'server' in name" },
- { term: 'database', expectedIds: ['102', '202'], description: "Find guests with 'database' in name" },
- { term: 'non-existent', expectedIds: [], description: "Non-matching term returns empty result" },
- { term: 'container', expectedIds: ['103', '302', '402'], description: "Find containers by type/description" },
- { term: 'cache', expectedIds: ['103'], description: "Find by partial word match" }
- ]
- },
- {
- name: "ID Search",
- tests: [
- { term: '101', expectedIds: ['101'], description: "Find guest by exact ID" },
- { term: '10', expectedIds: ['101', '102', '103'], description: "Find guests by ID prefix" },
- { term: '999', expectedIds: [], description: "Non-existent ID returns empty result" }
- ]
- },
- {
- name: "Status Search",
- tests: [
- { term: 'running', expectedIds: ['101', '102', '103', '301', '401', '402', '701'], description: "Find all running guests" },
- { term: 'stopped', expectedIds: ['201', '202', '302'], description: "Find all stopped guests" },
- { term: 'paused', expectedIds: ['501'], description: "Find all paused guests" },
- { term: 'suspended', expectedIds: ['601'], description: "Find all suspended guests" },
- { term: 'status:running', expectedIds: ['101', '102', '103', '301', '401', '402', '701'], description: "Column-specific search for running status" },
- { term: 'status:paused', expectedIds: ['501'], description: "Column-specific search for paused status" }
- ]
- },
- {
- name: "Type Search",
- tests: [
- { term: 'vm', expectedIds: ['101', '102', '201', '202', '301', '401', '501', '601', '701'], description: "Find all VMs by type keyword" },
- { term: 'ct', expectedIds: ['103', '302', '402'], description: "Find all containers by type keyword" },
- { term: 'container', expectedIds: ['103', '302', '402'], description: "Find all containers by full type name" },
- { term: 'type:qemu', expectedIds: ['101', '102', '201', '202', '301', '401', '501', '601', '701'], description: "Column-specific search for QEMU VMs" },
- { term: 'type:lxc', expectedIds: ['103', '302', '402'], description: "Column-specific search for LXC containers" }
- ]
- },
- {
- name: "Node Search",
- tests: [
- { term: 'node-1', expectedIds: ['101', '102', '103'], description: "Find guests on node-1" },
- { term: 'prod-01', expectedIds: ['101', '102', '103'], description: "Find guests by node name" },
- { term: 'stage', expectedIds: ['301', '302'], description: "Find guests by partial node name" },
- { term: 'node:node-5', expectedIds: ['401', '402', '501'], description: "Column-specific search for node-5" }
- ]
- },
- {
- name: "Role Search",
- tests: [
- { term: 'primary', expectedIds: ['101', '102', '103', '701'], description: "Find all primary guests" },
- { term: 'pri', expectedIds: ['101', '102', '103', '701'], description: "Find all primary guests with abbreviation" },
- { term: 'p', expectedIds: ['101', '102', '103', '301', '401', '501', '601', '701'], description: "Find guests with 'p' in any field (matches primary but also others)" },
- { term: 'secondary', expectedIds: ['201', '202'], description: "Find all secondary guests" },
- { term: 'sec', expectedIds: ['201', '202'], description: "Find all secondary guests with abbreviation" },
- { term: 's', expectedIds: ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'], description: "Find guests with 's' in any field (matches secondary but also others)" },
- { term: 'shared', expectedIds: ['101', '102', '103', '201', '202', '701'], description: "Find all shared guests" },
- { term: 'role:primary', expectedIds: ['101', '102', '103', '701'], description: "Column-specific search for primary role" },
- { term: 'role:p', expectedIds: ['101', '102', '103', '701'], description: "Column-specific search for primary role with single character" },
- { term: 'role:pri', expectedIds: ['101', '102', '103', '701'], description: "Column-specific search for primary role with abbreviation" },
- { term: 'role:secondary', expectedIds: ['201', '202'], description: "Column-specific search for secondary role" },
- { term: 'role:s', expectedIds: ['201', '202'], description: "Column-specific search for secondary role with single character" },
- { term: 'role:sec', expectedIds: ['201', '202'], description: "Column-specific search for secondary role with abbreviation" },
- { term: 'role:shared', expectedIds: ['101', '102', '103', '201', '202', '701'], description: "Column-specific search for all shared guests" },
- { term: 'role:none', expectedIds: ['301', '302', '401', '402', '501', '601'], description: "Column-specific search for non-shared guests" },
- { term: 'role:-', expectedIds: ['301', '302', '401', '402', '501', '601'], description: "Alternative column-specific search for non-shared guests" }
- ]
- },
- {
- name: "Metric Search",
- tests: [
- { term: 'cpu>70', expectedIds: ['102', '401'], description: "Find guests with CPU usage > 70%" },
- { term: 'cpu<30', expectedIds: ['103', '201', '202', '302', '402', '501', '601'], description: "Find guests with CPU usage < 30%" },
- { term: 'memory>70', expectedIds: ['102', '401', '701'], description: "Find guests with memory usage > 70%" },
- { term: 'disk>70', expectedIds: ['102', '401'], description: "Find guests with disk usage > 70%" },
- { term: 'cpu:50', expectedIds: ['101', '102', '401', '701'], description: "Column-specific search for CPU usage >= 50%" },
- { term: 'memory:0', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Column-specific search for memory usage >= 0%" },
- { term: 'disk=0', expectedIds: ['601'], description: "Find guests with exactly 0% disk usage" },
- { term: 'cpu>=60', expectedIds: ['102', '401', '701'], description: "Find guests with CPU usage >= 60%" },
- { term: 'memory<=25', expectedIds: ['101', '201', '202', '302', '402', '501', '601'], description: "Find guests with memory usage <= 25%" }
- ]
- },
- {
- name: "Tag Search",
- tests: [
- { term: 'prod', expectedIds: ['101', '102', '103', '201', '202', '701'], description: "Find guests with prod tag" },
- { term: 'web', expectedIds: ['101', '201'], description: "Find guests with web tag" },
- { term: 'db', expectedIds: ['102', '202'], description: "Find guests with db tag" },
- { term: 'stage', expectedIds: ['301', '302'], description: "Find guests with stage tag" },
- { term: 'dev', expectedIds: ['401', '402', '501'], description: "Find guests with dev tag" }
- ]
- },
- {
- name: "Multiple Term Search",
- tests: [
- { term: ['running', 'database'], expectedIds: ['102'], description: "Find running database guests" },
- { term: ['primary', 'lxc'], expectedIds: ['103'], description: "Find primary container guests" },
- { term: ['node-5', 'running'], expectedIds: ['401', '402'], description: "Find running guests on node-5" },
- { term: ['vm', 'stopped'], expectedIds: ['201', '202', '302'], description: "Find stopped VMs" },
- { term: ['prod', 'db', 'primary'], expectedIds: ['102'], description: "Find primary prod db" }
- ]
- },
- {
- name: "Edge Cases",
- tests: [
- { term: 'role:', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Incomplete column search" },
- { term: 'status:', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Incomplete status column search" },
- { term: 'type:', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Incomplete type column search" },
- { term: ' ', expectedIds: ['101', '102', '103', '201', '202', '301', '302', '401', '402', '501', '601', '701'], description: "Whitespace search" },
- { term: ' server ', expectedIds: ['101', '201', '301', '701'], description: "Search with extra whitespace" },
- { term: 'SERVER', expectedIds: ['101', '201', '301', '701'], description: "Case insensitive search" }
- ]
- },
- {
- name: "Combinations & Complex Queries",
- tests: [
- { term: 'cpu>50 memory>50', expectedIds: ['102', '401', '701'], description: "High CPU and memory usage" },
- { term: 'primary web', expectedIds: ['101'], description: "Primary web server" },
- { term: 'node:node-5 status:running', expectedIds: ['401', '402'], description: "Running guests on node-5" },
- { term: ['role:secondary', 'type:qemu'], expectedIds: ['201', '202'], description: "Secondary VMs" },
- { term: ['prod', 'role:primary', 'running'], expectedIds: ['101', '102', '103'], description: "Running primary prod guests" }
- ]
- },
- {
- name: "Single Character Searches",
- tests: [
- { term: 'p', expectedIds: ['101', '102', '103', '301', '401', '501', '601', '701'], description: "Find guests with 'p' in any field" },
- { term: 's', expectedIds: ['101', '103', '201', '202', '301', '302', '401', '501', '601', '701'], description: "Find guests with 's' in any field" },
- { term: 'c', expectedIds: ['103', '301', '302', '401', '402', '501', '601'], description: "Find guests with 'c' in any field" },
- { term: 'v', expectedIds: ['101', '102', '201', '202', '301', '401', '501', '601', '701'], description: "Find guests with 'v' in any field" },
- { term: '1', expectedIds: ['101', '102', '103'], description: "Find guests with '1' in any field" }
- ]
- }
-];
-
-// ============================================================================
-// TEST RUNNER FUNCTIONS
-// ============================================================================
-
-// Run all tests and generate report
-function runAllTests() {
- console.log('===== SEARCH FUNCTIONALITY COMPREHENSIVE TEST SUITE =====');
-
- let totalTests = 0;
- let passedTests = 0;
- let failedTests = 0;
- const failedTestDetails = [];
-
- // Run each test category
- testCategories.forEach(category => {
- console.log(`\n----- ${category.name} Tests -----`);
-
- category.tests.forEach(test => {
- process.stdout.write(`Testing: ${test.description}... `);
-
- // Run the test
- const result = testSearchTerm(test.term, test.expectedIds, test.description);
- totalTests++;
-
- if (result.passed) {
- console.log('✅ PASSED');
- passedTests++;
- } else {
- console.log('❌ FAILED');
- failedTests++;
- failedTestDetails.push(result);
- }
- });
- });
-
- // Print summary
- console.log('\n===== TEST SUMMARY =====');
- console.log(`Total Tests: ${totalTests}`);
- console.log(`Passed: ${passedTests}`);
- console.log(`Failed: ${failedTests}`);
-
- // Print failed test details
- if (failedTests > 0) {
- console.log('\n===== FAILED TESTS DETAILS =====');
- failedTestDetails.forEach(test => {
- console.log(`\n❌ Failed: ${test.description}`);
- console.log(` Search Term: "${typeof test.term === 'string' ? test.term : JSON.stringify(test.term)}"`);
- console.log(` Expected IDs: ${JSON.stringify(test.expectedIds)}`);
- console.log(` Actual IDs: ${JSON.stringify(test.resultIds)}`);
-
- // Show differences
- const missing = test.expectedIds.filter(id => !test.resultIds.includes(id));
- const unexpected = test.resultIds.filter(id => !test.expectedIds.includes(id));
-
- if (missing.length > 0) {
- console.log(` Missing (expected but not found): ${JSON.stringify(missing)}`);
- }
-
- if (unexpected.length > 0) {
- console.log(` Unexpected (found but not expected): ${JSON.stringify(unexpected)}`);
- }
-
- // Show affected guests by name for easier debugging
- if (missing.length > 0) {
- const missingGuests = missing.map(id => {
- const guest = mockGuests.find(g => g.id === id);
- return guest ? `${id} (${guest.name})` : id;
- });
- console.log(` Missing guests: ${missingGuests.join(', ')}`);
- }
-
- if (unexpected.length > 0) {
- const unexpectedGuests = unexpected.map(id => {
- const guest = mockGuests.find(g => g.id === id);
- return guest ? `${id} (${guest.name})` : id;
- });
- console.log(` Unexpected guests: ${unexpectedGuests.join(', ')}`);
- }
- });
- }
-
- return {
- totalTests,
- passedTests,
- failedTests,
- failedTestDetails
- };
-}
-
-// Run a specific test category
-function runTestCategory(categoryName) {
- const category = testCategories.find(c => c.name === categoryName);
- if (!category) {
- console.log(`Category "${categoryName}" not found`);
- return null;
- }
-
- console.log(`\n===== Running Tests for Category: ${category.name} =====`);
-
- let passedTests = 0;
- let failedTests = 0;
-
- category.tests.forEach(test => {
- process.stdout.write(`Testing: ${test.description}... `);
-
- // Run the test
- const result = testSearchTerm(test.term, test.expectedIds, test.description);
-
- if (result.passed) {
- console.log('✅ PASSED');
- passedTests++;
- } else {
- console.log('❌ FAILED');
- console.log(` Expected: ${JSON.stringify(result.expectedIds)}`);
- console.log(` Actual: ${JSON.stringify(result.resultIds)}`);
- failedTests++;
- }
- });
-
- console.log(`\n----- ${category.name} Results -----`);
- console.log(`Tests: ${category.tests.length}, Passed: ${passedTests}, Failed: ${failedTests}`);
-
- return {
- categoryName: category.name,
- totalTests: category.tests.length,
- passedTests,
- failedTests
- };
-}
-
-// Test a single search term directly
-function testSingleTerm(term, expectedIds) {
- console.log(`\n----- TESTING SINGLE TERM: "${term}" -----`);
-
- // Run the test
- const resultIds = runSearchQuery(term);
-
- // Check if the result matches the expected result
- const passed = arraysHaveSameElements(resultIds, expectedIds);
-
- // Detailed output of results
- console.log(`\nSearch results for "${term}":`);
- console.log(` Expected IDs: ${JSON.stringify(expectedIds)}`);
- console.log(` Actual IDs: ${JSON.stringify(resultIds)}`);
- console.log(` Result: ${passed ? '✅ PASSED' : '❌ FAILED'}`);
-
- if (!passed) {
- console.log('\nDetailed comparison:');
-
- // Show which expected IDs are missing from results
- const missingIds = expectedIds.filter(id => !resultIds.includes(id));
- if (missingIds.length > 0) {
- console.log(` Missing IDs (expected but not found): ${JSON.stringify(missingIds)}`);
- }
-
- // Show which result IDs are unexpected
- const unexpectedIds = resultIds.filter(id => !expectedIds.includes(id));
- if (unexpectedIds.length > 0) {
- console.log(` Unexpected IDs (found but not expected): ${JSON.stringify(unexpectedIds)}`);
- }
- }
-
- return {
- term,
- expectedIds,
- resultIds,
- passed
- };
-}
-
-// Generate a report of all search features
-function generateSearchFeatureMatrix() {
- console.log('\n===== SEARCH FEATURE MATRIX =====');
-
- const features = [
- { name: 'Basic Text Search', example: '"server", "database"', tests: testCategories.find(c => c.name === 'Basic Text Search').tests.length },
- { name: 'ID Search', example: '"101", "10"', tests: testCategories.find(c => c.name === 'ID Search').tests.length },
- { name: 'Status Search', example: '"running", "status:stopped"', tests: testCategories.find(c => c.name === 'Status Search').tests.length },
- { name: 'Type Search', example: '"vm", "type:lxc"', tests: testCategories.find(c => c.name === 'Type Search').tests.length },
- { name: 'Node Search', example: '"node-1", "node:node-5"', tests: testCategories.find(c => c.name === 'Node Search').tests.length },
- { name: 'Role Search', example: '"primary", "role:secondary"', tests: testCategories.find(c => c.name === 'Role Search').tests.length },
- { name: 'Metric Search', example: '"cpu>70", "memory:50"', tests: testCategories.find(c => c.name === 'Metric Search').tests.length },
- { name: 'Tag Search', example: '"prod", "web"', tests: testCategories.find(c => c.name === 'Tag Search').tests.length },
- { name: 'Multiple Term Search', example: '["running", "database"]', tests: testCategories.find(c => c.name === 'Multiple Term Search').tests.length },
- { name: 'Edge Cases', example: '"role:", " server "', tests: testCategories.find(c => c.name === 'Edge Cases').tests.length },
- { name: 'Complex Queries', example: '"cpu>50 memory>50"', tests: testCategories.find(c => c.name === 'Combinations & Complex Queries').tests.length },
- { name: 'Single Character Searches', example: '"p", "s"', tests: testCategories.find(c => c.name === 'Single Character Searches').tests.length }
- ];
-
- console.log('Feature | Examples | Test Count');
- console.log('--------|----------|----------');
- features.forEach(feature => {
- console.log(`${feature.name} | ${feature.example} | ${feature.tests}`);
- });
-
- const totalTests = features.reduce((sum, feature) => sum + feature.tests, 0);
- console.log(`\nTotal Test Coverage: ${totalTests} tests across ${features.length} feature categories`);
-}
-
-// ============================================================================
-// DIAGNOSTIC FUNCTIONS
-// ============================================================================
-
-// Generate a diagnostic report for a failing search term
-function diagnoseTerm(term) {
- console.log(`\n===== DIAGNOSTIC REPORT FOR TERM: "${term}" =====`);
-
- // Run the search
- const resultIds = runSearchQuery(term);
-
- // Show which guests matched and which didn't
- console.log('\nResults:');
- console.log(` ${resultIds.length} guests matched the search term "${term}"`);
-
- if (resultIds.length > 0) {
- console.log('\nMatching guests:');
- resultIds.forEach(id => {
- const guest = mockGuests.find(g => g.id === id);
- if (guest) {
- console.log(` ${id}: ${guest.name} (${guest.type}, ${guest.status}, node=${guest.node})`);
-
- // Print more detailed properties for this guest
- const isShared = guest.shared || false;
- const isPrimary = isShared && guest.primaryNode === guest.node;
- console.log(` - Shared: ${isShared}, Primary: ${isPrimary}`);
- console.log(` - Description: ${guest.description || 'N/A'}`);
- console.log(` - Tags: ${guest.tags || 'N/A'}`);
- }
- });
- } else {
- console.log(' No guests matched this search term');
- }
-
- // Show which guests didn't match
- const nonMatchingIds = mockGuests.map(g => g.id).filter(id => !resultIds.includes(id));
-
- console.log('\nNon-matching guests:');
- if (nonMatchingIds.length > 0) {
- nonMatchingIds.forEach(id => {
- const guest = mockGuests.find(g => g.id === id);
- if (guest) {
- console.log(` ${id}: ${guest.name} (${guest.type}, ${guest.status}, node=${guest.node})`);
- }
- });
- } else {
- console.log(' All guests matched this search term');
- }
-
- return {
- term,
- matchingIds: resultIds,
- nonMatchingIds
- };
-}
-
-// ============================================================================
-// MODULE EXPORTS AND DIRECT EXECUTION
-// ============================================================================
-
-// Export functions for programmatic use
-export {
- runAllTests,
- runTestCategory,
- testSingleTerm,
- testSearchTerm,
- generateSearchFeatureMatrix,
- diagnoseTerm
-};
-
-// Run the tests when this file is executed directly
-if (typeof require !== 'undefined' && require.main === module) {
- const results = runAllTests();
- generateSearchFeatureMatrix();
-
- // Return exit code based on test results
- if (results.failedTests > 0) {
- process.exit(1);
- } else {
- process.exit(0);
- }
-}
\ No newline at end of file
diff --git a/frontend/src/utils/version.js b/frontend/src/utils/version.js
deleted file mode 100644
index 879209537..000000000
--- a/frontend/src/utils/version.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// This file contains the version information for the application
-// It is automatically updated when a new release is created
-
-export const VERSION = '1.6.4';
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
deleted file mode 100644
index 5859d8ca1..000000000
--- a/frontend/vite.config.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-
-// https://vitejs.dev/config/
-export default defineConfig({
- plugins: [react()],
- server: {
- host: '0.0.0.0',
- port: 3000, // Use port 3000 for the frontend
- strictPort: true, // This will make Vite fail instead of trying another port
- proxy: {
- // Proxy WebSocket connections to the real backend
- '/socket.io': {
- target: process.env.VITE_API_URL || 'http://localhost:7654', // Proxy to the backend on 7654 during development
- ws: true,
- changeOrigin: true,
- secure: false,
- rewrite: (path) => path,
- onError: (err, req, res) => {
- // Distinguish between normal disconnects and actual errors
- if (err.code === 'ECONNRESET' || err.code === 'EPIPE') {
- console.log(`WebSocket client disconnect (${err.code}) - normal during page navigation`);
- } else {
- console.error('WebSocket proxy error:', err);
- }
- },
- configure: (proxy, _options) => {
- // Increase timeout values for Docker environments
- proxy.options.timeout = 60000; // 60 seconds
- proxy.options.proxyTimeout = 60000; // 60 seconds
-
- // Handle WebSocket-specific errors
- proxy.on('proxyReqWs', (proxyReq, req, socket, options, head) => {
- console.log('WebSocket proxy request:', req.url);
- socket.on('error', (err) => {
- if (err.code === 'ECONNRESET' || err.code === 'EPIPE') {
- console.log(`WebSocket socket error (${err.code}) - normal during page navigation`);
- } else {
- console.error('WebSocket socket error:', err);
- }
- });
- });
-
- proxy.on('error', (err, _req, _res) => {
- // Distinguish between normal disconnects and actual errors
- if (err.code === 'ECONNRESET' || err.code === 'EPIPE') {
- console.log(`WebSocket proxy disconnect (${err.code}) - normal during page navigation`);
- } else {
- console.error('Proxy error:', err);
- }
- });
- proxy.on('proxyReq', (proxyReq, req, _res) => {
- console.log('Proxy request:', req.method, req.url);
- });
- proxy.on('proxyRes', (proxyRes, req, _res) => {
- console.log('Proxy response:', proxyRes.statusCode, req.url);
- });
- }
- },
- // Proxy API requests to the real backend
- '/api': {
- target: process.env.VITE_API_URL || 'http://localhost:7654', // Proxy to the backend on 7654 during development
- changeOrigin: true,
- configure: (proxy, _options) => {
- // Increase timeout values for Docker environments
- proxy.options.timeout = 60000; // 60 seconds
- proxy.options.proxyTimeout = 60000; // 60 seconds
- }
- }
- },
- },
- build: {
- outDir: 'dist',
- emptyOutDir: true,
- },
- // Define environment variables that will be available in the frontend code
- define: {
- // Stringify the values to ensure they're treated as strings in the frontend
- 'import.meta.env.VITE_API_URL': JSON.stringify(process.env.VITE_API_URL || ''),
- 'import.meta.env.DOCKER_CONTAINER': JSON.stringify(process.env.DOCKER_CONTAINER || ''),
- 'import.meta.env.DEV': JSON.stringify(process.env.NODE_ENV === 'development' || true),
- 'import.meta.env.VITE_USE_MOCK_DATA': JSON.stringify(process.env.USE_MOCK_DATA || 'false'),
- 'import.meta.env.VITE_MOCK_DATA_ENABLED': JSON.stringify(process.env.MOCK_DATA_ENABLED || 'false'),
- }
-});
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a539a9bf9..8d921f8c1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,1760 +1,50 @@
{
- "name": "pulse",
- "version": "1.6.4",
+ "name": "proxmox-simplified",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "pulse",
- "version": "1.6.4",
- "license": "MIT",
+ "name": "proxmox-simplified",
+ "version": "1.0.0",
"dependencies": {
- "@types/express": "^5.0.0",
- "@types/node": "^22.13.5",
- "@types/socket.io": "^3.0.2",
- "@types/uuid": "^10.0.0",
- "axios": "^1.8.3",
- "cors": "^2.8.5",
- "dotenv": "^16.4.7",
- "express": "^4.21.2",
- "node-fetch": "^3.3.2",
- "socket.io": "^4.8.1",
- "socket.io-client": "^4.8.1",
- "ts-node": "^10.9.2",
- "typescript": "^5.7.3",
- "uuid": "^11.1.0",
- "winston": "^3.17.0"
+ "dotenv": "^16.0.0"
},
"devDependencies": {
- "@babel/plugin-transform-modules-commonjs": "^7.26.3",
- "@babel/preset-env": "^7.26.9",
- "@babel/register": "^7.25.9",
- "@types/cors": "^2.8.17",
- "concurrently": "^9.1.2",
- "nodemon": "^3.1.9",
- "ts-node-dev": "^2.0.0"
- },
- "engines": {
- "node": ">=20.0.0"
+ "tailwindcss": "^3.4.17"
}
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/compat-data": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
- "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/core": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
- "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@ampproject/remapping": "^2.2.0",
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.10",
- "@babel/helper-compilation-targets": "^7.26.5",
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helpers": "^7.26.10",
- "@babel/parser": "^7.26.10",
- "@babel/template": "^7.26.9",
- "@babel/traverse": "^7.26.10",
- "@babel/types": "^7.26.10",
- "convert-source-map": "^2.0.0",
- "debug": "^4.1.0",
- "gensync": "^1.0.0-beta.2",
- "json5": "^2.2.3",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
+ "node": ">=10"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/babel"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@babel/core/node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@babel/core/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/@babel/core/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
- "peer": true,
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/generator": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
- "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
- "dev": true,
- "license": "MIT",
"dependencies": {
- "@babel/parser": "^7.26.10",
- "@babel/types": "^7.26.10",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
- "jsesc": "^3.0.2"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz",
- "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
- "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.26.5",
- "@babel/helper-validator-option": "^7.25.9",
- "browserslist": "^4.24.0",
- "lru-cache": "^5.1.1",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz",
- "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.25.9",
- "@babel/helper-member-expression-to-functions": "^7.25.9",
- "@babel/helper-optimise-call-expression": "^7.25.9",
- "@babel/helper-replace-supers": "^7.26.5",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
- "@babel/traverse": "^7.26.9",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-create-regexp-features-plugin": {
- "version": "7.26.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
- "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.25.9",
- "regexpu-core": "^6.2.0",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
- "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.22.6",
- "@babel/helper-plugin-utils": "^7.22.5",
- "debug": "^4.1.1",
- "lodash.debounce": "^4.0.8",
- "resolve": "^1.14.2"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@babel/helper-member-expression-to-functions": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz",
- "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-imports": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
- "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-module-transforms": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
- "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-optimise-call-expression": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz",
- "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-plugin-utils": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
- "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-remap-async-to-generator": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz",
- "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.25.9",
- "@babel/helper-wrap-function": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-replace-supers": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz",
- "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-member-expression-to-functions": "^7.25.9",
- "@babel/helper-optimise-call-expression": "^7.25.9",
- "@babel/traverse": "^7.26.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz",
- "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-option": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
- "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-wrap-function": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz",
- "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/traverse": "^7.25.9",
- "@babel/types": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helpers": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
- "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/template": "^7.26.9",
- "@babel/types": "^7.26.10"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
- "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/types": "^7.26.10"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz",
- "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz",
- "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
- "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz",
- "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
- "@babel/plugin-transform-optional-chaining": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.13.0"
- }
- },
- "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz",
- "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-proposal-private-property-in-object": {
- "version": "7.21.0-placeholder-for-preset-env.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
- "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-assertions": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
- "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
- "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
- "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.18.6",
- "@babel/helper-plugin-utils": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-arrow-functions": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz",
- "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-async-generator-functions": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz",
- "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5",
- "@babel/helper-remap-async-to-generator": "^7.25.9",
- "@babel/traverse": "^7.26.8"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-async-to-generator": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz",
- "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-remap-async-to-generator": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoped-functions": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz",
- "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz",
- "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-class-properties": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz",
- "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-class-static-block": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz",
- "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.12.0"
- }
- },
- "node_modules/@babel/plugin-transform-classes": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz",
- "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.25.9",
- "@babel/helper-compilation-targets": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-replace-supers": "^7.25.9",
- "@babel/traverse": "^7.25.9",
- "globals": "^11.1.0"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-computed-properties": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz",
- "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/template": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz",
- "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-dotall-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz",
- "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-keys": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz",
- "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz",
- "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-dynamic-import": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
- "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "7.26.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz",
- "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-export-namespace-from": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz",
- "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-for-of": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz",
- "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-function-name": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz",
- "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-json-strings": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz",
- "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-literals": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz",
- "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz",
- "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-member-expression-literals": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz",
- "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-amd": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz",
- "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.26.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz",
- "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.26.0",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-systemjs": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz",
- "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9",
- "@babel/traverse": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-modules-umd": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz",
- "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-transforms": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz",
- "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-new-target": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz",
- "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
- "version": "7.26.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz",
- "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-numeric-separator": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz",
- "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz",
- "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-compilation-targets": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/plugin-transform-parameters": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-object-super": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz",
- "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-replace-supers": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-catch-binding": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz",
- "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz",
- "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-parameters": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz",
- "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-private-methods": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz",
- "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz",
- "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-annotate-as-pure": "^7.25.9",
- "@babel/helper-create-class-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-property-literals": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz",
- "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-regenerator": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz",
- "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "regenerator-transform": "^0.15.2"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-regexp-modifiers": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz",
- "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/plugin-transform-reserved-words": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz",
- "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-shorthand-properties": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz",
- "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-spread": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz",
- "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9",
- "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-sticky-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz",
- "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-template-literals": {
- "version": "7.26.8",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz",
- "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-typeof-symbol": {
- "version": "7.26.7",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz",
- "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.26.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-escapes": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz",
- "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-property-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz",
- "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz",
- "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-transform-unicode-sets-regex": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz",
- "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.25.9",
- "@babel/helper-plugin-utils": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
- }
- },
- "node_modules/@babel/preset-env": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz",
- "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.26.8",
- "@babel/helper-compilation-targets": "^7.26.5",
- "@babel/helper-plugin-utils": "^7.26.5",
- "@babel/helper-validator-option": "^7.25.9",
- "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9",
- "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9",
- "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9",
- "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9",
- "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9",
- "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
- "@babel/plugin-syntax-import-assertions": "^7.26.0",
- "@babel/plugin-syntax-import-attributes": "^7.26.0",
- "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
- "@babel/plugin-transform-arrow-functions": "^7.25.9",
- "@babel/plugin-transform-async-generator-functions": "^7.26.8",
- "@babel/plugin-transform-async-to-generator": "^7.25.9",
- "@babel/plugin-transform-block-scoped-functions": "^7.26.5",
- "@babel/plugin-transform-block-scoping": "^7.25.9",
- "@babel/plugin-transform-class-properties": "^7.25.9",
- "@babel/plugin-transform-class-static-block": "^7.26.0",
- "@babel/plugin-transform-classes": "^7.25.9",
- "@babel/plugin-transform-computed-properties": "^7.25.9",
- "@babel/plugin-transform-destructuring": "^7.25.9",
- "@babel/plugin-transform-dotall-regex": "^7.25.9",
- "@babel/plugin-transform-duplicate-keys": "^7.25.9",
- "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9",
- "@babel/plugin-transform-dynamic-import": "^7.25.9",
- "@babel/plugin-transform-exponentiation-operator": "^7.26.3",
- "@babel/plugin-transform-export-namespace-from": "^7.25.9",
- "@babel/plugin-transform-for-of": "^7.26.9",
- "@babel/plugin-transform-function-name": "^7.25.9",
- "@babel/plugin-transform-json-strings": "^7.25.9",
- "@babel/plugin-transform-literals": "^7.25.9",
- "@babel/plugin-transform-logical-assignment-operators": "^7.25.9",
- "@babel/plugin-transform-member-expression-literals": "^7.25.9",
- "@babel/plugin-transform-modules-amd": "^7.25.9",
- "@babel/plugin-transform-modules-commonjs": "^7.26.3",
- "@babel/plugin-transform-modules-systemjs": "^7.25.9",
- "@babel/plugin-transform-modules-umd": "^7.25.9",
- "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9",
- "@babel/plugin-transform-new-target": "^7.25.9",
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6",
- "@babel/plugin-transform-numeric-separator": "^7.25.9",
- "@babel/plugin-transform-object-rest-spread": "^7.25.9",
- "@babel/plugin-transform-object-super": "^7.25.9",
- "@babel/plugin-transform-optional-catch-binding": "^7.25.9",
- "@babel/plugin-transform-optional-chaining": "^7.25.9",
- "@babel/plugin-transform-parameters": "^7.25.9",
- "@babel/plugin-transform-private-methods": "^7.25.9",
- "@babel/plugin-transform-private-property-in-object": "^7.25.9",
- "@babel/plugin-transform-property-literals": "^7.25.9",
- "@babel/plugin-transform-regenerator": "^7.25.9",
- "@babel/plugin-transform-regexp-modifiers": "^7.26.0",
- "@babel/plugin-transform-reserved-words": "^7.25.9",
- "@babel/plugin-transform-shorthand-properties": "^7.25.9",
- "@babel/plugin-transform-spread": "^7.25.9",
- "@babel/plugin-transform-sticky-regex": "^7.25.9",
- "@babel/plugin-transform-template-literals": "^7.26.8",
- "@babel/plugin-transform-typeof-symbol": "^7.26.7",
- "@babel/plugin-transform-unicode-escapes": "^7.25.9",
- "@babel/plugin-transform-unicode-property-regex": "^7.25.9",
- "@babel/plugin-transform-unicode-regex": "^7.25.9",
- "@babel/plugin-transform-unicode-sets-regex": "^7.25.9",
- "@babel/preset-modules": "0.1.6-no-external-plugins",
- "babel-plugin-polyfill-corejs2": "^0.4.10",
- "babel-plugin-polyfill-corejs3": "^0.11.0",
- "babel-plugin-polyfill-regenerator": "^0.6.1",
- "core-js-compat": "^3.40.0",
- "semver": "^6.3.1"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/preset-env/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/@babel/preset-modules": {
- "version": "0.1.6-no-external-plugins",
- "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
- "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@babel/types": "^7.4.4",
- "esutils": "^2.0.2"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/@babel/register": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz",
- "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "clone-deep": "^4.0.1",
- "find-cache-dir": "^2.0.0",
- "make-dir": "^2.1.0",
- "pirates": "^4.0.6",
- "source-map-support": "^0.5.16"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
- "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/template": {
- "version": "7.26.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
- "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/parser": "^7.26.9",
- "@babel/types": "^7.26.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
- "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.26.2",
- "@babel/generator": "^7.26.10",
- "@babel/parser": "^7.26.10",
- "@babel/template": "^7.26.9",
- "@babel/types": "^7.26.10",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/traverse/node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@babel/traverse/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@babel/types": {
- "version": "7.26.10",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
- "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@colors/colors": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
- "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.1.90"
- }
- },
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
- "node_modules/@dabh/diagnostics": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
- "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
- "license": "MIT",
- "dependencies": {
- "colorspace": "1.1.x",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -1770,21 +60,11 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -1804,253 +84,102 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- },
- "node_modules/@socket.io/component-emitter": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
- "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
- "license": "MIT"
- },
- "node_modules/@tsconfig/node10": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
- "license": "MIT"
- },
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "license": "MIT"
- },
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "license": "MIT"
- },
- "node_modules/@tsconfig/node16": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "license": "MIT"
- },
- "node_modules/@types/body-parser": {
- "version": "1.19.5",
- "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
- "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
- "license": "MIT",
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/connect": {
- "version": "3.4.38",
- "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
- "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/cors": {
- "version": "2.8.17",
- "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
- "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/express": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
- "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
- "license": "MIT",
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^5.0.0",
- "@types/qs": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
- "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/http-errors": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
- "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
- "license": "MIT"
- },
- "node_modules/@types/mime": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
- "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "22.13.10",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
- "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.20.0"
- }
- },
- "node_modules/@types/qs": {
- "version": "6.9.18",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
- "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
- "license": "MIT"
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
- "license": "MIT"
- },
- "node_modules/@types/send": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
- "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
- "license": "MIT",
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "1.15.7",
- "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
- "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
- "license": "MIT",
- "dependencies": {
- "@types/http-errors": "*",
- "@types/node": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/socket.io": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-3.0.2.tgz",
- "integrity": "sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ==",
- "deprecated": "This is a stub types definition. socket.io provides its own type definitions, so you do not need this installed.",
- "license": "MIT",
- "dependencies": {
- "socket.io": "*"
- }
- },
- "node_modules/@types/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==",
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
},
- "node_modules/@types/strip-json-comments": {
- "version": "0.0.30",
- "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz",
- "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==",
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/@types/triple-beam": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
- "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
- "license": "MIT"
- },
- "node_modules/@types/uuid": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
- "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
- "license": "MIT"
- },
- "node_modules/accepts": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
- "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">= 8"
}
},
- "node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
"license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 8"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "acorn": "^8.11.0"
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
},
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
}
},
"node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -2066,91 +195,11 @@
}
},
"node_modules/arg": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "license": "MIT"
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
- "license": "MIT"
- },
- "node_modules/async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
- "license": "MIT"
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.8.3",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
- "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.12",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
- "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.6.3",
- "semver": "^6.3.1"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/babel-plugin-polyfill-corejs3": {
- "version": "0.11.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
- "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.3",
- "core-js-compat": "^3.40.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
- },
- "node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
- "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.3"
- },
- "peerDependencies": {
- "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
- }
+ "license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
@@ -2159,15 +208,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/base64id": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
- "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
- "license": "MIT",
- "engines": {
- "node": "^4.5.0 || >= 5.9"
- }
- },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -2181,39 +221,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/body-parser": {
- "version": "1.20.3",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
- "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
- "license": "MIT",
- "dependencies": {
- "bytes": "3.1.2",
- "content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "on-finished": "2.4.1",
- "qs": "6.13.0",
- "raw-body": "2.5.2",
- "type-is": "~1.6.18",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^1.0.0"
}
},
"node_modules/braces": {
@@ -2229,133 +244,14 @@
"node": ">=8"
}
},
- "node_modules/browserslist": {
- "version": "4.24.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
- "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "caniuse-lite": "^1.0.30001688",
- "electron-to-chromium": "^1.5.73",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.1"
- },
- "bin": {
- "browserslist": "cli.js"
- },
- "engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
- }
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/caniuse-lite": {
- "version": "1.0.30001705",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001705.tgz",
- "integrity": "sha512-S0uyMMiYvA7CxNgomYBwwwPUnWzFD83f3B1ce5jHUfHTH//QL6hHsreI8RVC5606R4ssqravelYO5TU6t8sEyg==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chalk/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "node": ">= 6"
}
},
"node_modules/chokidar": {
@@ -2383,44 +279,17 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/clone-deep": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
- "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-plain-object": "^2.0.4",
- "kind-of": "^6.0.2",
- "shallow-clone": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/color": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
- "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.3",
- "color-string": "^1.6.0"
+ "node": ">= 6"
}
},
"node_modules/color-convert": {
@@ -2440,222 +309,65 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
+ "engines": {
+ "node": ">= 6"
}
},
- "node_modules/color/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "license": "MIT"
- },
- "node_modules/colorspace": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
- "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
- "license": "MIT",
- "dependencies": {
- "color": "^3.1.3",
- "text-hex": "1.0.x"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">= 8"
}
},
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concurrently": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.1.2.tgz",
- "integrity": "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==",
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.1.2",
- "lodash": "^4.17.21",
- "rxjs": "^7.8.1",
- "shell-quote": "^1.8.1",
- "supports-color": "^8.1.1",
- "tree-kill": "^1.2.2",
- "yargs": "^17.7.2"
- },
"bin": {
- "conc": "dist/bin/concurrently.js",
- "concurrently": "dist/bin/concurrently.js"
+ "cssesc": "bin/cssesc"
},
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ "node": ">=4"
}
},
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "Apache-2.0"
},
- "node_modules/cookie": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
- "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
- "license": "MIT"
- },
- "node_modules/core-js-compat": {
- "version": "3.41.0",
- "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
- "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==",
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.24.4"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
- "node_modules/cors": {
- "version": "2.8.5",
- "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
- "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4",
- "vary": "^1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/create-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"license": "MIT"
},
- "node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
"node_modules/dotenv": {
- "version": "16.4.7",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
- "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "version": "16.5.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
+ "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -2664,357 +376,58 @@
"url": "https://dotenvx.com"
}
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/dynamic-dedupe": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
- "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==",
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "xtend": "^4.0.0"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
- "node_modules/electron-to-chromium": {
- "version": "1.5.119",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.119.tgz",
- "integrity": "sha512-Ku4NMzUjz3e3Vweh7PhApPrZSS4fyiCIbcIrG9eKrriYVLmbMepETR/v6SU7xPm98QTqMSYiCwfO89QNjXLkbQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
- "node_modules/enabled": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
- "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/engine.io": {
- "version": "6.6.4",
- "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
- "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
- "license": "MIT",
- "dependencies": {
- "@types/cors": "^2.8.12",
- "@types/node": ">=10.0.0",
- "accepts": "~1.3.4",
- "base64id": "2.0.0",
- "cookie": "~0.7.2",
- "cors": "~2.8.5",
- "debug": "~4.3.1",
- "engine.io-parser": "~5.2.1",
- "ws": "~8.17.1"
- },
- "engines": {
- "node": ">=10.2.0"
- }
- },
- "node_modules/engine.io-client": {
- "version": "6.6.3",
- "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
- "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1",
- "engine.io-parser": "~5.2.1",
- "ws": "~8.17.1",
- "xmlhttprequest-ssl": "~2.1.1"
- }
- },
- "node_modules/engine.io-client/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io-client/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/engine.io-client/node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io-parser": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
- "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/engine.io/node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/engine.io/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/engine.io/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/engine.io/node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=8.6.0"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express": {
- "version": "4.21.2",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
- "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "1.20.3",
- "content-disposition": "0.5.4",
- "content-type": "~1.0.4",
- "cookie": "0.7.1",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "2.0.0",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "1.3.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "merge-descriptors": "1.0.3",
- "methods": "~1.1.2",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.12",
- "proxy-addr": "~2.0.7",
- "qs": "6.13.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "0.19.0",
- "serve-static": "1.16.2",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": ">= 0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "node": ">= 6"
}
},
- "node_modules/fecha": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
- "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
- "license": "MIT"
- },
- "node_modules/fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- },
- "engines": {
- "node": "^12.20 || >= 14.13"
+ "reusify": "^1.0.4"
}
},
"node_modules/fill-range": {
@@ -3030,130 +443,23 @@
"node": ">=8"
}
},
- "node_modules/finalhandler": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
- "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "on-finished": "2.4.1",
- "parseurl": "~1.3.3",
- "statuses": "2.0.1",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/find-cache-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
- "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
- "license": "MIT",
+ "license": "ISC",
"dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^2.0.0",
- "pkg-dir": "^3.0.0"
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
},
"engines": {
- "node": ">=6"
- }
- },
- "node_modules/find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^3.0.0"
+ "node": ">=14"
},
- "engines": {
- "node": ">=6"
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/fn.name": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
- "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
- "license": "MIT"
- },
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
- "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
- "license": "MIT",
- "dependencies": {
- "fetch-blob": "^3.1.2"
- },
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3173,167 +479,51 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
},
- "engines": {
- "node": "*"
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
- "is-glob": "^4.0.1"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": ">= 6"
- }
- },
- "node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=10.13.0"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -3342,68 +532,6 @@
"node": ">= 0.4"
}
},
- "node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "license": "MIT",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
- "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3476,430 +604,145 @@
"node": ">=0.12.0"
}
},
- "node_modules/is-plain-object": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
- "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
+ "license": "ISC"
},
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
}
},
- "node_modules/isobject": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
- "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
+ "jiti": "bin/jiti.js"
}
},
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/kind-of": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
- "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/kuler": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
- "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
- "license": "MIT"
- },
- "node_modules/locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
+ "node": ">=14"
},
- "engines": {
- "node": ">=6"
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
}
},
- "node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.debounce": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/logform": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
- "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
- "license": "MIT",
- "dependencies": {
- "@colors/colors": "1.6.0",
- "@types/triple-beam": "^1.3.2",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/logform/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
"node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/make-dir": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
- "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^4.0.1",
- "semver": "^5.6.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/make-dir/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/make-error": {
- "version": "1.3.6",
- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"license": "ISC"
},
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">= 8"
}
},
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
- "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "mime-db": "1.52.0"
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">=8.6"
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "^2.0.1"
},
"engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
- "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
- "dev": true,
- "license": "MIT",
+ "node": ">=16 || 14 >=14.17"
+ },
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/mkdirp": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
- "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
- "bin": {
- "mkdirp": "bin/cmd.js"
- },
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
}
},
- "node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
"funding": [
{
"type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
+ "url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
- "license": "MIT",
- "dependencies": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/node-fetch"
- }
- },
- "node_modules/node-fetch/node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nodemon": {
- "version": "3.1.9",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
- "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.2",
- "debug": "^4",
- "ignore-by-default": "^1.0.1",
- "minimatch": "^3.1.2",
- "pstree.remy": "^1.1.8",
- "semver": "^7.5.3",
- "simple-update-notifier": "^2.0.0",
- "supports-color": "^5.5.0",
- "touch": "^3.1.0",
- "undefsafe": "^2.0.5"
- },
"bin": {
- "nodemon": "bin/nodemon.js"
+ "nanoid": "bin/nanoid.cjs"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nodemon"
- }
- },
- "node_modules/nodemon/node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/nodemon/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/nodemon/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nodemon/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/normalize-path": {
@@ -3916,120 +759,37 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/one-time": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
- "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
- "license": "MIT",
- "dependencies": {
- "fn.name": "1.x.x"
- }
- },
- "node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
+ "node_modules/object-hash": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/p-try": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">= 6"
}
},
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/path-is-absolute": {
+ "node_modules/package-json-from-dist": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
"node_modules/path-parse": {
@@ -4039,11 +799,22 @@
"dev": true,
"license": "MIT"
},
- "node_modules/path-to-regexp": {
- "version": "0.1.12",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
- "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
- "license": "MIT"
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
},
"node_modules/picocolors": {
"version": "1.1.1",
@@ -4066,115 +837,204 @@
}
},
"node_modules/pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=0.10.0"
}
},
"node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
- "node_modules/pkg-dir": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
- "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
- "find-up": "^3.0.0"
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
},
"engines": {
- "node": ">=6"
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
}
},
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
+ "camelcase-css": "^2.0.1"
},
"engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/pstree.remy": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
- "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/qs": {
- "version": "6.13.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
- "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.0.6"
- },
- "engines": {
- "node": ">=0.6"
+ "node": "^12 || ^14 || >= 16"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
}
},
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
}
},
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
+ "postcss-selector-parser": "^6.1.1"
},
"engines": {
- "node": ">= 6"
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
}
},
"node_modules/readdirp": {
@@ -4190,104 +1050,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/regenerate": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
- "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regenerate-unicode-properties": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz",
- "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regenerator-transform": {
- "version": "0.15.2",
- "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
- "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.8.4"
- }
- },
- "node_modules/regexpu-core": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
- "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "regenerate": "^1.4.2",
- "regenerate-unicode-properties": "^10.2.0",
- "regjsgen": "^0.8.0",
- "regjsparser": "^0.12.0",
- "unicode-match-property-ecmascript": "^2.0.0",
- "unicode-match-property-value-ecmascript": "^2.1.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regjsgen": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
- "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/regjsparser": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
- "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "jsesc": "~3.0.2"
- },
- "bin": {
- "regjsparser": "bin/parser"
- }
- },
- "node_modules/regjsparser/node_modules/jsesc": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
- "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -4309,34 +1071,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
}
},
- "node_modules/rxjs": {
- "version": "7.8.2",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
- "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
@@ -4351,440 +1101,77 @@
"url": "https://feross.org/support"
}
],
- "license": "MIT"
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/send": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
- "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
- },
- "engines": {
- "node": ">= 0.8.0"
+ "queue-microtask": "^1.2.2"
}
},
- "node_modules/send/node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/serve-static": {
- "version": "1.16.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
- "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
- "license": "MIT",
- "dependencies": {
- "encodeurl": "~2.0.0",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.19.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/shallow-clone": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
- "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "kind-of": "^6.0.2"
+ "shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
- "node_modules/shell-quote": {
- "version": "1.8.2",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz",
- "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8"
}
},
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/simple-swizzle/node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT"
- },
- "node_modules/simple-update-notifier": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
+ "license": "ISC",
"engines": {
- "node": ">=10"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/socket.io": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
- "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
- "license": "MIT",
- "dependencies": {
- "accepts": "~1.3.4",
- "base64id": "~2.0.0",
- "cors": "~2.8.5",
- "debug": "~4.3.2",
- "engine.io": "~6.6.0",
- "socket.io-adapter": "~2.5.2",
- "socket.io-parser": "~4.2.4"
- },
- "engines": {
- "node": ">=10.2.0"
- }
- },
- "node_modules/socket.io-adapter": {
- "version": "2.5.5",
- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
- "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
- "license": "MIT",
- "dependencies": {
- "debug": "~4.3.4",
- "ws": "~8.17.1"
- }
- },
- "node_modules/socket.io-adapter/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-adapter/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/socket.io-adapter/node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-client": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
- "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.2",
- "engine.io-client": "~6.6.1",
- "socket.io-parser": "~4.2.4"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-client/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-client/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/socket.io-parser": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
- "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
- "license": "MIT",
- "dependencies": {
- "@socket.io/component-emitter": "~3.1.0",
- "debug": "~4.3.1"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-parser/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io-parser/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/socket.io/node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/socket.io/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
- "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/stack-trace": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
- "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
- "license": "MIT",
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
"engines": {
- "node": "*"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string-width": {
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
@@ -4799,7 +1186,24 @@
"node": ">=8"
}
},
- "node_modules/strip-ansi": {
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
@@ -4812,40 +1216,67 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
- "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "has-flag": "^4.0.0"
+ "ansi-regex": "^6.0.1"
},
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
}
},
"node_modules/supports-preserve-symlinks-flag": {
@@ -4861,11 +1292,66 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/text-hex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
- "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
- "license": "MIT"
+ "node_modules/tailwindcss": {
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
},
"node_modules/to-regex-range": {
"version": "5.0.1",
@@ -4880,354 +1366,56 @@
"node": ">=8.0"
}
},
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/touch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
- "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
- "license": "ISC",
- "bin": {
- "nodetouch": "bin/nodetouch.js"
- }
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
- }
- },
- "node_modules/triple-beam": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
- "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
- "license": "MIT",
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/ts-node": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
- "license": "MIT",
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
- },
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
- }
- },
- "node_modules/ts-node-dev": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
- "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chokidar": "^3.5.1",
- "dynamic-dedupe": "^0.3.0",
- "minimist": "^1.2.6",
- "mkdirp": "^1.0.4",
- "resolve": "^1.0.0",
- "rimraf": "^2.6.1",
- "source-map-support": "^0.5.12",
- "tree-kill": "^1.2.2",
- "ts-node": "^10.4.0",
- "tsconfig": "^7.0.0"
- },
- "bin": {
- "ts-node-dev": "lib/bin.js",
- "tsnd": "lib/bin.js"
- },
- "engines": {
- "node": ">=0.8.0"
- },
- "peerDependencies": {
- "node-notifier": "*",
- "typescript": "*"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/tsconfig": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",
- "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/strip-bom": "^3.0.0",
- "@types/strip-json-comments": "0.0.30",
- "strip-bom": "^3.0.0",
- "strip-json-comments": "^2.0.0"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
- "license": "0BSD"
- },
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "license": "MIT",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typescript": {
- "version": "5.8.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
- "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/undefsafe": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
- "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
- "license": "MIT"
- },
- "node_modules/unicode-canonical-property-names-ecmascript": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
- "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-ecmascript": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
- "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "unicode-canonical-property-names-ecmascript": "^2.0.0",
- "unicode-property-aliases-ecmascript": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-match-property-value-ecmascript": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz",
- "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unicode-property-aliases-ecmascript": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
- "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/update-browserslist-db": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
- }
+ "license": "Apache-2.0"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/uuid": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
- "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
"bin": {
- "uuid": "dist/esm/bin/uuid"
- }
- },
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "license": "MIT"
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
- "license": "MIT",
+ "node-which": "bin/node-which"
+ },
"engines": {
"node": ">= 8"
}
},
- "node_modules/winston": {
- "version": "3.17.0",
- "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
- "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
- "license": "MIT",
- "dependencies": {
- "@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.2",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.7.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.9.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/winston-transport": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
- "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
- "license": "MIT",
- "dependencies": {
- "logform": "^2.7.0",
- "readable-stream": "^3.6.2",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
"node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
@@ -5245,84 +1433,78 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/xmlhttprequest-ssl": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
- "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=0.4"
+ "node": ">=8"
}
},
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yallist": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
+ "color-convert": "^2.0.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
+ "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"dev": true,
"license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/yn": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
+ "node": ">= 14"
}
}
}
diff --git a/package.json b/package.json
index 0ac5c05b1..5d0f1693b 100644
--- a/package.json
+++ b/package.json
@@ -1,64 +1,15 @@
{
- "name": "pulse",
- "version": "1.6.4",
- "main": "dist/server.js",
- "engines": {
- "node": ">=20.0.0"
- },
+ "name": "proxmox-simplified",
+ "version": "1.0.0",
+ "description": "Simplified monitor for Proxmox",
"scripts": {
- "start": "node dist/server.js",
- "build": "tsc",
- "dev": "node scripts/configure-env.js dev && NODE_ENV=development node scripts/start.js dev",
- "dev:docker": "[ -f .env ] || cp .env.example .env && node scripts/configure-env.js dev && docker compose up --build",
- "dev:screenshots": "rm -rf docs/images/* && concurrently --kill-others-on-fail \"npm run dev\" \"sleep 10 && cd tools/screenshot-automation && npm run build && npm start\"",
- "prod": "node scripts/configure-env.js prod && node scripts/check-config.js && node scripts/start.js prod",
- "prod:docker": "[ -f .env ] || cp .env.example .env && node scripts/configure-env.js prod && node scripts/check-config.js && docker compose up --build",
- "stop": "docker compose stop",
- "cleanup": "docker compose down --rmi all --volumes --remove-orphans"
+ "dev": "NODE_ENV=development cd server && node index.js",
+ "start": "cd server && node index.js"
},
- "keywords": [
- "proxmox",
- "monitoring",
- "dashboard",
- "real-time",
- "metrics",
- "virtualization"
- ],
- "author": "Richard Courtman",
- "license": "MIT",
- "description": "A lightweight, responsive monitoring application for Proxmox VE that displays real-time metrics for CPU, memory, network, and disk usage across multiple nodes.",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/rcourtman/pulse.git"
- },
- "bugs": {
- "url": "https://github.com/rcourtman/pulse/issues"
- },
- "homepage": "https://github.com/rcourtman/pulse#readme",
"dependencies": {
- "@types/express": "^5.0.0",
- "@types/node": "^22.13.5",
- "@types/socket.io": "^3.0.2",
- "@types/uuid": "^10.0.0",
- "axios": "^1.8.3",
- "cors": "^2.8.5",
- "dotenv": "^16.4.7",
- "express": "^4.21.2",
- "node-fetch": "^3.3.2",
- "socket.io": "^4.8.1",
- "socket.io-client": "^4.8.1",
- "ts-node": "^10.9.2",
- "typescript": "^5.7.3",
- "uuid": "^11.1.0",
- "winston": "^3.17.0"
+ "dotenv": "^16.0.0"
},
"devDependencies": {
- "@babel/plugin-transform-modules-commonjs": "^7.26.3",
- "@babel/preset-env": "^7.26.9",
- "@babel/register": "^7.25.9",
- "@types/cors": "^2.8.17",
- "concurrently": "^9.1.2",
- "nodemon": "^3.1.9",
- "ts-node-dev": "^2.0.0"
+ "tailwindcss": "^4.1.4"
}
}
diff --git a/public/app.js b/public/app.js
new file mode 100644
index 000000000..299973b31
--- /dev/null
+++ b/public/app.js
@@ -0,0 +1,845 @@
+// Setup hot reload capability (No changes needed here unless it manipulates classes/styles)
+(function setupHotReload() {
+ // Check for connection to server and auto-refresh
+ const socket = io();
+
+ // Listen for hotReload event from server
+ socket.on('hotReload', function() {
+ console.log('Hot reload triggered, refreshing page...');
+ window.location.reload();
+ });
+
+ // Fallback: Check for server disconnects/reconnects as trigger for reload
+ let wasConnected = false;
+ socket.on('connect', function() {
+ console.log('Connected to server');
+ if (wasConnected) {
+ console.log('Reconnected - refreshing page');
+ // Slight delay to ensure server is ready after reconnect
+ setTimeout(() => window.location.reload(), 500);
+ }
+ wasConnected = true;
+ // Note: Initial data request moved to DOMContentLoaded to ensure elements exist
+ });
+
+ // Optional: Log disconnects
+ socket.on('disconnect', function(reason) {
+ console.log('Disconnected from server:', reason);
+ wasConnected = false; // Reset connection status
+ // UI update for disconnect handled in DOMContentLoaded listener
+ });
+
+})();
+
+document.addEventListener('DOMContentLoaded', function() {
+ // Guard clauses to ensure essential elements exist before proceeding
+ const themeToggle = document.getElementById('theme-toggle');
+ const connectionStatus = document.getElementById('connection-status');
+ const mainTableBody = document.querySelector('#main-table tbody');
+ const tooltipElement = document.getElementById('custom-tooltip');
+ const searchInput = document.getElementById('dashboard-search');
+ const statusElement = document.getElementById('dashboard-status-text');
+
+ if (!connectionStatus) {
+ console.error('Critical element #connection-status not found!');
+ return; // Stop execution if essential elements are missing
+ }
+ if (!mainTableBody) {
+ console.error('Critical element #main-table tbody not found!');
+ // Allow execution for other features, but log error
+ }
+ if (!tooltipElement) {
+ console.warn('Element #custom-tooltip not found - tooltips will not work.');
+ }
+
+ const htmlElement = document.documentElement; // Target
+
+ // --- Theme Handling ---
+ const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
+ const savedTheme = localStorage.getItem('theme');
+
+ function applyTheme(theme) {
+ if (!themeToggle) return; // Guard against missing toggle
+ if (theme === 'dark') {
+ htmlElement.classList.add('dark');
+ themeToggle.checked = true;
+ localStorage.setItem('theme', 'dark');
+ } else {
+ htmlElement.classList.remove('dark');
+ themeToggle.checked = false;
+ localStorage.setItem('theme', 'light');
+ }
+ }
+
+ // Apply initial theme only if toggle exists
+ if (themeToggle) {
+ applyTheme(savedTheme || (prefersDarkScheme.matches ? 'dark' : 'light'));
+
+ themeToggle.addEventListener('change', function() {
+ applyTheme(this.checked ? 'dark' : 'light');
+ });
+ } else {
+ console.warn('Element #theme-toggle not found - theme switching disabled.');
+ // Apply system preference without saving/using toggle state
+ applyTheme(prefersDarkScheme.matches ? 'dark' : 'light');
+ }
+
+ // --- Tab Functionality ---
+ const tabs = document.querySelectorAll('.tab');
+ const tabContents = document.querySelectorAll('.tab-content');
+ let showTab = 'main'; // Default visible tab
+
+ tabs.forEach(tab => {
+ tab.addEventListener('click', () => {
+ const tabId = tab.getAttribute('data-tab');
+
+ tabs.forEach(t => {
+ t.classList.remove('active', 'bg-white', 'dark:bg-gray-800', 'border', 'border-gray-300', 'dark:border-gray-700', 'border-b-0', '-mb-px');
+ t.classList.add('bg-gray-100', 'dark:bg-gray-700/50', 'border-transparent', 'text-gray-600', 'dark:text-gray-400', 'hover:bg-gray-200', 'dark:hover:bg-gray-700');
+ });
+ tab.classList.add('active', 'bg-white', 'dark:bg-gray-800', 'border', 'border-gray-300', 'dark:border-gray-700', 'border-b-0', '-mb-px');
+ tab.classList.remove('bg-gray-100', 'dark:bg-gray-700/50', 'border-transparent', 'text-gray-600', 'dark:text-gray-400', 'hover:bg-gray-200', 'dark:hover:bg-gray-700');
+
+ tabContents.forEach(content => {
+ content.classList.remove('block');
+ content.classList.add('hidden');
+ if (content.id === tabId) {
+ content.classList.remove('hidden');
+ content.classList.add('block');
+ }
+ });
+
+ showTab = tabId; // Update global state
+ // Potentially trigger data refresh if needed for the specific tab
+ });
+ });
+
+ // --- Data Storage and State ---
+ let nodesData = [];
+ let vmsData = [];
+ let containersData = [];
+ let metricsData = [];
+ let dashboardData = [];
+ const sortState = {
+ nodes: { column: null, direction: 'asc' },
+ vms: { column: null, direction: 'asc' },
+ containers: { column: null, direction: 'asc' },
+ main: { column: 'id', direction: 'asc' }
+ };
+ let groupByNode = true; // Default view
+ let filterGuestType = 'all'; // Default filter
+ const AVERAGING_WINDOW_SIZE = 5;
+ const dashboardHistory = {};
+ let filterStatus = 'all'; // New state variable for status filter
+
+ // --- WebSocket Connection ---
+ const socket = io();
+
+ socket.on('connect', function() {
+ console.log('[socket] Connected');
+ connectionStatus.textContent = 'Connected';
+ connectionStatus.classList.remove('disconnected', 'bg-gray-200', 'dark:bg-gray-700', 'text-gray-600', 'dark:text-gray-400', 'bg-red-100', 'dark:bg-red-800/30', 'text-red-700', 'dark:text-red-300');
+ connectionStatus.classList.add('connected', 'bg-green-100', 'dark:bg-green-800/30', 'text-green-700', 'dark:text-green-300');
+ requestFullData(); // Request data once connected
+ });
+
+ socket.on('disconnect', function(reason) {
+ console.log('[socket] Disconnected:', reason);
+ connectionStatus.textContent = 'Disconnected';
+ connectionStatus.classList.remove('connected', 'bg-green-100', 'dark:bg-green-800/30', 'text-green-700', 'dark:text-green-300');
+ connectionStatus.classList.add('disconnected', 'bg-red-100', 'dark:bg-red-800/30', 'text-red-700', 'dark:text-red-300');
+ });
+
+ // --- Sorting Logic ---
+ function updateSortUI(tableId, clickedHeader) {
+ const tableElement = document.getElementById(tableId);
+ if (!tableElement) return; // Guard against missing table
+
+ const table = tableId.split('-')[0];
+ const headers = tableElement.querySelectorAll('th.sortable');
+ const currentSort = sortState[table];
+
+ headers.forEach(header => {
+ header.classList.remove('bg-blue-50', 'dark:bg-blue-900/20');
+ const arrow = header.querySelector('.sort-arrow');
+ if (arrow) arrow.remove();
+
+ if (header === clickedHeader && currentSort.column) { // Only highlight/arrow if a sort is active
+ header.classList.add('bg-blue-50', 'dark:bg-blue-900/20');
+ const arrowSpan = document.createElement('span');
+ arrowSpan.className = 'sort-arrow ml-1';
+ arrowSpan.textContent = currentSort.direction === 'asc' ? '▲' : '▼';
+ header.appendChild(arrowSpan);
+ }
+ });
+ }
+
+ function setupTableSorting(tableId) {
+ const tableElement = document.getElementById(tableId);
+ if (!tableElement) {
+ console.warn(`Table #${tableId} not found for sort setup.`);
+ return;
+ }
+ const tableType = tableId.split('-')[0]; // e.g., 'nodes', 'main'
+
+ tableElement.querySelectorAll('th.sortable').forEach(th => {
+ th.addEventListener('click', () => {
+ const column = th.getAttribute('data-sort');
+ if (!column) return;
+
+ if (sortState[tableType].column === column) {
+ sortState[tableType].direction = sortState[tableType].direction === 'asc' ? 'desc' : 'asc';
+ } else {
+ sortState[tableType].column = column;
+ sortState[tableType].direction = 'asc';
+ }
+
+ // Trigger the correct update function based on table type
+ switch(tableType) {
+ case 'nodes': updateNodesTable(nodesData); break;
+ case 'vms': updateVmsTable(vmsData); break;
+ case 'containers': updateContainersTable(containersData); break;
+ case 'main': updateDashboardTable(); break;
+ default: console.error('Unknown table type for sorting:', tableType);
+ }
+
+ updateSortUI(tableId, th);
+ });
+ });
+ }
+
+ // Setup sorting for all tables
+ setupTableSorting('nodes-table');
+ setupTableSorting('vms-table');
+ setupTableSorting('containers-table');
+ setupTableSorting('main-table');
+
+ // --- Filtering Logic ---
+ // Grouping Filter
+ document.querySelectorAll('input[name="group-filter"]').forEach(radio => {
+ radio.addEventListener('change', function() {
+ if (this.checked) {
+ groupByNode = (this.value === 'grouped');
+ updateDashboardTable();
+ if (searchInput) searchInput.dispatchEvent(new Event('input')); // Re-apply text filter
+ }
+ });
+ });
+
+ // Type Filter
+ document.querySelectorAll('input[name="type-filter"]').forEach(radio => {
+ radio.addEventListener('change', function() {
+ if (this.checked) {
+ filterGuestType = this.value;
+ updateDashboardTable();
+ if (searchInput) searchInput.dispatchEvent(new Event('input')); // Re-apply text filter
+ }
+ });
+ });
+
+ // Text Search Filter
+ if (searchInput) {
+ searchInput.addEventListener('input', function() {
+ // Re-rendering the table applies the search filter within updateDashboardTable
+ updateDashboardTable();
+ });
+ } else {
+ console.warn('Element #dashboard-search not found - text filtering disabled.');
+ }
+
+ // Status Filter (NEW)
+ document.querySelectorAll('input[name="status-filter"]').forEach(radio => {
+ radio.addEventListener('change', function() {
+ if (this.checked) {
+ filterStatus = this.value; // Update the status filter state
+ updateDashboardTable(); // Re-render the table
+ if (searchInput) searchInput.dispatchEvent(new Event('input')); // Re-apply text filter if needed
+ }
+ });
+ });
+
+ // --- Data Sorting Function ---
+ function sortData(data, column, direction, type) {
+ if (!column || !data) return data || []; // Return empty array if data is null/undefined
+
+ // Create a shallow copy to avoid modifying the original array
+ const dataToSort = [...data];
+
+ return dataToSort.sort((a, b) => {
+ let valueA, valueB;
+
+ // Use a helper to get comparable values, handling potential missing data
+ const getValue = (item, col) => {
+ if (!item) return type === 'string' ? '' : 0; // Default value based on expected type
+ let val = item[col];
+ // Handle specific column logic if needed
+ if (type === 'main' && col === 'id') val = parseInt(item.vmid || item.id || 0);
+ else if (type === 'nodes' && col === 'id') val = item.node;
+ // ... other specific cases ...
+ return val ?? (type === 'string' ? '' : 0); // Use default if null/undefined
+ };
+
+ valueA = getValue(a, column);
+ valueB = getValue(b, column);
+
+ // Determine type for comparison (simple check)
+ const compareType = (typeof valueA === 'string' || typeof valueB === 'string') ? 'string' : 'number';
+
+ // Comparison logic
+ if (compareType === 'string') {
+ valueA = String(valueA).toLowerCase();
+ valueB = String(valueB).toLowerCase();
+ return direction === 'asc' ? valueA.localeCompare(valueB) : valueB.localeCompare(valueA);
+ } else {
+ // Ensure numeric comparison
+ valueA = parseFloat(valueA) || 0;
+ valueB = parseFloat(valueB) || 0;
+ return direction === 'asc' ? valueA - valueB : valueB - valueA;
+ }
+ });
+ }
+
+ // --- Data Update/Display Functions ---
+ function updateNodesTable(nodes, skipSorting = false) {
+ const tbody = document.querySelector('#nodes-table tbody');
+ if (!tbody) return; // Guard
+ tbody.innerHTML = '';
+
+ const dataToDisplay = skipSorting ? (nodes || []) : sortData(nodes, sortState.nodes.column, sortState.nodes.direction, 'nodes');
+
+ if (dataToDisplay.length === 0) {
+ tbody.innerHTML = '| No nodes found |
';
+ return;
+ }
+
+ dataToDisplay.forEach(node => {
+ const row = document.createElement('tr');
+ row.className = 'border-b border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700/60 transition-colors duration-150 ease-in-out';
+
+ const statusColor = node.status === 'online' ? 'bg-green-500' : 'bg-red-500'; // Use red for non-online, consider gray/yellow for others if needed
+ const cpuPercent = (node.cpu || 0) * 100;
+ const memPercent = node.maxmem > 0 ? ((node.mem || 0) / node.maxmem) * 100 : 0;
+
+ // Determine color based on percentage
+ const getUsageColor = (percent) => {
+ if (percent > 85) return 'bg-red-500';
+ if (percent > 65) return 'bg-yellow-500';
+ return 'bg-green-500'; // Default to green
+ };
+
+ const cpuColorClass = getUsageColor(cpuPercent);
+ const memColorClass = getUsageColor(memPercent);
+
+ row.innerHTML = `
+ ${node.node || 'N/A'} |
+
+
+
+ ${node.status || 'N/A'}
+
+ |
+
+
+
+
+ ${cpuPercent.toFixed(1)}%
+
+
+ |
+
+
+
+
+ ${formatBytes(node.mem)} / ${formatBytes(node.maxmem)} (${memPercent.toFixed(1)}%)
+
+
+ |
+ ${formatBytes(node.maxmem)} |
+ ${formatUptime(node.uptime)} |
+ ${node.ip || 'N/A'} |
+ `;
+ tbody.appendChild(row);
+ });
+
+ // Re-enable tooltips if the library/method exists (assuming a simple CSS hover tooltip here)
+ // This example uses group-hover, so no extra JS needed for *these* tooltips.
+ }
+
+ function updateVmsTable(vms, skipSorting = false) {
+ const tbody = document.querySelector('#vms-table tbody');
+ if (!tbody) return; // Guard
+ tbody.innerHTML = '';
+
+ const dataToDisplay = skipSorting ? (vms || []) : sortData(vms, sortState.vms.column, sortState.vms.direction, 'vms');
+
+ if (dataToDisplay.length === 0) {
+ tbody.innerHTML = '| No VMs found |
';
+ return;
+ }
+
+ dataToDisplay.forEach(vm => {
+ const row = document.createElement('tr');
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors';
+ row.innerHTML = `
+ ${vm.vmid || 'N/A'} |
+ ${vm.name || 'N/A'} |
+ ${vm.node || 'N/A'} |
+ ${vm.status || 'N/A'} |
+ ${vm.cpus || 'N/A'} |
+ ${formatBytes(vm.maxmem)} |
+ ${formatBytes(vm.maxdisk)} |
+ ${formatUptime(vm.uptime)} |
+ `;
+ tbody.appendChild(row);
+ });
+ }
+
+ function updateContainersTable(containers, skipSorting = false) {
+ const tbody = document.querySelector('#containers-table tbody');
+ if (!tbody) return; // Guard
+ tbody.innerHTML = '';
+
+ const dataToDisplay = skipSorting ? (containers || []) : sortData(containers, sortState.containers.column, sortState.containers.direction, 'containers');
+
+ if (dataToDisplay.length === 0) {
+ tbody.innerHTML = '| No containers found |
';
+ return;
+ }
+
+ dataToDisplay.forEach(ct => {
+ const row = document.createElement('tr');
+ row.className = 'hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors';
+ row.innerHTML = `
+ ${ct.vmid || 'N/A'} |
+ ${ct.name || 'N/A'} |
+ ${ct.node || 'N/A'} |
+ ${ct.status || 'N/A'} |
+ ${ct.cpus || 'N/A'} |
+ ${formatBytes(ct.maxmem)} |
+ ${formatBytes(ct.maxdisk)} |
+ ${formatUptime(ct.uptime)} |
+ `;
+ tbody.appendChild(row);
+ });
+ }
+
+ // --- Formatting Helpers ---
+ function formatBytes(bytes) {
+ if (bytes === undefined || bytes === null || isNaN(bytes)) return 'N/A';
+ if (bytes <= 0) return '0 B'; // Handle 0 or negative
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
+ const unitIndex = Math.max(0, Math.min(i, units.length - 1));
+ const value = bytes / Math.pow(1024, unitIndex);
+ return `${parseFloat(value.toFixed(unitIndex === 0 ? 0 : 1))} ${units[unitIndex]}`;
+ }
+ function formatCpu(cpu) {
+ if (cpu === undefined || cpu === null || isNaN(cpu)) return 'N/A';
+ return `${(cpu * 100).toFixed(1)}%`;
+ }
+ function formatUptime(seconds) {
+ if (seconds === undefined || seconds === null || isNaN(seconds) || seconds < 0) return 'N/A';
+ if (seconds < 60) return '<1m';
+ const d = Math.floor(seconds / 86400);
+ const h = Math.floor((seconds % 86400) / 3600);
+ const m = Math.floor((seconds % 3600) / 60);
+ let str = '';
+ if (d > 0) str += `${d}d `;
+ if (h > 0 || d > 0) str += `${h}h `; // Show 0h if days are present
+ str += `${m}m`;
+ return str.trim();
+ }
+ function formatSpeed(bytesPerSecond) {
+ if (bytesPerSecond === undefined || bytesPerSecond === null || isNaN(bytesPerSecond)) return 'N/A';
+ if (bytesPerSecond < 1) return '0 B/s';
+ return `${formatBytes(bytesPerSecond)}/s`;
+ }
+ function formatBytesInt(bytes) {
+ if (bytes === undefined || bytes === null || isNaN(bytes)) return 'N/A';
+ if (bytes <= 0) return '0 B';
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
+ const unitIndex = Math.max(0, Math.min(i, units.length - 1));
+ const value = bytes / Math.pow(1024, unitIndex);
+ return `${Math.max(1, Math.round(value))} ${units[unitIndex]}`;
+ }
+ function formatCpuInt(cpu) {
+ if (cpu === undefined || cpu === null || isNaN(cpu)) return 'N/A';
+ return `${Math.round(cpu * 100)}%`;
+ }
+ function formatSpeedInt(bytesPerSecond) {
+ if (bytesPerSecond === undefined || bytesPerSecond === null || isNaN(bytesPerSecond)) return 'N/A';
+ if (bytesPerSecond < 1) return '0 B/s';
+ return `${formatBytesInt(bytesPerSecond)}/s`;
+ }
+
+ // --- Dashboard Data Processing & Display ---
+ function refreshDashboardData() {
+ dashboardData = [];
+ console.log('[refreshDashboardData] Starting refresh...');
+
+ let maxNameLength = 0;
+ let maxUptimeLength = 0;
+
+ // Helper: Calculates average, returns null if invalid/insufficient data
+ function calculateAverage(historyArray, key) {
+ if (!historyArray || historyArray.length === 0) return null;
+ const validEntries = historyArray.filter(entry => typeof entry[key] === 'number' && !isNaN(entry[key]));
+ if (validEntries.length === 0) return null;
+ const sum = validEntries.reduce((acc, curr) => acc + curr[key], 0);
+ return sum / validEntries.length;
+ }
+
+ // Helper: Calculates rate, returns null if invalid/insufficient data
+ function calculateAverageRate(historyArray, key) {
+ if (!historyArray || historyArray.length < 2) return null;
+ const validHistory = historyArray.filter(entry =>
+ typeof entry.timestamp === 'number' && !isNaN(entry.timestamp) &&
+ typeof entry[key] === 'number' && !isNaN(entry[key])
+ );
+ if (validHistory.length < 2) return null;
+ const oldest = validHistory[0];
+ const newest = validHistory[validHistory.length - 1];
+ const valueDiff = newest[key] - oldest[key];
+ const timeDiff = (newest.timestamp - oldest.timestamp) / 1000;
+ if (timeDiff <= 0) return 0;
+ return valueDiff / timeDiff;
+ }
+
+ // Process VMs and Containers
+ const processGuest = (guest, type) => {
+ const metrics = (metricsData || []).find(m => m.id === guest.vmid && m.type === type);
+ // console.log(`[refreshDashboardData] Processing ${type} ${guest.vmid} (${guest.name}). Found metrics:`, metrics);
+
+ let avgCpu = 0, avgMem = 0, avgDisk = 0;
+ let avgDiskReadRate = 0, avgDiskWriteRate = 0, avgNetInRate = 0, avgNetOutRate = 0;
+ let avgMemoryPercent = 'N/A', avgDiskPercent = 'N/A';
+
+ if (guest.status === 'running' && metrics && metrics.current) {
+ // Only process metrics history for running guests with current metrics
+ console.log(`[dbg ${guest.vmid}] metrics.current:`, JSON.stringify(metrics.current)); // DEBUG: Log raw current metrics
+ if (!dashboardHistory[guest.vmid]) dashboardHistory[guest.vmid] = [];
+ const history = dashboardHistory[guest.vmid];
+ const currentDataPoint = { timestamp: Date.now(), ...metrics.current };
+ history.push(currentDataPoint);
+ if (history.length > AVERAGING_WINDOW_SIZE) history.shift();
+
+ avgCpu = calculateAverage(history, 'cpu') ?? 0;
+ avgMem = calculateAverage(history, 'mem') ?? 0;
+ avgDisk = calculateAverage(history, 'disk') ?? 0;
+ avgDiskReadRate = calculateAverageRate(history, 'diskread') ?? 0;
+ avgDiskWriteRate = calculateAverageRate(history, 'diskwrite') ?? 0;
+ avgNetInRate = calculateAverageRate(history, 'netin') ?? 0;
+ avgNetOutRate = calculateAverageRate(history, 'netout') ?? 0;
+ avgMemoryPercent = (guest.maxmem > 0) ? Math.round(avgMem / guest.maxmem * 100) : 'N/A';
+ avgDiskPercent = (guest.maxdisk > 0) ? Math.round(avgDisk / guest.maxdisk * 100) : 'N/A';
+ console.log(`[dbg ${guest.vmid}] Rates (B/s): netin=${avgNetInRate?.toFixed(0)}, netout=${avgNetOutRate?.toFixed(0)}, diskread=${avgDiskReadRate?.toFixed(0)}, diskwrite=${avgDiskWriteRate?.toFixed(0)}`); // DEBUG: Log calculated rates
+ // console.log(`[refreshDashboardData] ${type} ${guest.vmid} Calculated Percentages: Mem%=${avgMemoryPercent}, Disk%=${avgDiskPercent}`);
+ } else if (guest.status === 'stopped') {
+ // Clear history for stopped guests
+ if (dashboardHistory[guest.vmid]) {
+ delete dashboardHistory[guest.vmid];
+ }
+ // Metrics remain at default 0 / N/A for stopped guests
+ }
+
+ const name = guest.name || `${type === 'qemu' ? 'VM' : 'CT'} ${guest.vmid}`;
+ const uptimeFormatted = formatUptime(guest.uptime);
+ if (name.length > maxNameLength) maxNameLength = name.length;
+ if (uptimeFormatted.length > maxUptimeLength) maxUptimeLength = uptimeFormatted.length;
+
+ // Always push the guest data, using defaults for stopped guests
+ dashboardData.push({
+ id: guest.vmid, name: name, node: guest.node,
+ type: type === 'qemu' ? 'VM' : 'CT',
+ status: guest.status,
+ cpu: avgCpu,
+ cpus: guest.cpus || 1,
+ memory: avgMemoryPercent, // 'N/A' for stopped or if no maxmem
+ memoryCurrent: avgMem,
+ memoryTotal: guest.maxmem,
+ disk: avgDiskPercent, // 'N/A' for stopped or if no maxdisk
+ diskCurrent: avgDisk,
+ diskTotal: guest.maxdisk,
+ uptime: guest.status === 'running' ? guest.uptime : 0, // Sort stopped guests differently
+ diskread: avgDiskReadRate,
+ diskwrite: avgDiskWriteRate,
+ netin: avgNetInRate,
+ netout: avgNetOutRate
+ });
+ };
+
+ (vmsData || []).forEach(vm => processGuest(vm, 'qemu'));
+ (containersData || []).forEach(ct => processGuest(ct, 'lxc'));
+
+ // Set Column Widths
+ const nameColWidth = Math.min(Math.max(maxNameLength * 8 + 16, 100), 300);
+ const uptimeColWidth = Math.max(maxUptimeLength * 7 + 16, 80);
+ if (htmlElement) {
+ htmlElement.style.setProperty('--name-col-width', `${nameColWidth}px`);
+ htmlElement.style.setProperty('--uptime-col-width', `${uptimeColWidth}px`);
+ }
+
+ updateDashboardTable(); // Render the table
+ }
+
+ function updateDashboardTable() {
+ if (!mainTableBody) return;
+ mainTableBody.innerHTML = ''; // Clear
+
+ const currentSearchTerm = searchInput ? searchInput.value.toLowerCase() : '';
+
+ // Apply type filter
+ const typeFilteredData = (dashboardData || []).filter(guest =>
+ filterGuestType === 'all' || (guest.type && guest.type.toLowerCase() === filterGuestType)
+ );
+
+ // Apply status filter
+ const statusFilteredData = typeFilteredData.filter(guest =>
+ filterStatus === 'all' || guest.status === filterStatus
+ );
+
+ // Apply sorting
+ const sortedData = sortData(statusFilteredData, sortState.main.column, sortState.main.direction, 'main');
+
+ // Apply search filter
+ const searchTerms = (searchInput ? searchInput.value.toLowerCase().split(',').map(term => term.trim()).filter(term => term) : []);
+
+ // Filter data based on search terms, type filter, and status filter
+ let filteredData = sortedData.filter(item => {
+ const typeMatch = (filterGuestType === 'all' || (item.type && item.type.toLowerCase() === filterGuestType));
+ const statusMatch = (filterStatus === 'all' || item.status === filterStatus);
+ const nameMatch = searchTerms.length === 0 || searchTerms.some(term =>
+ (item.name?.toLowerCase() || '').includes(term) ||
+ (item.node?.toLowerCase() || '').includes(term) || // Allow searching node name
+ (item.id?.toString() || '').includes(term) // Allow searching ID
+ );
+ return typeMatch && statusMatch && nameMatch; // Combine all filters
+ });
+
+ // Group data if needed
+ const nodeGroups = {};
+ if (groupByNode) {
+ filteredData.forEach(guest => {
+ if (!nodeGroups[guest.node]) nodeGroups[guest.node] = [];
+ nodeGroups[guest.node].push(guest);
+ });
+ }
+
+ // Render Rows
+ let visibleCount = 0;
+ let visibleNodes = new Set();
+
+ if (groupByNode) {
+ Object.keys(nodeGroups).sort().forEach(nodeName => {
+ visibleNodes.add(nodeName.toLowerCase());
+ const nodeHeaderRow = document.createElement('tr');
+ nodeHeaderRow.className = 'node-header bg-gray-100 dark:bg-gray-700/80 font-semibold text-gray-700 dark:text-gray-300 text-xs';
+ nodeHeaderRow.innerHTML = `
+
+ ${nodeName}
+ | `;
+ mainTableBody.appendChild(nodeHeaderRow);
+ nodeGroups[nodeName].forEach(guest => {
+ mainTableBody.appendChild(createGuestRow(guest));
+ visibleCount++;
+ });
+ });
+ } else {
+ filteredData.forEach(guest => {
+ mainTableBody.appendChild(createGuestRow(guest));
+ visibleCount++;
+ visibleNodes.add(guest.node.toLowerCase());
+ });
+ }
+
+ // Handle empty table states
+ if (visibleCount === 0) {
+ const filterText = currentSearchTerm ? ` match filter "${currentSearchTerm}"` : '';
+ const typeText = filterGuestType !== 'all' ? filterGuestType.toUpperCase() + 's' : 'guests';
+ const statusText = filterStatus !== 'all' ? ` (${filterStatus})` : ''; // Add status to the message
+ mainTableBody.innerHTML = `| No ${typeText}${statusText}${filterText} found |
`;
+ }
+
+ // Update Status Text
+ if (statusElement) {
+ const statusBaseText = `Updated: ${new Date().toLocaleTimeString()}`;
+ let statusFilterText = currentSearchTerm ? ` | Filter: "${currentSearchTerm}"` : '';
+ let statusCountText = ` | Showing ${visibleCount}`;
+ if (filterGuestType !== 'all') statusCountText += ` ${filterGuestType.toUpperCase()}s`;
+ if (filterStatus !== 'all') statusCountText += ` (${filterStatus})`; // Add status to the count text
+ statusCountText += ` guests`;
+ if (groupByNode && visibleNodes.size > 0) statusCountText += ` across ${visibleNodes.size} nodes`;
+ statusElement.textContent = statusBaseText + statusFilterText + statusCountText;
+ }
+ }
+
+ function createGuestRow(guest) {
+ // console.log('[createGuestRow] Received guest data:', guest);
+ const row = document.createElement('tr');
+ // Add more prominent hover background, shadow, lift effect, and transition
+ row.className = `transition-all duration-150 ease-out hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md hover:-translate-y-px ${guest.status === 'stopped' ? 'opacity-60' : ''}`;
+ row.setAttribute('data-name', guest.name.toLowerCase());
+ row.setAttribute('data-type', guest.type.toLowerCase());
+ row.setAttribute('data-node', guest.node.toLowerCase());
+ row.setAttribute('data-id', guest.id);
+
+ const memoryPercent = guest.memory; // Already calculated, possibly 'N/A'
+ const diskPercent = guest.disk; // Already calculated, possibly 'N/A'
+ const cpuPercent = Math.round(guest.cpu * 100);
+
+ const cpuAbsolute = guest.cpus ? `(${(guest.cpu * guest.cpus).toFixed(1)}/${guest.cpus} cores)` : '';
+ const memoryAbsolute = guest.memoryTotal ? `(${formatBytesInt(guest.memoryCurrent)} / ${formatBytesInt(guest.memoryTotal)})` : '';
+ const diskAbsolute = guest.diskTotal ? `(${formatBytesInt(guest.diskCurrent)} / ${formatBytesInt(guest.diskTotal)})` : '';
+
+ const cpuUsageText = createUsageText(cpuPercent, cpuAbsolute);
+ const memoryUsageText = createUsageText(memoryPercent, memoryAbsolute);
+ const diskUsageText = createUsageText(diskPercent, diskAbsolute);
+
+ const typeIconClass = guest.type === 'VM'
+ ? 'vm-icon bg-blue-100 dark:bg-blue-900/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-700'
+ : 'ct-icon bg-green-100 dark:bg-green-900/50 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-700';
+ const typeIcon = `${guest.type}`;
+
+ row.innerHTML = `
+ ${guest.name} |
+ ${typeIcon} |
+ ${guest.id} |
+ ${formatUptime(guest.uptime)} |
+ ${cpuUsageText} |
+ ${memoryUsageText} |
+ ${diskUsageText} |
+ ${formatSpeedInt(guest.diskread)} |
+ ${formatSpeedInt(guest.diskwrite)} |
+ ${formatSpeedInt(guest.netin)} |
+ ${formatSpeedInt(guest.netout)} |
+ `;
+ return row;
+ }
+
+ function createUsageText(percentage, tooltipText = '') {
+ // console.log(`[createUsageText] Received percentage: ${percentage}, tooltip: ${tooltipText}`);
+ let colorClass = '';
+ let displayPercentage = percentage;
+
+ if (percentage === 'N/A' || isNaN(percentage)) {
+ displayPercentage = 'N/A';
+ colorClass = 'text-gray-400 dark:text-gray-500';
+ } else {
+ const numericPercentage = parseInt(percentage);
+ displayPercentage = `${numericPercentage}%`;
+ if (numericPercentage > 85) {
+ colorClass = 'text-red-600 dark:text-red-400 font-medium';
+ } else if (numericPercentage > 65) {
+ colorClass = 'text-yellow-600 dark:text-yellow-400';
+ } else {
+ colorClass = 'text-green-600 dark:text-green-400';
+ }
+ }
+ const safeTooltipText = tooltipText.replace(/"/g, '"');
+ return `${displayPercentage}`;
+ }
+
+ // --- WebSocket Message Handling ---
+ // Add a generic listener to catch *any* events from the server
+ socket.onAny((eventName, ...args) => {
+ console.log(`[socket.onAny] Received event: ${eventName}`, args);
+ });
+
+ // Listener for the 'rawData' event from the server
+ socket.on('rawData', (jsonData) => {
+ console.log('[socket.on("rawData")] Received data event');
+ try {
+ // Assuming server sends data as a JSON string
+ const data = typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
+
+ // Update global data stores
+ nodesData = data.nodes || [];
+ vmsData = data.vms || [];
+ containersData = data.containers || [];
+ metricsData = data.metrics || [];
+ console.log('[socket.on("rawData")] Parsed data');
+
+ // Update UI tables
+ updateNodesTable(nodesData);
+ updateVmsTable(vmsData);
+ updateContainersTable(containersData);
+ refreshDashboardData(); // Process and update the main dashboard
+ console.log('[socket.on("rawData")] Processed data and updated UI');
+
+ } catch (e) {
+ console.error('Error processing received rawData:', e, jsonData);
+ }
+ });
+
+ function requestFullData() {
+ console.log("Requesting full data...");
+ if (socket.connected) {
+ socket.emit('requestData'); // Standard emit
+ } else {
+ console.warn("Socket not connected, cannot request full data.");
+ }
+ }
+
+ // --- Tooltip Logic ---
+ if (mainTableBody && tooltipElement) {
+ mainTableBody.addEventListener('mouseover', (event) => {
+ const target = event.target.closest('.metric-tooltip-trigger');
+ if (target) {
+ const tooltipText = target.getAttribute('data-tooltip');
+ if (tooltipText) {
+ tooltipElement.textContent = tooltipText;
+ const offsetX = 10;
+ const offsetY = 15;
+ tooltipElement.style.left = `${event.pageX + offsetX}px`;
+ tooltipElement.style.top = `${event.pageY + offsetY}px`;
+ tooltipElement.classList.remove('hidden', 'opacity-0');
+ tooltipElement.classList.add('opacity-100');
+ }
+ }
+ });
+ mainTableBody.addEventListener('mouseout', (event) => {
+ const target = event.target.closest('.metric-tooltip-trigger');
+ if (target) {
+ tooltipElement.classList.add('hidden', 'opacity-0');
+ tooltipElement.classList.remove('opacity-100');
+ }
+ });
+ mainTableBody.addEventListener('mousemove', (event) => {
+ const target = event.target.closest('.metric-tooltip-trigger');
+ if (!tooltipElement.classList.contains('hidden') && target) {
+ // Update position while moving over the trigger
+ const offsetX = 10;
+ const offsetY = 15;
+ tooltipElement.style.left = `${event.pageX + offsetX}px`;
+ tooltipElement.style.top = `${event.pageY + offsetY}px`;
+ } else if (!tooltipElement.classList.contains('hidden') && !target) {
+ // Optional: hide if mouse moves off trigger onto non-trigger area
+ // tooltipElement.classList.add('hidden', 'opacity-0');
+ // tooltipElement.classList.remove('opacity-100');
+ }
+ });
+ }
+
+ // --- Reset Filters/Sort Listener ---
+ document.addEventListener('keydown', function(event) {
+ if (event.key === 'Escape') {
+ if(searchInput) searchInput.value = '';
+ sortState.main = { column: 'id', direction: 'asc' };
+ updateSortUI('main-table', document.querySelector('#main-table th[data-sort="id"]'));
+ const groupGroupedRadio = document.getElementById('group-grouped');
+ if(groupGroupedRadio) groupGroupedRadio.checked = true;
+ groupByNode = true;
+ const filterAllRadio = document.getElementById('filter-all');
+ if(filterAllRadio) filterAllRadio.checked = true;
+ filterGuestType = 'all';
+ filterStatus = 'all';
+ updateDashboardTable();
+ }
+ });
+
+ // --- Initial Setup Calls ---
+ updateSortUI('main-table', document.querySelector('#main-table th[data-sort="id"]'));
+ // Data is requested on socket 'connect' event
+
+}); // End DOMContentLoaded
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 000000000..3c6717160
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,219 @@
+
+
+
+
+
+ Pulse
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Nodes
+
+
+
+
+ | ID |
+ Status |
+ CPU Usage |
+ Mem Usage |
+ Mem Total |
+ Uptime |
+ IP |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Type |
+ ID |
+ Uptime |
+ CPU |
+ Mem |
+ Disk |
+ Disk Read |
+ Disk Write |
+ Net In |
+ Net Out |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
+
Loading dashboard data...
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/index.html.bak b/public/index.html.bak
new file mode 100644
index 000000000..2a1b33358
--- /dev/null
+++ b/public/index.html.bak
@@ -0,0 +1,232 @@
+
+
+
+
+
+ Pulse
+
+
+
+
+
+
+
+
+
+
+
Main
+
Nodes
+
VMs
+
Containers
+
+
+
+
+
+
Nodes
+
+
+
+
+ | ID |
+ Status |
+ CPU |
+ Memory Used |
+ Memory Total |
+ Uptime |
+ IP |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
Virtual Machines
+
+
+
+
+ | ID |
+ Name |
+ Node |
+ Status |
+ CPU |
+ Memory |
+ Disk |
+ Uptime |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
Containers
+
+
+
+
+ | ID |
+ Name |
+ Node |
+ Status |
+ CPU |
+ Memory |
+ Disk |
+ Uptime |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Name |
+ Type |
+ ID |
+ Uptime |
+ CPU |
+ Mem |
+ Disk |
+ Disk Read |
+ Disk Write |
+ Net In |
+ Net Out |
+
+
+
+
+ | Loading data... |
+
+
+
+
+
+
+
+
+
+
Loading dashboard data...
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 000000000..9c9b212f2
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/public/logos/README.md b/public/logos/README.md
deleted file mode 100644
index a22c43ae7..000000000
--- a/public/logos/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-# Pulse Logo Assets
-
-This directory contains various PNG versions of the Pulse logo for use in different contexts.
-
-## Square Logos (Icon Only)
-
-These are square logos containing only the Pulse icon (no text):
-
-- `pulse-logo-16x16.png` - Favicon size
-- `pulse-logo-32x32.png` - Small icon size
-- `pulse-logo-64x64.png` - Medium icon size
-- `pulse-logo-128x128.png` - Large icon size
-- `pulse-logo-256x256.png` - Extra large icon size
-- `pulse-logo-512x512.png` - App icon size
-- `pulse-logo-1024x1024.png` - High-resolution icon size
-
-## Rectangular Logos (Icon with Text)
-
-These are rectangular logos containing both the Pulse icon and text:
-
-- `pulse-logo-with-text-200x60.png` - Small banner size
-- `pulse-logo-with-text-400x120.png` - Medium banner size
-- `pulse-logo-with-text-800x240.png` - Large banner size
-
-## Usage Guidelines
-
-- For favicons and small UI elements, use the smaller square logos (16x16, 32x32)
-- For app icons and larger UI elements, use the medium to large square logos (64x64, 128x128, 256x256)
-- For headers, banners, and marketing materials, use the rectangular logos with text
-- For high-resolution displays or when you need to scale the logo, use the largest sizes (512x512, 1024x1024)
-
-## Generating New Sizes
-
-If you need additional sizes or variations, you can run the logo generation scripts:
-
-```bash
-# Install the required dependencies
-npm install canvas
-
-# Generate square logos (icon only)
-node scripts/generate-logo-pngs.js
-
-# Generate rectangular logos (icon with text)
-node scripts/generate-logo-with-text.js
-```
-
-## Logo Design
-
-The Pulse logo features:
-- A circular background with a blue gradient (#3a7bd5)
-- A pulsing animation ring (represented as a static ring in the PNG versions)
-- A central white dot representing the core of the pulse
\ No newline at end of file
diff --git a/public/logos/pulse-logo-1024x1024.png b/public/logos/pulse-logo-1024x1024.png
deleted file mode 100644
index 076a3061e..000000000
Binary files a/public/logos/pulse-logo-1024x1024.png and /dev/null differ
diff --git a/public/logos/pulse-logo-128x128.png b/public/logos/pulse-logo-128x128.png
deleted file mode 100644
index 6eaf270f0..000000000
Binary files a/public/logos/pulse-logo-128x128.png and /dev/null differ
diff --git a/public/logos/pulse-logo-16x16.png b/public/logos/pulse-logo-16x16.png
deleted file mode 100644
index 53bb65870..000000000
Binary files a/public/logos/pulse-logo-16x16.png and /dev/null differ
diff --git a/public/logos/pulse-logo-256x256.png b/public/logos/pulse-logo-256x256.png
deleted file mode 100644
index 041234d86..000000000
Binary files a/public/logos/pulse-logo-256x256.png and /dev/null differ
diff --git a/public/logos/pulse-logo-32x32.png b/public/logos/pulse-logo-32x32.png
deleted file mode 100644
index 275e1f55b..000000000
Binary files a/public/logos/pulse-logo-32x32.png and /dev/null differ
diff --git a/public/logos/pulse-logo-512x512.png b/public/logos/pulse-logo-512x512.png
deleted file mode 100644
index 8c8ad19ce..000000000
Binary files a/public/logos/pulse-logo-512x512.png and /dev/null differ
diff --git a/public/logos/pulse-logo-64x64.png b/public/logos/pulse-logo-64x64.png
deleted file mode 100644
index 9cc297141..000000000
Binary files a/public/logos/pulse-logo-64x64.png and /dev/null differ
diff --git a/public/logos/pulse-logo-with-text-200x60.png b/public/logos/pulse-logo-with-text-200x60.png
deleted file mode 100644
index e4e4ab96e..000000000
Binary files a/public/logos/pulse-logo-with-text-200x60.png and /dev/null differ
diff --git a/public/logos/pulse-logo-with-text-400x120.png b/public/logos/pulse-logo-with-text-400x120.png
deleted file mode 100644
index dc1b38296..000000000
Binary files a/public/logos/pulse-logo-with-text-400x120.png and /dev/null differ
diff --git a/public/logos/pulse-logo-with-text-800x240.png b/public/logos/pulse-logo-with-text-800x240.png
deleted file mode 100644
index 2254b1aae..000000000
Binary files a/public/logos/pulse-logo-with-text-800x240.png and /dev/null differ
diff --git a/public/output.css b/public/output.css
new file mode 100644
index 000000000..04fc13d02
--- /dev/null
+++ b/public/output.css
@@ -0,0 +1,1593 @@
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+/*
+! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: #e5e7eb;
+ /* 2 */
+}
+
+::before,
+::after {
+ --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+ line-height: 1.5;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+ -moz-tab-size: 4;
+ /* 3 */
+ -o-tab-size: 4;
+ tab-size: 4;
+ /* 3 */
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 4 */
+ font-feature-settings: normal;
+ /* 5 */
+ font-variation-settings: normal;
+ /* 6 */
+ -webkit-tap-highlight-color: transparent;
+ /* 7 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+ margin: 0;
+ /* 1 */
+ line-height: inherit;
+ /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+ border-top-width: 1px;
+ /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ /* 1 */
+ font-feature-settings: normal;
+ /* 2 */
+ font-variation-settings: normal;
+ /* 3 */
+ font-size: 1em;
+ /* 4 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+ border-collapse: collapse;
+ /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-feature-settings: inherit;
+ /* 1 */
+ font-variation-settings: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ font-weight: inherit;
+ /* 1 */
+ line-height: inherit;
+ /* 1 */
+ letter-spacing: inherit;
+ /* 1 */
+ color: inherit;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+ padding: 0;
+ /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+ text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+input:where([type='button']),
+input:where([type='reset']),
+input:where([type='submit']) {
+ -webkit-appearance: button;
+ /* 1 */
+ background-color: transparent;
+ /* 2 */
+ background-image: none;
+ /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+ outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+legend {
+ padding: 0;
+}
+
+ol,
+ul,
+menu {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+ padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+ resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ /* 1 */
+ color: #9ca3af;
+ /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+ cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+ This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden]:where(:not([hidden="until-found"])) {
+ display: none;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.pointer-events-none {
+ pointer-events: none;
+}
+
+.visible {
+ visibility: visible;
+}
+
+.absolute {
+ position: absolute;
+}
+
+.relative {
+ position: relative;
+}
+
+.sticky {
+ position: sticky;
+}
+
+.bottom-0 {
+ bottom: 0px;
+}
+
+.bottom-\[4px\] {
+ bottom: 4px;
+}
+
+.left-0 {
+ left: 0px;
+}
+
+.left-\[4px\] {
+ left: 4px;
+}
+
+.right-0 {
+ right: 0px;
+}
+
+.top-0 {
+ top: 0px;
+}
+
+.z-10 {
+ z-index: 10;
+}
+
+.z-50 {
+ z-index: 50;
+}
+
+.z-\[1\] {
+ z-index: 1;
+}
+
+.mx-auto {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.-mb-px {
+ margin-bottom: -1px;
+}
+
+.mb-2 {
+ margin-bottom: 0.5rem;
+}
+
+.mb-3 {
+ margin-bottom: 0.75rem;
+}
+
+.ml-1 {
+ margin-left: 0.25rem;
+}
+
+.mr-1 {
+ margin-right: 0.25rem;
+}
+
+.mr-2 {
+ margin-right: 0.5rem;
+}
+
+.block {
+ display: block;
+}
+
+.inline-block {
+ display: inline-block;
+}
+
+.flex {
+ display: flex;
+}
+
+.inline-flex {
+ display: inline-flex;
+}
+
+.table {
+ display: table;
+}
+
+.contents {
+ display: contents;
+}
+
+.hidden {
+ display: none;
+}
+
+.h-0 {
+ height: 0px;
+}
+
+.h-5 {
+ height: 1.25rem;
+}
+
+.h-7 {
+ height: 1.75rem;
+}
+
+.h-\[20px\] {
+ height: 20px;
+}
+
+.h-\[28px\] {
+ height: 28px;
+}
+
+.max-h-\[80vh\] {
+ max-height: 80vh;
+}
+
+.w-0 {
+ width: 0px;
+}
+
+.w-5 {
+ width: 1.25rem;
+}
+
+.w-\[20px\] {
+ width: 20px;
+}
+
+.w-\[60px\] {
+ width: 60px;
+}
+
+.w-full {
+ width: 100%;
+}
+
+.max-w-\[95\%\] {
+ max-width: 95%;
+}
+
+.flex-1 {
+ flex: 1 1 0%;
+}
+
+.flex-grow {
+ flex-grow: 1;
+}
+
+.table-fixed {
+ table-layout: fixed;
+}
+
+.border-collapse {
+ border-collapse: collapse;
+}
+
+.transform {
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.cursor-default {
+ cursor: default;
+}
+
+.cursor-pointer {
+ cursor: pointer;
+}
+
+.select-none {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.flex-wrap {
+ flex-wrap: wrap;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.items-stretch {
+ align-items: stretch;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-2 {
+ gap: 0.5rem;
+}
+
+.gap-3 {
+ gap: 0.75rem;
+}
+
+.gap-4 {
+ gap: 1rem;
+}
+
+.space-x-1 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.25rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+}
+
+.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(229 231 235 / var(--tw-divide-opacity, 1));
+}
+
+.overflow-hidden {
+ overflow: hidden;
+}
+
+.overflow-x-auto {
+ overflow-x: auto;
+}
+
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
+.truncate {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.whitespace-nowrap {
+ white-space: nowrap;
+}
+
+.rounded {
+ border-radius: 0.25rem;
+}
+
+.rounded-full {
+ border-radius: 9999px;
+}
+
+.rounded-lg {
+ border-radius: 0.5rem;
+}
+
+.rounded-md {
+ border-radius: 0.375rem;
+}
+
+.rounded-b {
+ border-bottom-right-radius: 0.25rem;
+ border-bottom-left-radius: 0.25rem;
+}
+
+.rounded-t {
+ border-top-left-radius: 0.25rem;
+ border-top-right-radius: 0.25rem;
+}
+
+.rounded-tr {
+ border-top-right-radius: 0.25rem;
+}
+
+.border {
+ border-width: 1px;
+}
+
+.border-b {
+ border-bottom-width: 1px;
+}
+
+.border-b-0 {
+ border-bottom-width: 0px;
+}
+
+.border-l {
+ border-left-width: 1px;
+}
+
+.border-blue-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(191 219 254 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
+}
+
+.border-gray-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
+}
+
+.border-green-200 {
+ --tw-border-opacity: 1;
+ border-color: rgb(187 247 208 / var(--tw-border-opacity, 1));
+}
+
+.border-transparent {
+ border-color: transparent;
+}
+
+.bg-blue-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
+}
+
+.bg-blue-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
+}
+
+.bg-gray-900\/90 {
+ background-color: rgb(17 24 39 / 0.9);
+}
+
+.bg-green-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1));
+}
+
+.bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
+}
+
+.bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.bg-slate-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(241 245 249 / var(--tw-bg-opacity, 1));
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+
+.p-2 {
+ padding: 0.5rem;
+}
+
+.p-3 {
+ padding: 0.75rem;
+}
+
+.p-4 {
+ padding: 1rem;
+}
+
+.px-1 {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+}
+
+.px-2 {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.px-3 {
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+}
+
+.py-0\.5 {
+ padding-top: 0.125rem;
+ padding-bottom: 0.125rem;
+}
+
+.py-1 {
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+}
+
+.py-1\.5 {
+ padding-top: 0.375rem;
+ padding-bottom: 0.375rem;
+}
+
+.pl-4 {
+ padding-left: 1rem;
+}
+
+.text-left {
+ text-align: left;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.align-middle {
+ vertical-align: middle;
+}
+
+.font-sans {
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+.text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+}
+
+.text-\[10px\] {
+ font-size: 10px;
+}
+
+.text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+}
+
+.text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.text-xs {
+ font-size: 0.75rem;
+ line-height: 1rem;
+}
+
+.font-bold {
+ font-weight: 700;
+}
+
+.font-medium {
+ font-weight: 500;
+}
+
+.font-semibold {
+ font-weight: 600;
+}
+
+.leading-5 {
+ line-height: 1.25rem;
+}
+
+.text-blue-700 {
+ --tw-text-opacity: 1;
+ color: rgb(29 78 216 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-400 {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-500 {
+ --tw-text-opacity: 1;
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-600 {
+ --tw-text-opacity: 1;
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-700 {
+ --tw-text-opacity: 1;
+ color: rgb(55 65 81 / var(--tw-text-opacity, 1));
+}
+
+.text-gray-800 {
+ --tw-text-opacity: 1;
+ color: rgb(31 41 55 / var(--tw-text-opacity, 1));
+}
+
+.text-green-600 {
+ --tw-text-opacity: 1;
+ color: rgb(22 163 74 / var(--tw-text-opacity, 1));
+}
+
+.text-green-700 {
+ --tw-text-opacity: 1;
+ color: rgb(21 128 61 / var(--tw-text-opacity, 1));
+}
+
+.text-red-600 {
+ --tw-text-opacity: 1;
+ color: rgb(220 38 38 / var(--tw-text-opacity, 1));
+}
+
+.text-red-700 {
+ --tw-text-opacity: 1;
+ color: rgb(185 28 28 / var(--tw-text-opacity, 1));
+}
+
+.text-white {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
+}
+
+.text-yellow-600 {
+ --tw-text-opacity: 1;
+ color: rgb(202 138 4 / var(--tw-text-opacity, 1));
+}
+
+.text-slate-700 {
+ --tw-text-opacity: 1;
+ color: rgb(51 65 85 / var(--tw-text-opacity, 1));
+}
+
+.opacity-0 {
+ opacity: 0;
+}
+
+.opacity-100 {
+ opacity: 1;
+}
+
+.shadow {
+ --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-lg {
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.outline-none {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.filter {
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-all {
+ transition-property: all;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-colors {
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.transition-opacity {
+ transition-property: opacity;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
+.duration-100 {
+ transition-duration: 100ms;
+}
+
+.duration-150 {
+ transition-duration: 150ms;
+}
+
+.duration-300 {
+ transition-duration: 300ms;
+}
+
+.ease-in-out {
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.content-\[\'\'\] {
+ --tw-content: '';
+ content: var(--tw-content);
+}
+
+.focus-within\:ring-2:focus-within {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus-within\:ring-blue-500:focus-within {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
+}
+
+.focus-within\:ring-offset-2:focus-within {
+ --tw-ring-offset-width: 2px;
+}
+
+.hover\:scale-\[1\.01\]:hover {
+ --tw-scale-x: 1.01;
+ --tw-scale-y: 1.01;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.hover\:bg-gray-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-gray-50:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:bg-white:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.hover\:text-slate-900:hover {
+ --tw-text-opacity: 1;
+ color: rgb(15 23 42 / var(--tw-text-opacity, 1));
+}
+
+.hover\:shadow-md:hover {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.focus\:border-blue-500:focus {
+ --tw-border-opacity: 1;
+ border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
+}
+
+.focus\:ring-1:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus\:ring-blue-500:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
+}
+
+.peer:checked ~ .peer-checked\:translate-x-\[32px\] {
+ --tw-translate-x: 32px;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.peer\/all:checked ~ .peer-checked\/all\:bg-green-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/grouped:checked ~ .peer-checked\/grouped\:bg-blue-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(191 219 254 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/list:checked ~ .peer-checked\/list\:bg-blue-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(191 219 254 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/lxc:checked ~ .peer-checked\/lxc\:bg-green-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/vm:checked ~ .peer-checked\/vm\:bg-green-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1));
+}
+
+.peer:checked ~ .peer-checked\:bg-blue-600 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/all:checked ~ .peer-checked\/all\:font-semibold {
+ font-weight: 600;
+}
+
+.peer\/grouped:checked ~ .peer-checked\/grouped\:font-semibold {
+ font-weight: 600;
+}
+
+.peer\/list:checked ~ .peer-checked\/list\:font-semibold {
+ font-weight: 600;
+}
+
+.peer\/lxc:checked ~ .peer-checked\/lxc\:font-semibold {
+ font-weight: 600;
+}
+
+.peer\/vm:checked ~ .peer-checked\/vm\:font-semibold {
+ font-weight: 600;
+}
+
+.peer\/all:checked ~ .peer-checked\/all\:text-green-800 {
+ --tw-text-opacity: 1;
+ color: rgb(22 101 52 / var(--tw-text-opacity, 1));
+}
+
+.peer\/grouped:checked ~ .peer-checked\/grouped\:text-blue-800 {
+ --tw-text-opacity: 1;
+ color: rgb(30 64 175 / var(--tw-text-opacity, 1));
+}
+
+.peer\/list:checked ~ .peer-checked\/list\:text-blue-800 {
+ --tw-text-opacity: 1;
+ color: rgb(30 64 175 / var(--tw-text-opacity, 1));
+}
+
+.peer\/lxc:checked ~ .peer-checked\/lxc\:text-green-800 {
+ --tw-text-opacity: 1;
+ color: rgb(22 101 52 / var(--tw-text-opacity, 1));
+}
+
+.peer\/vm:checked ~ .peer-checked\/vm\:text-green-800 {
+ --tw-text-opacity: 1;
+ color: rgb(22 101 52 / var(--tw-text-opacity, 1));
+}
+
+.has-\[\:checked\]\:bg-white:has(:checked) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
+}
+
+.has-\[\:checked\]\:text-slate-900:has(:checked) {
+ --tw-text-opacity: 1;
+ color: rgb(15 23 42 / var(--tw-text-opacity, 1));
+}
+
+.has-\[\:checked\]\:shadow-sm:has(:checked) {
+ --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.dark\:divide-gray-700:is(.dark *) > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-divide-opacity, 1));
+}
+
+.dark\:border-blue-700:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(29 78 216 / var(--tw-border-opacity, 1));
+}
+
+.dark\:border-gray-600:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(75 85 99 / var(--tw-border-opacity, 1));
+}
+
+.dark\:border-gray-700:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
+}
+
+.dark\:border-green-700:is(.dark *) {
+ --tw-border-opacity: 1;
+ border-color: rgb(21 128 61 / var(--tw-border-opacity, 1));
+}
+
+.dark\:bg-blue-900\/20:is(.dark *) {
+ background-color: rgb(30 58 138 / 0.2);
+}
+
+.dark\:bg-blue-900\/50:is(.dark *) {
+ background-color: rgb(30 58 138 / 0.5);
+}
+
+.dark\:bg-gray-600:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:bg-gray-700:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:bg-gray-700\/50:is(.dark *) {
+ background-color: rgb(55 65 81 / 0.5);
+}
+
+.dark\:bg-gray-700\/80:is(.dark *) {
+ background-color: rgb(55 65 81 / 0.8);
+}
+
+.dark\:bg-gray-800:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:bg-gray-900:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:bg-green-800\/30:is(.dark *) {
+ background-color: rgb(22 101 52 / 0.3);
+}
+
+.dark\:bg-green-900\/50:is(.dark *) {
+ background-color: rgb(20 83 45 / 0.5);
+}
+
+.dark\:bg-red-800\/30:is(.dark *) {
+ background-color: rgb(153 27 27 / 0.3);
+}
+
+.dark\:text-blue-300:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-gray-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(229 231 235 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-gray-300:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-gray-400:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-green-300:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(134 239 172 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-green-400:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(74 222 128 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-red-300:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(252 165 165 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-red-400:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(248 113 113 / var(--tw-text-opacity, 1));
+}
+
+.dark\:text-yellow-400:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(250 204 21 / var(--tw-text-opacity, 1));
+}
+
+.dark\:hover\:bg-gray-700:hover:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
+}
+
+.dark\:hover\:bg-gray-700\/50:hover:is(.dark *) {
+ background-color: rgb(55 65 81 / 0.5);
+}
+
+.peer\/all:checked ~ .dark\:peer-checked\/all\:bg-green-700\/60:is(.dark *) {
+ background-color: rgb(21 128 61 / 0.6);
+}
+
+.peer\/grouped:checked ~ .dark\:peer-checked\/grouped\:bg-blue-700\/60:is(.dark *) {
+ background-color: rgb(29 78 216 / 0.6);
+}
+
+.peer\/list:checked ~ .dark\:peer-checked\/list\:bg-blue-700\/60:is(.dark *) {
+ background-color: rgb(29 78 216 / 0.6);
+}
+
+.peer\/lxc:checked ~ .dark\:peer-checked\/lxc\:bg-green-700\/60:is(.dark *) {
+ background-color: rgb(21 128 61 / 0.6);
+}
+
+.peer\/vm:checked ~ .dark\:peer-checked\/vm\:bg-green-700\/60:is(.dark *) {
+ background-color: rgb(21 128 61 / 0.6);
+}
+
+.peer:checked ~ .dark\:peer-checked\:bg-blue-700:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
+}
+
+.peer\/all:checked ~ .dark\:peer-checked\/all\:text-green-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(187 247 208 / var(--tw-text-opacity, 1));
+}
+
+.peer\/grouped:checked ~ .dark\:peer-checked\/grouped\:text-blue-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(191 219 254 / var(--tw-text-opacity, 1));
+}
+
+.peer\/list:checked ~ .dark\:peer-checked\/list\:text-blue-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(191 219 254 / var(--tw-text-opacity, 1));
+}
+
+.peer\/lxc:checked ~ .dark\:peer-checked\/lxc\:text-green-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(187 247 208 / var(--tw-text-opacity, 1));
+}
+
+.peer\/vm:checked ~ .dark\:peer-checked\/vm\:text-green-200:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(187 247 208 / var(--tw-text-opacity, 1));
+}
+
+@media (min-width: 768px) {
+ .md\:flex-row {
+ flex-direction: row;
+ }
+
+ .md\:items-center {
+ align-items: center;
+ }
+}
\ No newline at end of file
diff --git a/reports/proxmox-api-test-2025-03-04T11-41-12.389Z.json b/reports/proxmox-api-test-2025-03-04T11-41-12.389Z.json
deleted file mode 100644
index 755cda0dd..000000000
--- a/reports/proxmox-api-test-2025-03-04T11-41-12.389Z.json
+++ /dev/null
@@ -1,379 +0,0 @@
-{
- "timestamp": "2025-03-04T11:41:12.387Z",
- "nodes": [
- {
- "nodeConfig": {
- "id": "node-1",
- "name": "Proxmox Node 1",
- "host": "https://10.0.0.1:8006",
- "tokenId": "root@pam!pulse",
- "tokenSecret": "f3bad8c9-98da-41e3-a55d-0cbeca1a16eb"
- },
- "discoveredNode": {
- "id": "node-1",
- "name": "Proxmox Node 1",
- "nodeName": "pve",
- "host": "https://10.0.0.1:8006",
- "ipAddress": "10.0.0.1"
- },
- "testResults": [
- {
- "endpoint": "/nodes/pve/status",
- "success": true,
- "responseTime": 25,
- "statusCode": 200,
- "data": {
- "data": {
- "memory": {
- "free": 9812631400,
- "total": 16539889664,
- "used": 4170511396
- },
- "kversion": "Linux 6.8.12-8-pve #1 SMP PREEMPT_DYNAMIC PMX 6.8.12-8 (2025-01-24T12:32Z)",
- "pveversion": "pve-manager/8.3.4/65224a0f9cd294a3",
- "wait": 0,
- "rootfs": {
- "free": 22391552234,
- "total": 33501757440,
- "avail": 24827311985,
- "used": 9752822652
- },
- "idle": 0,
- "cpu": 0.01,
- "ksm": {
- "shared": 0
- },
- "loadavg": [
- "0.11",
- "0.18",
- "0.24"
- ],
- "current-kernel": {
- "machine": "x86_64",
- "sysname": "Linux",
- "version": "#1 SMP PREEMPT_DYNAMIC PMX 6.8.12-8 (2025-01-24T12:32Z)",
- "release": "6.8.12-8-pve"
- },
- "swap": {
- "used": 2199479654,
- "total": 8589930496,
- "free": 5171566783
- },
- "cpuinfo": {
- "sockets": 1,
- "user_hz": 100,
- "cores": 4,
- "flags": "fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a rdseed adx smap clflushopt clwb intel_pt sha_ni xsaveopt xsavec xgetbv1 xsaves split_lock_detect user_shstk avx_vnni dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req vnmi umip pku ospke waitpkg gfni vaes vpclmulqdq rdpid movdiri movdir64b fsrm md_clear serialize arch_lbr ibt flush_l1d arch_capabilities",
- "hvm": "1",
- "model": "Intel(R) CPU",
- "mhz": "3200.000",
- "cpus": 4
- },
- "boot-info": {
- "secureboot": 0,
- "mode": "efi"
- },
- "uptime": 1214144
- }
- }
- },
- {
- "endpoint": "/nodes/pve/qemu",
- "success": true,
- "responseTime": 33,
- "statusCode": 200,
- "data": {
- "data": []
- }
- },
- {
- "endpoint": "/nodes/pve/lxc",
- "success": true,
- "responseTime": 301,
- "statusCode": 200,
- "data": {
- "data": [
- {
- "netout": 6360562,
- "netin": 41174089,
- "uptime": 1726080,
- "vmid": 832,
- "diskread": 492906670,
- "status": "running",
- "mem": 418713692,
- "type": "lxc",
- "cpu": 0.01,
- "maxmem": 1797457948,
- "name": "web-server",
- "tags": "alpine;community-script;docker",
- "diskwrite": 309916256,
- "maxswap": 536870912,
- "swap": 40866254,
- "cpus": 3,
- "maxdisk": 1783859375,
- "disk": 2065724962
- },
- {
- "netout": 5013282,
- "netin": 21614712,
- "uptime": 1686299,
- "vmid": 196,
- "diskread": 478841701,
- "status": "stopped",
- "mem": 429860162,
- "pid": 8755,
- "type": "lxc",
- "cpu": 0.01,
- "maxmem": 506671165,
- "name": "database",
- "tags": "alpine;community-script;docker",
- "diskwrite": 882889215,
- "maxswap": 536870912,
- "swap": 23493410,
- "cpus": 2,
- "maxdisk": 3669498313,
- "disk": 1803734283
- },
- {
- "netout": 5806006,
- "netin": 59432559,
- "uptime": 1674525,
- "vmid": 261,
- "diskread": 64188486,
- "status": "running",
- "mem": 457613610,
- "pid": 17136,
- "type": "lxc",
- "cpu": 0.01,
- "maxmem": 1862341628,
- "name": "cache",
- "tags": "alpine;community-script;docker",
- "diskwrite": 114888768,
- "maxswap": 536870912,
- "swap": 17704330,
- "cpus": 1,
- "maxdisk": 2884791794,
- "disk": 2405366900
- },
- {
- "netout": 2612736,
- "netin": 33041413,
- "uptime": 1412816,
- "vmid": 961,
- "diskread": 730973209,
- "status": "running",
- "mem": 166992786,
- "pid": 9200,
- "type": "lxc",
- "cpu": 0.02,
- "maxmem": 1497731900,
- "name": "monitoring",
- "tags": "alpine;community-script;docker",
- "diskwrite": 336129197,
- "maxswap": 536870912,
- "swap": 37805574,
- "cpus": 4,
- "maxdisk": 1805739601,
- "disk": 1654931381
- },
- {
- "netout": 9416694,
- "netin": 24288518,
- "uptime": 1501667,
- "vmid": 611,
- "diskread": 655348328,
- "status": "running",
- "mem": 66289447,
- "type": "lxc",
- "cpu": 0.01,
- "maxmem": 1501448904,
- "name": "proxy",
- "tags": "alpine;community-script;docker",
- "diskwrite": 499119824,
- "maxswap": 536870912,
- "swap": 11024429,
- "cpus": 1,
- "maxdisk": 4243330840,
- "disk": 786362766
- },
- {
- "netout": 8712054,
- "netin": 50336395,
- "uptime": 1220777,
- "vmid": 646,
- "diskread": 226539083,
- "status": "running",
- "mem": 358390912,
- "type": "lxc",
- "cpu": 0,
- "maxmem": 1854658065,
- "name": "backup",
- "tags": "alpine;community-script;docker",
- "diskwrite": 532009231,
- "maxswap": 536870912,
- "swap": 12877451,
- "cpus": 2,
- "maxdisk": 3000706145,
- "disk": 1867635211
- }
- ]
- }
- },
- {
- "endpoint": "/nodes/pve/storage",
- "success": true,
- "responseTime": 139,
- "statusCode": 200,
- "data": {
- "data": [
- {
- "total": 0,
- "content": "backup",
- "used": 0,
- "shared": 1,
- "type": "pbs",
- "storage": "backup-pbs",
- "avail": 0,
- "enabled": 1,
- "active": 0
- },
- {
- "total": 443984604736,
- "used_fraction": 0.29,
- "content": "images,rootdir",
- "shared": 0,
- "used": 131808261291,
- "type": "lvmthin",
- "avail": 338214365055,
- "storage": "local-lvm",
- "enabled": 1,
- "active": 1
- },
- {
- "type": "dir",
- "shared": 0,
- "content": "vztmpl,backup,iso",
- "used": 6471064379,
- "used_fraction": 0.21,
- "total": 33501757440,
- "active": 1,
- "enabled": 1,
- "avail": 20381082056,
- "storage": "local"
- }
- ]
- }
- },
- {
- "endpoint": "/nodes/pve/network",
- "success": true,
- "responseTime": 30,
- "statusCode": 200,
- "data": {
- "data": [
- {
- "method6": "manual",
- "families": [
- "inet"
- ],
- "iface": "wlan0",
- "method": "manual",
- "priority": 6,
- "type": "unknown"
- },
- {
- "type": "eth",
- "exists": 1,
- "families": [
- "inet"
- ],
- "priority": 4,
- "method": "manual",
- "iface": "eth1",
- "method6": "manual"
- },
- {
- "netmask": "24",
- "families": [
- "inet"
- ],
- "bridge_fd": "0",
- "cidr": "10.0.0.1/24",
- "type": "bridge",
- "method6": "manual",
- "active": 1,
- "address": "10.0.0.1",
- "iface": "vmbr0",
- "method": "static",
- "bridge_stp": "off",
- "bridge_ports": "eth0",
- "gateway": "10.0.0.254",
- "autostart": 1,
- "priority": 5
- },
- {
- "families": [
- "inet"
- ],
- "exists": 1,
- "type": "eth",
- "method6": "manual",
- "active": 1,
- "iface": "eth0",
- "method": "manual",
- "priority": 3
- }
- ]
- }
- },
- {
- "endpoint": "/nodes/pve/tasks",
- "success": true,
- "responseTime": 21,
- "statusCode": 200,
- "data": {
- "data": [],
- "total": 0
- }
- },
- {
- "endpoint": "/nodes/pve/subscription",
- "success": true,
- "responseTime": 23,
- "statusCode": 200,
- "data": {
- "data": {
- "status": "notfound",
- "message": "There is no subscription key",
- "serverid": "MOCK000000000000000000000000000000",
- "url": "https://www.proxmox.com/en/proxmox-virtual-environment/pricing"
- }
- }
- },
- {
- "endpoint": "/nodes/pve/version",
- "success": true,
- "responseTime": 44,
- "statusCode": 200,
- "data": {
- "data": {
- "version": "8.3.4",
- "release": "8.3",
- "repoid": "65224a0f9cd294a3"
- }
- }
- }
- ],
- "overallSuccess": true,
- "totalTests": 8,
- "successfulTests": 8,
- "failedTests": 0,
- "averageResponseTime": 77
- }
- ],
- "overallSuccess": true,
- "totalTests": 8,
- "successfulTests": 8,
- "failedTests": 0,
- "averageResponseTime": 77
-}
\ No newline at end of file
diff --git a/screenshot-config.json b/screenshot-config.json
deleted file mode 100644
index 4016484ab..000000000
--- a/screenshot-config.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "baseUrl": "http://localhost:7654",
- "outputDir": "../../docs/images",
- "mockData": {
- "enabled": true,
- "mockDataUrl": "http://localhost:7656",
- "setupScript": "window.localStorage.setItem('use_mock_data', 'true'); window.localStorage.setItem('MOCK_DATA_ENABLED', 'true'); localStorage.removeItem('mock_enabled'); localStorage.removeItem('MOCK_SERVER_URL'); localStorage.removeItem('MOCK_DATA'); console.log('Server-side mock data flags set in localStorage');"
- },
- "screenshots": [
- {
- "path": "/resources",
- "name": "dashboard",
- "viewportSize": {
- "width": 1440,
- "height": 900
- },
- "waitForSelector": "#root",
- "lightModeOnly": true,
- "waitForTimeout": 5000,
- "enableFilters": false
- },
- {
- "path": "/resources",
- "name": "dashboard-dark-compact",
- "viewportSize": {
- "width": 1440,
- "height": 900
- },
- "waitForSelector": "#root",
- "theme": "dark",
- "waitForTimeout": 5000,
- "enableFilters": false,
- "beforeScreenshot": "// Try multiple methods to enable compact mode\nlocalStorage.setItem('pulse_compact_mode', 'true');\nlocalStorage.setItem('compact_mode', 'true');\nlocalStorage.setItem('app_compact_mode', 'true');\n\n// Set dark mode\nlocalStorage.setItem('app_dark_mode', JSON.stringify(true));\n\n// Try to find and click compact mode toggle if it exists\nconst compactButtons = [\n ...document.querySelectorAll('button[title*=\"compact\" i], button[aria-label*=\"compact\" i]'),\n ...Array.from(document.querySelectorAll('button')).filter(btn => btn.textContent?.toLowerCase().includes('compact')),\n ...document.querySelectorAll('.compact-toggle, [data-testid=\"compact-toggle\"]')\n];\n\nif (compactButtons.length > 0) {\n console.log('Found compact mode button, clicking it');\n compactButtons[0].click();\n}\n\n// Set compact mode attributes on document\ndocument.documentElement.setAttribute('data-compact-mode', 'true');\ndocument.body.classList.add('compact-mode');\n\n// Force any React components to update if possible\nif (window.dispatchEvent) {\n window.dispatchEvent(new Event('storage'));\n window.dispatchEvent(new Event('resize'));\n}"
- }
- ]
-}
\ No newline at end of file
diff --git a/scripts/README-dev-tools.md b/scripts/README-dev-tools.md
deleted file mode 100644
index d04885980..000000000
--- a/scripts/README-dev-tools.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# Pulse Development Tools
-
-**⚠️ DEVELOPMENT USE ONLY ⚠️**
-
-These tools are for development and testing purposes only. They are not part of the main Pulse application and should not be used in production environments.
-
-## Mock Data Generator
-
-This directory contains a tool for generating simulated data for the Pulse application. This is useful for development, testing, and creating documentation without needing a real Proxmox environment.
-
-### Available Tools
-
-1. **generate-mock-data.js** - Generates simulated data for the Pulse application
-2. **debug-socket.js** - Debug proxy for troubleshooting socket communication
-3. **debug-proxy.sh** - Runs the application with the debug proxy
-
-### Quick Start
-
-To use the mock data generator:
-
-```bash
-# Start the mock data server
-node scripts/generate-mock-data.js
-
-# In another terminal, start the frontend
-cd frontend
-VITE_API_URL=http://localhost:7655 npm run dev
-```
-
-The mock data server will run on port 7655 and provide simulated data to the frontend.
-
-### Simulated Data
-
-The generator creates:
-
-- A single Proxmox node with realistic specifications
-- 10 virtual machines with various configurations
-- 10 containers with various configurations
-- Realistic resource usage metrics that update every 2 seconds
-
-The VMs and containers have:
-- Different operating systems (Ubuntu, Debian, CentOS, Windows, etc.)
-- Varying CPU, memory, and disk configurations
-- Realistic network throughput
-- A mix of running and stopped states
-
-### Customizing the Data
-
-You can modify the `generate-mock-data.js` script to change:
-
-- The node specifications
-- The number and types of VMs and containers
-- The resource usage patterns
-- The update frequency
-
-### Troubleshooting
-
-If you encounter issues with the socket communication, you can use the debug proxy:
-
-```bash
-./scripts/debug-proxy.sh
-```
-
-This will run the application with a debug proxy that logs all socket messages to `socket-debug.log`.
\ No newline at end of file
diff --git a/scripts/README.md b/scripts/README.md
deleted file mode 100644
index 72937f67c..000000000
--- a/scripts/README.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Pulse Scripts
-
-This directory contains various scripts used for development, testing, and maintenance of the Pulse application.
-
-## Development Tools
-
-These scripts are for development and testing purposes only. They are not part of the main Pulse application and should not be used in production environments.
-
-## Available Scripts
-
-### Core Scripts
-
-- **start.js** - Main launcher script that provides a menu to select which environment to start
-- **install.sh** - Interactive installation and setup script
-- **check-config.js** - Validates the configuration and checks for common issues
-- **monitor-logs.js** - Real-time log monitoring with filtering capabilities
-- **run-with-logs.js** - Runs the application and monitors logs in the same terminal
-
-### Development Scripts
-
-- **start-dev.sh/bat** - Start the application in development mode with mock data
-- **start-mock-dev.sh/bat** - Start the application in development mode with mock data (alias for start-dev.sh)
-- **start-mock-server.js** - Starts only the mock data server
-- **debug-socket.js** - Debug proxy for troubleshooting socket communication
-- **debug-proxy.sh** - Runs the application with the debug proxy
-
-### Production Scripts
-
-- **start-prod.sh/bat** - Start the application in production mode
-- **docker-prod.sh** - Start the application in production mode with Docker
-
-### Utility Scripts
-
-- **check-connections.js** - Tests various connection methods to help diagnose issues
-- **clear-data.js** - Clears cached data and resets the application state
-- **configure-env.js** - Interactive script to configure the .env file
-- **verify-cluster-config.js** - Verifies the cluster configuration
-
-## Which Script Should I Use?
-
-Instead of calling these scripts directly, you can use the npm scripts in the root directory:
-
-```bash
-# Development mode with mock data (local)
-npm run dev
-
-# Development mode with mock data (Docker)
-npm run dev:docker
-
-# Production mode with real Proxmox data (local)
-npm run prod
-
-# Production mode with real Proxmox data (Docker)
-npm run prod:docker
-
-# Monitor logs
-npm run logs
-
-# Check container status
-npm run status
-
-# Restart the application
-npm run restart
-
-# Stop the application
-npm run stop
-
-# Clean up (remove containers, images, volumes)
-npm run cleanup
-```
-
-## Using the Launcher
-
-You can also use the launcher in the root directory:
-
-```bash
-# On Unix/Linux/macOS
-./start.sh
-
-# On Windows
-start.bat
-```
-
-The launcher provides a menu to select which environment to start, making it easier for users to choose the right option.
\ No newline at end of file
diff --git a/scripts/check-config.js b/scripts/check-config.js
deleted file mode 100755
index 4810e7c82..000000000
--- a/scripts/check-config.js
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Check Configuration Script
- *
- * This script checks if the Proxmox configuration is valid before running in production mode.
- * If the configuration is missing or invalid, it provides guidance to the user.
- */
-
-const fs = require('fs');
-const path = require('path');
-const readline = require('readline');
-const dotenv = require('dotenv');
-
-// Load environment variables
-dotenv.config();
-
-// Path to the .env file
-const envFilePath = path.join(process.cwd(), '.env');
-
-// Check if .env file exists
-if (!fs.existsSync(envFilePath)) {
- console.error('\x1b[31mError: .env file not found.\x1b[0m');
- console.log('Please create a .env file with your Proxmox configuration.');
- process.exit(1);
-}
-
-// Check for nodes using the original format (PROXMOX_NODE_X_NAME, etc.)
-let hasValidConfig = false;
-for (let i = 1; i <= 10; i++) {
- const hostKey = `PROXMOX_NODE_${i}_HOST`;
- const nodeNameKey = `PROXMOX_NODE_${i}_NAME`;
- const tokenIdKey = `PROXMOX_NODE_${i}_TOKEN_ID`;
- const tokenSecretKey = `PROXMOX_NODE_${i}_TOKEN_SECRET`;
-
- const hasNodeConfig = [hostKey, nodeNameKey, tokenIdKey, tokenSecretKey].every(key =>
- process.env[key] && process.env[key] !== 'your-token-secret-here' && process.env[key] !== 'your-token-secret'
- );
-
- if (hasNodeConfig) {
- hasValidConfig = true;
- break;
- }
-}
-
-// If mock data is enabled, we don't need Proxmox configuration
-const mockDataEnabled = process.env.USE_MOCK_DATA === 'true' || process.env.MOCK_DATA_ENABLED === 'true';
-
-if (!hasValidConfig && !mockDataEnabled) {
- console.error('\x1b[31mError: Missing or invalid Proxmox configuration.\x1b[0m');
- console.log('You need to configure at least one Proxmox node:');
- console.log('\nNode configuration format:');
- console.log(' - PROXMOX_NODE_1_NAME (e.g. "pve")');
- console.log(' - PROXMOX_NODE_1_HOST (e.g. "https://proxmox.local:8006")');
- console.log(' - PROXMOX_NODE_1_TOKEN_ID (e.g. "root@pam!pulse")');
- console.log(' - PROXMOX_NODE_1_TOKEN_SECRET (your API token)');
-
- console.log('\nYou have two options:');
- console.log('1. Update your .env file with valid Proxmox details');
- console.log('2. Use development mode with mock data instead');
-
- const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout
- });
-
- rl.question('\nWould you like to continue with mock data instead? (y/n): ', (answer) => {
- rl.close();
-
- if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
- console.log('\n\x1b[33mSwitching to development mode with mock data...\x1b[0m');
-
- // Update the current .env file with development settings
- let envContent = fs.readFileSync(envFilePath, 'utf8');
- envContent = envContent.replace(/NODE_ENV=.*/g, 'NODE_ENV=development');
- envContent = envContent.replace(/USE_MOCK_DATA=.*/g, 'USE_MOCK_DATA=true');
- envContent = envContent.replace(/MOCK_DATA_ENABLED=.*/g, 'MOCK_DATA_ENABLED=true');
- fs.writeFileSync(envFilePath, envContent);
- console.log('\x1b[32mUpdated .env file with development settings and mock data enabled.\x1b[0m');
-
- console.log('\x1b[33mPlease run "npm run dev" to start in development mode.\x1b[0m');
- process.exit(0);
- } else {
- console.log('\nPlease update your .env file with valid Proxmox configuration and try again.');
- process.exit(1);
- }
- });
-} else {
- // Configuration is valid
- if (hasValidConfig) {
- console.log('\x1b[32mProxmox configuration is valid. Continuing with production mode.\x1b[0m');
- } else if (mockDataEnabled) {
- console.log('\x1b[32mMock data is enabled. No Proxmox configuration needed. Continuing with production mode.\x1b[0m');
- }
- process.exit(0);
-}
\ No newline at end of file
diff --git a/scripts/check-connections.js b/scripts/check-connections.js
deleted file mode 100644
index a53dfeaf6..000000000
--- a/scripts/check-connections.js
+++ /dev/null
@@ -1,236 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Script to check connections between frontend, backend, and mock server
- * Usage: node scripts/check-connections.js
- */
-
-const http = require('http');
-const net = require('net');
-const fs = require('fs');
-const path = require('path');
-const dotenv = require('dotenv');
-
-// Load environment variables
-dotenv.config();
-
-console.log('Pulse Connection Checker');
-console.log('=======================');
-console.log(`Environment: ${process.env.NODE_ENV || 'Not set'}`);
-console.log(`USE_MOCK_DATA: ${process.env.USE_MOCK_DATA || 'Not set'}`);
-console.log(`MOCK_DATA_ENABLED: ${process.env.MOCK_DATA_ENABLED || 'Not set'}`);
-
-// Check if a port is in use
-function checkPort(port) {
- return new Promise((resolve) => {
- const server = net.createServer();
-
- server.once('error', (err) => {
- if (err.code === 'EADDRINUSE') {
- resolve(true); // Port is in use
- } else {
- resolve(false);
- }
- });
-
- server.once('listening', () => {
- server.close();
- resolve(false); // Port is not in use
- });
-
- server.listen(port);
- });
-}
-
-// Check if a server is responding on a given port
-function checkServer(host, port, path = '/') {
- return new Promise((resolve) => {
- const req = http.request({
- host,
- port,
- path,
- method: 'GET',
- timeout: 3000
- }, (res) => {
- let data = '';
- res.on('data', (chunk) => {
- data += chunk;
- });
- res.on('end', () => {
- resolve({
- status: res.statusCode,
- data: data.substring(0, 100) // Just get the first 100 chars
- });
- });
- });
-
- req.on('error', (err) => {
- resolve({
- status: 'error',
- error: err.message
- });
- });
-
- req.on('timeout', () => {
- req.destroy();
- resolve({
- status: 'timeout',
- error: 'Request timed out'
- });
- });
-
- req.end();
- });
-}
-
-// Check frontend socket.js file
-function checkFrontendSocketConfig() {
- const socketJsPath = path.join(process.cwd(), 'frontend', 'src', 'hooks', 'useSocket.js');
-
- if (!fs.existsSync(socketJsPath)) {
- return {
- exists: false,
- error: 'useSocket.js file not found'
- };
- }
-
- const content = fs.readFileSync(socketJsPath, 'utf8');
-
- // Check if we're using the dynamic port selection
- if (content.includes('useMockData ?')) {
- // We're using dynamic port selection
- const isDevelopment = process.env.NODE_ENV === 'development';
- const useMockData = process.env.USE_MOCK_DATA === 'true' || process.env.MOCK_DATA_ENABLED === 'true';
-
- if (isDevelopment) {
- const port = useMockData ? '7656' : '7654';
- return {
- exists: true,
- port,
- isDynamic: true,
- useMockData
- };
- } else {
- // In production, we use window.location.origin
- return {
- exists: true,
- port: '7654', // Production always uses 7654
- isDynamic: true,
- useMockData: false
- };
- }
- } else {
- // Extract the port from the socketUrl (old method)
- const portMatch = content.match(/socketUrl\s*=\s*`http:\/\/\${currentHost}:(\d+)`/);
- const port = portMatch ? portMatch[1] : null;
-
- return {
- exists: true,
- port,
- isDynamic: false
- };
- }
-}
-
-async function main() {
- // Check if backend server is running (port 7654)
- const backendRunning = await checkPort(7654);
- console.log(`\nBackend server (port 7654): ${backendRunning ? 'RUNNING' : 'NOT RUNNING'}`);
-
- if (backendRunning) {
- const backendResponse = await checkServer('localhost', 7654);
- console.log(` Response: ${typeof backendResponse.status === 'number' ? backendResponse.status : backendResponse.status}`);
- if (backendResponse.error) {
- console.log(` Error: ${backendResponse.error}`);
- }
- }
-
- // Check if mock server is running (port 7656)
- const mockRunning = await checkPort(7656);
- console.log(`\nMock server (port 7656): ${mockRunning ? 'RUNNING' : 'NOT RUNNING'}`);
-
- if (mockRunning) {
- const mockResponse = await checkServer('localhost', 7656);
- console.log(` Response: ${typeof mockResponse.status === 'number' ? mockResponse.status : mockResponse.status}`);
- if (mockResponse.error) {
- console.log(` Error: ${mockResponse.error}`);
- }
- }
-
- // Check frontend socket configuration
- const socketConfig = checkFrontendSocketConfig();
- console.log('\nFrontend Socket Configuration:');
- if (socketConfig.exists) {
- if (socketConfig.isDynamic) {
- console.log(' Dynamic port selection: ENABLED');
- console.log(` Current environment: ${process.env.NODE_ENV || 'Not set'}`);
- console.log(` Mock data enabled: ${socketConfig.useMockData ? 'Yes' : 'No'}`);
- console.log(` Will connect to port: ${socketConfig.port}`);
-
- if (socketConfig.port === '7654') {
- console.log(' Frontend is configured to connect to the BACKEND server');
- } else if (socketConfig.port === '7656') {
- console.log(' Frontend is configured to connect to the MOCK server');
- }
- } else {
- console.log(` Socket port: ${socketConfig.port || 'Not found'}`);
- if (socketConfig.port === '7654') {
- console.log(' Frontend is configured to connect to the BACKEND server');
- } else if (socketConfig.port === '7656') {
- console.log(' Frontend is configured to connect to the MOCK server');
- } else {
- console.log(` Frontend is configured to connect to an UNKNOWN port: ${socketConfig.port}`);
- }
- }
- } else {
- console.log(` Error: ${socketConfig.error}`);
- }
-
- // Check if frontend dev server is running
- const frontendRunning = await checkPort(5173);
- console.log(`\nFrontend dev server (port 5173): ${!frontendRunning ? 'RUNNING' : 'NOT RUNNING'}`);
-
- // Summary and recommendations
- console.log('\nSummary:');
-
- if (process.env.NODE_ENV === 'production') {
- console.log('- Running in PRODUCTION mode');
-
- if (process.env.USE_MOCK_DATA === 'true' || process.env.MOCK_DATA_ENABLED === 'true') {
- console.log(' WARNING: Mock data is enabled in production mode!');
- }
-
- if (socketConfig.port === '7656') {
- console.log(' WARNING: Frontend is configured to connect to the mock server (port 7656) but you are in production mode!');
- console.log(' SOLUTION: Edit frontend/src/hooks/useSocket.js to use port 7654 instead of 7656');
- }
- } else {
- console.log('- Running in DEVELOPMENT mode');
-
- if (process.env.USE_MOCK_DATA !== 'true' && process.env.MOCK_DATA_ENABLED !== 'true') {
- console.log(' NOTE: Mock data is disabled in development mode');
- }
-
- if (socketConfig.port === '7654' && (process.env.USE_MOCK_DATA === 'true' || process.env.MOCK_DATA_ENABLED === 'true')) {
- console.log(' WARNING: Frontend is configured to connect to the backend server (port 7654) but mock data is enabled!');
- console.log(' SOLUTION: Edit frontend/src/hooks/useSocket.js to use port 7656 instead of 7654');
- }
- }
-
- if (backendRunning && mockRunning && socketConfig.port === '7656' && process.env.NODE_ENV === 'production') {
- console.log('\nPROBLEM DETECTED: You are running in production mode but the frontend is connecting to the mock server!');
- console.log('SOLUTION: Kill all servers, edit frontend/src/hooks/useSocket.js to use port 7654, and restart with npm run prod');
- }
-
- if (!backendRunning && process.env.NODE_ENV === 'production') {
- console.log('\nPROBLEM DETECTED: Production backend server is not running!');
- console.log('SOLUTION: Start the production server with npm run prod');
- }
-
- if (!mockRunning && (process.env.USE_MOCK_DATA === 'true' || process.env.MOCK_DATA_ENABLED === 'true')) {
- console.log('\nPROBLEM DETECTED: Mock data is enabled but the mock server is not running!');
- console.log('SOLUTION: Start the mock server with npm run mock');
- }
-}
-
-main().catch(console.error);
\ No newline at end of file
diff --git a/scripts/clear-data.js b/scripts/clear-data.js
deleted file mode 100644
index 8a9f8548e..000000000
--- a/scripts/clear-data.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * Script to clear persisted data files when switching between environments
- * This script is called by the start-dev.sh and start-prod.sh scripts
- */
-
-const fs = require('fs');
-const path = require('path');
-
-// Directories to clear
-const dataDirs = [
- 'data',
- 'logs',
- 'tmp'
-];
-
-// Function to clear a directory
-function clearDirectory(dirPath) {
- if (!fs.existsSync(dirPath)) {
- console.log(`Directory ${dirPath} does not exist, creating it...`);
- fs.mkdirSync(dirPath, { recursive: true });
- return;
- }
-
- try {
- const files = fs.readdirSync(dirPath);
-
- for (const file of files) {
- const filePath = path.join(dirPath, file);
- const stat = fs.statSync(filePath);
-
- if (stat.isDirectory()) {
- // Skip .git and node_modules directories
- if (file !== '.git' && file !== 'node_modules') {
- clearDirectory(filePath);
- }
- } else {
- // Delete the file
- fs.unlinkSync(filePath);
- console.log(`Deleted file: ${filePath}`);
- }
- }
-
- console.log(`Cleared directory: ${dirPath}`);
- } catch (error) {
- console.error(`Error clearing directory ${dirPath}:`, error.message);
- }
-}
-
-// Main function
-function clearData() {
- console.log('Clearing persisted data files...');
-
- // Get the project root directory
- const rootDir = path.resolve(__dirname, '..');
-
- // Clear each directory
- for (const dir of dataDirs) {
- const dirPath = path.join(rootDir, dir);
- clearDirectory(dirPath);
- }
-
- console.log('Data clearing complete.');
-}
-
-// Run the script if called directly
-if (require.main === module) {
- clearData();
-}
-
-// Export for use in other scripts
-module.exports = clearData;
\ No newline at end of file
diff --git a/scripts/configure-env.js b/scripts/configure-env.js
deleted file mode 100755
index 9edbfec58..000000000
--- a/scripts/configure-env.js
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Script to configure environment variables in .env file
- * Usage: node scripts/configure-env.js [prod|dev]
- */
-
-const fs = require('fs');
-const path = require('path');
-const dotenv = require('dotenv');
-
-// Get the environment from command line arguments
-const args = process.argv.slice(2);
-const env = args[0] || 'dev'; // Default to dev if no argument provided
-
-// Path to the .env file
-const envFilePath = path.resolve(process.cwd(), '.env');
-
-// Check if .env file exists
-if (!fs.existsSync(envFilePath)) {
- console.error('Error: .env file not found. Please create one by copying .env.example');
- process.exit(1);
-}
-
-// Load current .env file
-const currentEnv = dotenv.parse(fs.readFileSync(envFilePath));
-
-// Define environment-specific values
-const envConfigs = {
- prod: {
- NODE_ENV: 'production',
- LOG_LEVEL: 'info',
- USE_MOCK_DATA: 'false',
- MOCK_DATA_ENABLED: 'false',
- PROXMOX_AUTO_DETECT_CLUSTER: 'true',
- DOCKERFILE: 'docker/Dockerfile'
- },
- 'dev-real': {
- NODE_ENV: 'development',
- LOG_LEVEL: 'info',
- USE_MOCK_DATA: 'false',
- MOCK_DATA_ENABLED: 'false',
- PROXMOX_AUTO_DETECT_CLUSTER: 'true',
- DOCKERFILE: 'docker/Dockerfile.dev'
- },
- dev: {
- NODE_ENV: 'development',
- LOG_LEVEL: 'info',
- USE_MOCK_DATA: 'true',
- MOCK_DATA_ENABLED: 'true',
- PROXMOX_AUTO_DETECT_CLUSTER: 'true',
- DOCKERFILE: 'docker/Dockerfile.dev'
- }
-};
-
-// Get the config for the specified environment
-const config = envConfigs[env];
-if (!config) {
- console.error(`Error: Unknown environment "${env}". Use "prod", "dev-real", or "dev".`);
- process.exit(1);
-}
-
-// Update the .env file
-let envContent = fs.readFileSync(envFilePath, 'utf8');
-
-// Update each environment variable
-Object.entries(config).forEach(([key, value]) => {
- // Check if the key exists in the .env file
- const regex = new RegExp(`^${key}=.*$`, 'm');
- if (regex.test(envContent)) {
- // Replace the existing value
- envContent = envContent.replace(regex, `${key}=${value}`);
- console.log(`Updated ${key}=${value}`);
- } else {
- // Add the key if it doesn't exist
- envContent += `\n${key}=${value}`;
- console.log(`Added ${key}=${value}`);
- }
-});
-
-// Write the updated content back to the .env file
-fs.writeFileSync(envFilePath, envContent);
-
-console.log(`\nEnvironment configured for ${env === 'prod' ? 'production' : env === 'dev-real' ? 'development (real)' : 'development'}`);
\ No newline at end of file
diff --git a/scripts/dev-cleanup.js b/scripts/dev-cleanup.js
deleted file mode 100644
index 171308bd3..000000000
--- a/scripts/dev-cleanup.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * Cross-platform script to clean up development processes
- * This script stops any running Docker containers with "pulse" in their name
- * and kills any processes using the development ports
- */
-
-const { execSync } = require('child_process');
-const os = require('os');
-
-console.log('Cleaning up development environment...');
-
-// Stop Docker containers
-try {
- console.log('Stopping any running Pulse Docker containers...');
- if (os.platform() === 'win32') {
- // Windows command
- execSync('for /f "tokens=*" %i in (\'docker ps -q --filter "name=pulse"\') do docker stop %i', { stdio: 'inherit' });
- } else {
- // Unix command
- execSync('docker ps -q --filter "name=pulse" | xargs -r docker stop', { stdio: 'inherit' });
- }
-} catch (error) {
- console.log('No Docker containers to stop or Docker is not installed.');
-}
-
-// Kill processes using the development ports
-try {
- console.log('Killing processes using development ports...');
-
- // Backend ports
- try {
- execSync('npx kill-port 7654 7656', { stdio: 'inherit' });
- } catch (error) {
- console.log('No processes using backend ports.');
- }
-
- // Frontend ports
- try {
- execSync('npx kill-port 7654 9513', { stdio: 'inherit' });
- } catch (error) {
- console.log('No processes using frontend ports.');
- }
-} catch (error) {
- console.log('Error killing port processes:', error.message);
-}
-
-console.log('Cleanup complete. Ready to start development environment.');
\ No newline at end of file
diff --git a/scripts/docker-prod.sh b/scripts/docker-prod.sh
deleted file mode 100755
index 750ba2f47..000000000
--- a/scripts/docker-prod.sh
+++ /dev/null
@@ -1,107 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-# Default values
-BUILD=false
-RUN=false
-DETACHED=false
-CLEANUP=false
-HELP=false
-
-# Parse command line arguments
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --build)
- BUILD=true
- shift
- ;;
- --run)
- RUN=true
- shift
- ;;
- --detached)
- DETACHED=true
- shift
- ;;
- --cleanup)
- CLEANUP=true
- shift
- ;;
- --help)
- HELP=true
- shift
- ;;
- *)
- echo "Unknown option: $1"
- HELP=true
- shift
- ;;
- esac
-done
-
-# Display help
-if [ "$HELP" = true ]; then
- echo "Usage: $0 [options]"
- echo "Options:"
- echo " --build Build the Docker image"
- echo " --run Run the Docker container"
- echo " --detached Run in detached mode (background)"
- echo " --cleanup Remove existing containers and images"
- echo " --help Display this help message"
- exit 0
-fi
-
-# Ensure we're in the project root directory
-if [ ! -f "docker-compose.yml" ]; then
- echo "Error: docker-compose.yml not found. Please run this script from the project root directory."
- exit 1
-fi
-
-# Ensure .env file exists
-if [ ! -f ".env" ]; then
- if [ -f ".env.example" ]; then
- echo "Creating .env file from .env.example..."
- cp .env.example .env
- else
- echo "Error: .env file not found and .env.example is missing."
- exit 1
- fi
-fi
-
-# Cleanup if requested
-if [ "$CLEANUP" = true ]; then
- echo "Cleaning up Docker resources..."
- docker compose down --rmi all --volumes --remove-orphans
- exit 0
-fi
-
-# Build if requested
-if [ "$BUILD" = true ]; then
- echo "Building Docker image..."
- docker compose build
-fi
-
-# Run if requested
-if [ "$RUN" = true ]; then
- # Stop any existing containers
- echo "Stopping any existing containers..."
- docker compose down
-
- # Run the container
- if [ "$DETACHED" = true ]; then
- echo "Starting container in detached mode..."
- docker compose up -d
- else
- echo "Starting container..."
- docker compose up
- fi
-fi
-
-# If no action specified, show help
-if [ "$BUILD" = false ] && [ "$RUN" = false ] && [ "$CLEANUP" = false ]; then
- echo "No action specified. Use --build, --run, or --cleanup."
- echo "Run with --help for more information."
- exit 1
-fi
\ No newline at end of file
diff --git a/scripts/generate-logo-pngs.js b/scripts/generate-logo-pngs.js
deleted file mode 100644
index 9cfd1f8a9..000000000
--- a/scripts/generate-logo-pngs.js
+++ /dev/null
@@ -1,45 +0,0 @@
-const { createCanvas } = require('canvas');
-const fs = require('fs');
-const path = require('path');
-
-// Create output directory if it doesn't exist
-const outputDir = path.join(__dirname, '..', 'public', 'images');
-if (!fs.existsSync(outputDir)) {
- fs.mkdirSync(outputDir, { recursive: true });
-}
-
-// Generate different sizes of the logo
-const sizes = [16, 32, 64, 128, 256, 512];
-
-sizes.forEach(size => {
- const canvas = createCanvas(size, size);
- const ctx = canvas.getContext('2d');
-
- // Set background
- ctx.fillStyle = '#1a1a1a';
- ctx.fillRect(0, 0, size, size);
-
- // Draw pulse wave
- ctx.strokeStyle = '#00ff00';
- ctx.lineWidth = size * 0.1;
- ctx.beginPath();
-
- const amplitude = size * 0.3;
- const frequency = 2 * Math.PI / size;
-
- for (let x = 0; x < size; x++) {
- const y = size/2 + amplitude * Math.sin(frequency * x);
- if (x === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
- }
-
- ctx.stroke();
-
- // Save the image
- const buffer = canvas.toBuffer('image/png');
- fs.writeFileSync(path.join(outputDir, `logo-${size}.png`), buffer);
- console.log(`Generated logo-${size}.png`);
-});
\ No newline at end of file
diff --git a/scripts/generate-logo-with-text.js b/scripts/generate-logo-with-text.js
deleted file mode 100644
index 0df165829..000000000
--- a/scripts/generate-logo-with-text.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const { createCanvas, registerFont } = require('canvas');
-const fs = require('fs');
-const path = require('path');
-
-// Register the font
-const fontPath = path.join(__dirname, '..', 'assets', 'fonts', 'Roboto-Bold.ttf');
-registerFont(fontPath, { family: 'Roboto' });
-
-// Create output directory if it doesn't exist
-const outputDir = path.join(__dirname, '..', 'public', 'images');
-if (!fs.existsSync(outputDir)) {
- fs.mkdirSync(outputDir, { recursive: true });
-}
-
-// Create canvas
-const width = 800;
-const height = 200;
-const canvas = createCanvas(width, height);
-const ctx = canvas.getContext('2d');
-
-// Set background
-ctx.fillStyle = '#1a1a1a';
-ctx.fillRect(0, 0, width, height);
-
-// Draw pulse wave
-ctx.strokeStyle = '#00ff00';
-ctx.lineWidth = 10;
-ctx.beginPath();
-
-const amplitude = 40;
-const frequency = 2 * Math.PI / width;
-
-for (let x = 0; x < width; x++) {
- const y = height/2 + amplitude * Math.sin(frequency * x);
- if (x === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
-}
-
-ctx.stroke();
-
-// Add text
-ctx.fillStyle = '#ffffff';
-ctx.font = 'bold 48px Roboto';
-ctx.textAlign = 'center';
-ctx.textBaseline = 'middle';
-ctx.fillText('Pulse', width/2, height/2);
-
-// Save the image
-const buffer = canvas.toBuffer('image/png');
-fs.writeFileSync(path.join(outputDir, 'logo-with-text.png'), buffer);
-console.log('Generated logo-with-text.png');
\ No newline at end of file
diff --git a/scripts/generate-logos.sh b/scripts/generate-logos.sh
deleted file mode 100644
index 1d3a2b402..000000000
--- a/scripts/generate-logos.sh
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-echo "Generating logos..."
-
-# Install canvas if not already installed
-if ! npm list canvas >/dev/null 2>&1; then
- echo "Installing canvas..."
- npm install canvas
-fi
-
-# Generate logo PNGs
-echo "Generating logo PNGs..."
-node scripts/generate-logo-pngs.js
-
-# Generate logo with text
-echo "Generating logo with text..."
-node scripts/generate-logo-with-text.js
-
-echo "Logo generation complete!"
\ No newline at end of file
diff --git a/scripts/install.sh b/scripts/install.sh
deleted file mode 100755
index daf7b5035..000000000
--- a/scripts/install.sh
+++ /dev/null
@@ -1,177 +0,0 @@
-#!/bin/bash
-
-# Colors for output
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-RED='\033[0;31m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-# Function to check if a command exists
-command_exists() {
- command -v "$1" >/dev/null 2>&1
-}
-
-# Function to print a section header
-print_header() {
- echo -e "\n${BLUE}==== $1 ====${NC}\n"
-}
-
-# Function to print a success message
-print_success() {
- echo -e "${GREEN}✓ $1${NC}"
-}
-
-# Function to print a warning message
-print_warning() {
- echo -e "${YELLOW}⚠ $1${NC}"
-}
-
-# Function to print an error message
-print_error() {
- echo -e "${RED}✗ $1${NC}"
-}
-
-# Function to print a step
-print_step() {
- echo -e "${BLUE}$1${NC}"
-}
-
-# Welcome message
-clear
-echo -e "${BLUE}"
-echo " _____ _ _____ _ _ _ "
-echo " | __ \ | | |_ _| | | | | | "
-echo " | |__) | | |___ ___ | | _ __ ___| |_ __ _| | | ___ _ __ "
-echo " | ___/ | | / __|/ _ \ | | | '_ \/ __| __/ _\` | | |/ _ \ '__|"
-echo " | | | |_| \__ \ __/ _| |_| | | \__ \ || (_| | | | __/ | "
-echo " |_| \__,_|___/\___| |_____|_| |_|___/\__\__,_|_|_|\___|_| "
-echo -e "${NC}"
-echo -e "Welcome to the Pulse installation script!\n"
-
-# Ask for installation type
-print_header "Installation Type"
-echo "Please select the type of installation:"
-echo "1) Production - connect to a real Proxmox server"
-echo "2) Development - use with mock data (no Proxmox server needed)"
-read -p "Enter your choice (1/2) [1]: " install_type
-install_type=${install_type:-1}
-
-# Set environment variables based on installation type
-if [ "$install_type" = "1" ]; then
- # Production with real Proxmox
- NODE_ENV="production"
- USE_MOCK_DATA="false"
- MOCK_DATA_ENABLED="false"
- echo "Set up for production with real Proxmox server"
-elif [ "$install_type" = "2" ]; then
- # Development with mock data
- NODE_ENV="development"
- USE_MOCK_DATA="true"
- MOCK_DATA_ENABLED="true"
- echo "Set up for development with mock data"
-else
- print_error "Invalid choice. Exiting."
- exit 1
-fi
-
-# Check for Docker
-print_header "Checking System Requirements"
-if command_exists docker; then
- print_success "Docker is installed ($(docker --version))"
-else
- print_error "Docker is not installed. Please install Docker before continuing."
- echo "Visit https://docs.docker.com/get-docker/ for installation instructions."
- exit 1
-fi
-
-# Check for Docker Compose
-if command_exists docker-compose; then
- print_success "Docker Compose is installed ($(docker-compose --version))"
-elif docker compose version >/dev/null 2>&1; then
- print_success "Docker Compose plugin is installed ($(docker compose version))"
-else
- print_warning "Docker Compose is not installed. It's recommended for easier management."
- echo "Visit https://docs.docker.com/compose/install/ for installation instructions."
-fi
-
-# Create .env file if it doesn't exist
-print_header "Environment Configuration"
-if [ -f ".env" ]; then
- print_warning "An existing .env file was found."
- read -p "Do you want to create a new one? This will overwrite the existing file. (y/n) [n]: " create_new_env
- create_new_env=${create_new_env:-n}
-else
- create_new_env="y"
-fi
-
-if [ "$create_new_env" = "y" ] || [ "$create_new_env" = "Y" ]; then
- echo "Creating new .env file..."
-
- # Copy the example file
- cp .env.example .env
-
- # Update the .env file with the installation type
- sed -i.bak "s/NODE_ENV=.*/NODE_ENV=$NODE_ENV/" .env
- sed -i.bak "s/USE_MOCK_DATA=.*/USE_MOCK_DATA=$USE_MOCK_DATA/" .env
- sed -i.bak "s/MOCK_DATA_ENABLED=.*/MOCK_DATA_ENABLED=$MOCK_DATA_ENABLED/" .env
- rm -f .env.bak
-
- print_success "Created new .env file with $NODE_ENV configuration"
-
- # If using real Proxmox, ask for server details
- if [ "$install_type" = "1" ]; then
- print_step "Please enter your Proxmox server details:"
-
- read -p "Proxmox Host URL (e.g., https://proxmox.local:8006): " proxmox_host
- read -p "Proxmox Node Name (e.g., pve): " proxmox_node
- read -p "Proxmox API Token ID (e.g., root@pam!pulse): " proxmox_token_id
- read -p "Proxmox API Token Secret: " proxmox_token_secret
-
- # Update the .env file with Proxmox details if provided
- if [ -n "$proxmox_host" ]; then
- sed -i.bak "s|PROXMOX_HOST=.*|PROXMOX_HOST=$proxmox_host|" .env
- fi
-
- if [ -n "$proxmox_node" ]; then
- sed -i.bak "s/PROXMOX_NODE=.*/PROXMOX_NODE=$proxmox_node/" .env
- fi
-
- if [ -n "$proxmox_token_id" ]; then
- sed -i.bak "s/PROXMOX_TOKEN_ID=.*/PROXMOX_TOKEN_ID=$proxmox_token_id/" .env
- fi
-
- if [ -n "$proxmox_token_secret" ]; then
- sed -i.bak "s/PROXMOX_TOKEN_SECRET=.*/PROXMOX_TOKEN_SECRET=$proxmox_token_secret/" .env
- fi
-
- rm -f .env.bak
- print_success "Updated .env file with Proxmox configuration"
- fi
-else
- print_success "Using existing .env file"
-fi
-
-# Ask if the user wants to run the setup script
-print_header "Setup"
-read -p "Do you want to run the application now? (y/n) [y]: " run_setup
-run_setup=${run_setup:-y}
-
-if [ "$run_setup" = "y" ] || [ "$run_setup" = "Y" ]; then
- if [ "$install_type" = "1" ]; then
- echo "Starting Pulse with Proxmox connection..."
- npm run prod:docker
- else
- echo "Starting Pulse with mock data..."
- npm run dev:docker
- fi
-else
- echo -e "\nTo start Pulse later, run one of the following commands:"
- echo -e " - For production: ${GREEN}npm run prod:docker${NC}"
- echo -e " - For development with mock data: ${GREEN}npm run dev:docker${NC}"
-fi
-
-print_header "Installation Complete"
-echo "Thank you for installing Pulse!"
-echo "Access the dashboard at http://localhost:7654"
-echo -e "For more information, see the ${BLUE}README.md${NC} file or visit ${BLUE}https://github.com/rcourtman/pulse${NC}"
\ No newline at end of file
diff --git a/scripts/start-dev.bat b/scripts/start-dev.bat
deleted file mode 100644
index b52155705..000000000
--- a/scripts/start-dev.bat
+++ /dev/null
@@ -1,110 +0,0 @@
-@echo off
-setlocal
-
-REM Stop any running Pulse Docker containers first
-echo Stopping any running Pulse Docker containers...
-where docker >nul 2>&1
-if %ERRORLEVEL% EQU 0 (
- for /f "tokens=*" %%i in ('docker ps -q --filter "name=pulse"') do (
- docker stop %%i
- )
-) else (
- echo Docker not found, skipping container cleanup...
-)
-
-REM Kill any existing servers
-echo Killing any existing servers...
-taskkill /f /im "node.exe" /fi "WINDOWTITLE eq node dist/server.js" 2>nul
-call npx kill-port 7654 7656 3000
-
-REM Clear any existing data files that might persist between sessions
-echo Clearing any persisted data from previous sessions...
-node scripts/clear-data.js
-
-REM Set environment to development
-set NODE_ENV=development
-
-REM Load environment variables from .env if it exists
-if exist .env (
- echo Loading environment from .env
- for /f "tokens=*" %%a in (.env) do (
- set "%%a"
- )
-)
-
-REM Override with development settings
-set USE_MOCK_DATA=true
-set MOCK_DATA_ENABLED=true
-set MOCK_SERVER_PORT=7656
-
-REM Start the mock data server on port 7656
-echo Starting mock data server on port 7656...
-start /b cmd /c "npx ts-node src/mock/run-server.ts"
-
-REM Wait a moment for the mock server to start
-timeout /t 5 /nobreak > nul
-
-REM Verify mock server is running
-if "%DOCKER_CONTAINER%"=="" (
- :: Not in Docker, check localhost
- for /f "tokens=*" %%a in ('powershell -Command "(Invoke-WebRequest -Uri http://localhost:7656 -UseBasicParsing -ErrorAction SilentlyContinue).StatusCode"') do set HTTP_CODE=%%a
-) else (
- :: In Docker, check 0.0.0.0
- for /f "tokens=*" %%a in ('powershell -Command "(Invoke-WebRequest -Uri http://0.0.0.0:7656 -UseBasicParsing -ErrorAction SilentlyContinue).StatusCode"') do set HTTP_CODE=%%a
-)
-
-:: Check if we got a valid HTTP response (200 or 404 both mean the server is running)
-if "%HTTP_CODE%"=="200" (
- echo ✅ Mock server is running on port 7656 (HTTP code: %HTTP_CODE%)
-) else if "%HTTP_CODE%"=="404" (
- echo ✅ Mock server is running on port 7656 (HTTP code: %HTTP_CODE%)
-) else (
- echo ❌ Mock server failed to start
- type %TEMP%\pulse-mock-server.log
-)
-
-REM Start the backend server on port 7654
-echo Starting backend server on port 7654...
-start /b cmd /c "set PORT=7654 && npm run dev:server"
-
-REM Wait a moment for the server to start
-timeout /t 3 /nobreak > nul
-
-REM Verify backend server is running
-call :check_server_running 7654 "Backend server"
-if %ERRORLEVEL% NEQ 0 (
- echo ERROR: Backend server failed to start on port 7654
- exit /b 1
-)
-
-REM Set host IP to bind to all interfaces
-set HOST_IP=0.0.0.0
-
-echo.
-echo Pulse is now running in development mode with mock data!
-echo - Mock Data Server: http://localhost:7656 (internal only)
-echo - Backend API: http://localhost:7654 (internal only)
-echo - Frontend UI: http://localhost:3000 (use this for development)
-echo.
-echo Access the application at: http://localhost:3000
-echo.
-
-REM Start the frontend Vite dev server
-echo Starting frontend development server on port 3000...
-cd frontend && npm run dev -- --host %HOST_IP% --port 3000 --strict-port
-
-REM When the frontend exits, also kill the backend and mock servers
-taskkill /f /im "node.exe" /fi "WINDOWTITLE eq npm run dev:server" 2>nul
-taskkill /f /im "node.exe" /fi "WINDOWTITLE eq npx ts-node src/mock/run-server.ts" 2>nul
-exit /b 0
-
-:check_server_running
-set PORT=%~1
-set SERVER_NAME=%~2
-netstat -ano | findstr ":%PORT% " | findstr "LISTENING" > nul
-if %ERRORLEVEL% NEQ 0 (
- echo %SERVER_NAME% is not running on port %PORT%
- exit /b 1
-)
-echo %SERVER_NAME% is running on port %PORT%
-exit /b 0
\ No newline at end of file
diff --git a/scripts/start-dev.sh b/scripts/start-dev.sh
deleted file mode 100755
index 7f8037a13..000000000
--- a/scripts/start-dev.sh
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-# Check for dry run flag from command line or environment variable
-DRY_RUN=false
-if [ "$DRY_RUN" = "true" ]; then
- echo "Dry run mode enabled via environment variable - will not actually start the server"
-else
- for arg in "$@"; do
- if [ "$arg" == "--dry-run" ]; then
- DRY_RUN=true
- echo "Dry run mode enabled via command line flag - will not actually start the server"
- fi
- done
-fi
-
-# Check if running in Docker
-if [ -n "$DOCKER_CONTAINER" ]; then
- echo "Running in Docker container"
-else
- # Stop any running Pulse Docker containers first if not in Docker
- echo "Stopping any running Pulse Docker containers..."
- if command -v docker &> /dev/null; then
- docker ps -q --filter "name=pulse" | xargs -r docker stop
- else
- echo "Docker not found, skipping container cleanup..."
- fi
-fi
-
-# Kill any existing servers
-echo "Killing any existing servers..."
-pkill -f "node dist/server.js" || true
-pkill -f "ts-node src/mock/run-server.ts" || true
-npx kill-port 7654 7656 3000
-
-# Clear any existing data files that might persist between sessions
-echo "Clearing any persisted data from previous sessions..."
-node scripts/clear-data.js
-
-# Set environment to development
-export NODE_ENV=development
-
-# Load environment variables from .env if it exists
-if [ -f .env ]; then
- echo "Loading environment from .env"
- set -a
- source .env
- set +a
-fi
-
-# Only set mock data if it's not already set in the environment
-# This allows the caller to override it
-if [ -z "$USE_MOCK_DATA" ]; then
- export USE_MOCK_DATA=true
-fi
-if [ -z "$MOCK_DATA_ENABLED" ]; then
- export MOCK_DATA_ENABLED=true
-fi
-export MOCK_SERVER_PORT=7656
-
-# Check if we should use mock data
-if [ "$USE_MOCK_DATA" = "true" ] || [ "$MOCK_DATA_ENABLED" = "true" ]; then
- echo "Starting development environment with mock data..."
-
- # Start the mock data server
- echo "Starting mock data server on port 7656..."
-
- if [ "$DRY_RUN" = false ]; then
- # If running in Docker, we need to bind to 0.0.0.0 instead of localhost
- if [ -n "$DOCKER_CONTAINER" ]; then
- # Create a modified version of the run-server.ts file that binds to 0.0.0.0
- echo "global.HOST = '0.0.0.0';" > /tmp/mock-server-config.js
- # Start the mock server with the modified config
- NODE_OPTIONS="--require /tmp/mock-server-config.js" MOCK_SERVER_PORT=7656 ts-node src/mock/run-server.ts > /tmp/pulse-mock-server.log 2>&1 &
- else
- # Start the mock server normally
- MOCK_SERVER_PORT=7656 ts-node src/mock/run-server.ts > /tmp/pulse-mock-server.log 2>&1 &
- fi
-
- MOCK_SERVER_PID=$!
-
- # Wait a moment for the mock server to start
- sleep 5
-
- # Verify mock server is running
- if [ -n "$DOCKER_CONTAINER" ]; then
- # In Docker, check 0.0.0.0 or the HOST_IP environment variable
- # Use -I to get headers only and check if we got ANY HTTP response (200 or 404 both mean the server is running)
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://0.0.0.0:7656)
- if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "200" ]]; then
- echo "✅ Mock server is running on port 7656 (HTTP code: $HTTP_CODE)"
- else
- echo "❌ Mock server failed to start (HTTP code: $HTTP_CODE)"
- cat /tmp/pulse-mock-server.log
- fi
- else
- # Not in Docker, check localhost
- # Use -I to get headers only and check if we got ANY HTTP response (200 or 404 both mean the server is running)
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:7656)
- if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "200" ]]; then
- echo "✅ Mock server is running on port 7656 (HTTP code: $HTTP_CODE)"
- else
- echo "❌ Mock server failed to start (HTTP code: $HTTP_CODE)"
- cat /tmp/pulse-mock-server.log
- fi
- fi
- else
- echo "[DRY RUN] Would start mock data server"
- fi
-else
- echo "Starting development environment with real data..."
-fi
-
-# Start the backend server with hot reloading on port 7654
-echo "Starting backend server on port 7654..."
-if [ "$DRY_RUN" = false ]; then
- PORT=7654 ts-node-dev --respawn --transpile-only src/server.ts &
- BACKEND_PID=$!
-
- # Wait a moment for the server to start
- sleep 3
-
- # Verify backend server is running
- if curl -s http://localhost:7654/api/status > /dev/null; then
- echo "✅ Backend server is running on port 7654"
- else
- echo "❌ Backend server failed to start"
- fi
-else
- echo "[DRY RUN] Would start backend server with: PORT=7654 ts-node-dev --respawn --transpile-only src/server.ts"
-fi
-
-# Get the host IP (use 0.0.0.0 in Docker, otherwise use localhost)
-HOST_IP="0.0.0.0"
-if [ -z "$DOCKER_CONTAINER" ]; then
- # Not in Docker, use 0.0.0.0 to bind to all interfaces
- HOST_IP="0.0.0.0"
-fi
-
-echo ""
-echo "Pulse is now running in development mode!"
-echo "- Backend API: http://localhost:7654"
-if [ "$USE_MOCK_DATA" = "true" ] || [ "$MOCK_DATA_ENABLED" = "true" ]; then
- echo "- Mock Server: http://localhost:7656"
-else
- echo "- Using real Proxmox data"
-fi
-echo "- Frontend UI: http://localhost:3000 (use this for development)"
-echo ""
-echo "Access the application at: http://localhost:3000"
-echo ""
-
-# Start the frontend Vite dev server with hot reloading on port 3000
-echo "Starting frontend development server on port 3000..."
-if [ "$DRY_RUN" = false ]; then
- cd frontend && npm run dev -- --host "$HOST_IP" --port 3000 --strict-port
-
- # When the frontend exits, also kill the backend and mock server
- kill $BACKEND_PID
- if [ -n "$MOCK_SERVER_PID" ]; then
- kill $MOCK_SERVER_PID
- fi
-else
- echo "[DRY RUN] Would start frontend with: cd frontend && npm run dev -- --host \"$HOST_IP\" --port 3000 --strict-port"
-fi
\ No newline at end of file
diff --git a/scripts/start-mock-dev.bat b/scripts/start-mock-dev.bat
deleted file mode 100644
index 82af1b30b..000000000
--- a/scripts/start-mock-dev.bat
+++ /dev/null
@@ -1,45 +0,0 @@
-@echo off
-setlocal
-
-:: Kill any existing servers
-echo Killing any existing servers...
-taskkill /F /IM node.exe /FI "WINDOWTITLE eq dist/server.js" 2>nul
-taskkill /F /IM node.exe /FI "WINDOWTITLE eq ts-node src/mock/run-server.ts" 2>nul
-npx kill-port 7654 7655 7656 5173
-
-:: Clear any existing data files that might persist between sessions
-echo Clearing any persisted data from previous sessions...
-node scripts/clear-data.js
-
-:: Set environment to development and load the environment file
-set NODE_ENV=development
-
-:: Load environment variables from .env if it exists
-if exist .env (
- echo Loading environment from .env
- for /f "tokens=*" %%a in (.env) do set "%%a"
-)
-
-:: Force mock data to be enabled for development
-set USE_MOCK_DATA=true
-set MOCK_DATA_ENABLED=true
-
-:: Start the mock server in the background
-echo Starting mock server...
-start /B node scripts/start-mock-server.js
-set MOCK_SERVER_PID=%ERRORLEVEL%
-
-:: Build the backend
-echo Building backend...
-call npm run build
-
-:: Build the frontend
-echo Building frontend...
-cd frontend && call npm run build && cd ..
-
-:: Start the development server
-echo Starting development server...
-node dist/server.js
-
-:: When the server exits, also kill the mock server
-taskkill /F /PID %MOCK_SERVER_PID% 2>nul
\ No newline at end of file
diff --git a/scripts/start-mock-dev.sh b/scripts/start-mock-dev.sh
deleted file mode 100644
index 3f2738fd5..000000000
--- a/scripts/start-mock-dev.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-# Kill any existing servers
-echo "Killing any existing servers..."
-pkill -f "node dist/server.js" || true
-pkill -f "ts-node src/mock/run-server.ts" || true
-npx kill-port 7654 7655 7656 5173
-
-# Clear any existing data files that might persist between sessions
-echo "Clearing any persisted data from previous sessions..."
-node scripts/clear-data.js
-
-# Set environment to development and load the environment file
-export NODE_ENV=development
-
-# Load environment variables from .env if it exists
-if [ -f .env ]; then
- echo "Loading environment from .env"
- set -a
- source .env
- set +a
-fi
-
-# Force mock data to be enabled for development
-export USE_MOCK_DATA=true
-export MOCK_DATA_ENABLED=true
-
-# Start the mock server in the background
-echo "Starting mock server..."
-node scripts/start-mock-server.js &
-MOCK_SERVER_PID=$!
-
-# Build the backend
-echo "Building backend..."
-npm run build
-
-# Build the frontend
-echo "Building frontend..."
-cd frontend && npm run build && cd ..
-
-# Start the development server
-echo "Starting development server..."
-node dist/server.js
-
-# When the server exits, also kill the mock server
-kill $MOCK_SERVER_PID
\ No newline at end of file
diff --git a/scripts/start-mock-server.js b/scripts/start-mock-server.js
deleted file mode 100644
index fc3db3ed5..000000000
--- a/scripts/start-mock-server.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const express = require('express');
-const cors = require('cors');
-const app = express();
-
-// Enable CORS
-app.use(cors());
-
-// Mock data endpoints
-app.get('/api/v1/cluster/status', (req, res) => {
- res.json({
- nodes: [
- {
- name: 'pve1',
- status: 'online',
- cpu: {
- usage: 45.2,
- cores: 4
- },
- memory: {
- total: 16777216,
- used: 8388608,
- free: 8388608
- }
- },
- {
- name: 'pve2',
- status: 'online',
- cpu: {
- usage: 32.8,
- cores: 4
- },
- memory: {
- total: 16777216,
- used: 6291456,
- free: 10485760
- }
- }
- ]
- });
-});
-
-app.get('/api/v1/cluster/resources', (req, res) => {
- res.json({
- vms: [
- {
- id: '100',
- name: 'vm-100',
- status: 'running',
- node: 'pve1',
- cpu: {
- usage: 25.5,
- cores: 2
- },
- memory: {
- total: 4096,
- used: 2048,
- free: 2048
- }
- }
- ],
- containers: [
- {
- id: '101',
- name: 'ct-101',
- status: 'running',
- node: 'pve2',
- cpu: {
- usage: 15.2,
- cores: 1
- },
- memory: {
- total: 2048,
- used: 1024,
- free: 1024
- }
- }
- ]
- });
-});
-
-const PORT = process.env.MOCK_SERVER_PORT || 7655;
-app.listen(PORT, () => {
- console.log(`Mock server running on port ${PORT}`);
-});
\ No newline at end of file
diff --git a/scripts/start-prod.bat b/scripts/start-prod.bat
deleted file mode 100644
index 211bf507f..000000000
--- a/scripts/start-prod.bat
+++ /dev/null
@@ -1,101 +0,0 @@
-@echo off
-setlocal enabledelayedexpansion
-
-REM Check for dry run flag
-set DRY_RUN=false
-for %%a in (%*) do (
- if "%%a"=="--dry-run" (
- set DRY_RUN=true
- echo Dry run mode enabled - will not actually start the server
- )
-)
-
-REM Kill any existing server processes
-taskkill /f /im "node.exe" /fi "WINDOWTITLE eq node dist/server.js" 2>nul
-taskkill /f /im "node.exe" /fi "WINDOWTITLE eq ts-node src/mock/run-server.ts" 2>nul
-call npx kill-port 7654 7655 7656 5173
-
-REM Clear any existing data files that might persist between sessions
-echo Clearing any persisted data from previous sessions...
-node scripts/clear-data.js
-
-REM Set environment to production
-set NODE_ENV=production
-
-REM Load environment variables from .env if it exists
-if exist .env (
- echo Loading environment from .env
- for /f "tokens=*" %%a in (.env) do (
- set "line=%%a"
- if not "!line:~0,1!"=="#" (
- if not "!line!"=="" (
- set "%%a"
- )
- )
- )
-)
-
-REM Force mock data to be disabled for production
-set USE_MOCK_DATA=false
-set MOCK_DATA_ENABLED=false
-
-REM Build the backend
-echo Building the backend...
-if "%DRY_RUN%"=="false" (
- call npm run build
-) else (
- echo [DRY RUN] Would run: npm run build
-)
-
-REM Build the frontend
-echo Building the frontend...
-if "%DRY_RUN%"=="false" (
- cd frontend
- call npm run build
- cd ..
-) else (
- echo [DRY RUN] Would run: cd frontend ^&^& npm run build ^&^& cd ..
-)
-
-REM Start the production server
-echo Starting production server...
-if "%DRY_RUN%"=="false" (
- node dist/server.js
-) else (
- echo [DRY RUN] Would run: node dist/server.js
-)
-
-REM If we're using mock data, start the mock server
-if "%USE_MOCK_DATA%"=="true" (
- echo Starting mock data server on port 7656...
-
- :: Start the mock server
- start /b node dist/mock/run-server.js > %TEMP%\pulse-mock-server.log 2>&1
-
- :: Wait a moment for the mock server to start
- timeout /t 5 > nul
-
- :: Verify mock server is running
- if "%DOCKER_CONTAINER%"=="" (
- :: Not in Docker, check localhost
- for /f "tokens=*" %%a in ('powershell -Command "(Invoke-WebRequest -Uri http://localhost:7656 -UseBasicParsing -ErrorAction SilentlyContinue).StatusCode"') do set HTTP_CODE=%%a
- ) else (
- :: In Docker, check 0.0.0.0
- for /f "tokens=*" %%a in ('powershell -Command "(Invoke-WebRequest -Uri http://0.0.0.0:7656 -UseBasicParsing -ErrorAction SilentlyContinue).StatusCode"') do set HTTP_CODE=%%a
- )
-
- :: Check if we got a valid HTTP response (200 or 404 both mean the server is running)
- if "%HTTP_CODE%"=="200" (
- echo ✅ Mock server is running on port 7656 (HTTP code: %HTTP_CODE%)
- ) else if "%HTTP_CODE%"=="404" (
- echo ✅ Mock server is running on port 7656 (HTTP code: %HTTP_CODE%)
- ) else (
- echo ❌ Mock server failed to start
- type %TEMP%\pulse-mock-server.log
- )
-)
-
-REM When the server exits, also kill the mock server if it's running
-if "%USE_MOCK_DATA%"=="true" (
- taskkill /f /im "node.exe" /fi "WINDOWTITLE eq node dist/mock/run-server.js" 2>nul
-)
\ No newline at end of file
diff --git a/scripts/start-prod.sh b/scripts/start-prod.sh
deleted file mode 100755
index f91f8fa91..000000000
--- a/scripts/start-prod.sh
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-# Check for dry run flag from command line or environment variable
-DRY_RUN=false
-if [ "$DRY_RUN" = "true" ]; then
- echo "Dry run mode enabled via environment variable - will not actually start the server"
-else
- for arg in "$@"; do
- if [ "$arg" == "--dry-run" ]; then
- DRY_RUN=true
- echo "Dry run mode enabled via command line flag - will not actually start the server"
- fi
- done
-fi
-
-# Kill any existing servers
-echo "Killing any existing servers..."
-pkill -f "node dist/server.js" || true
-pkill -f "ts-node src/mock/run-server.ts" || true
-npx kill-port 7654 7655 7656 5173
-
-# Clear any existing data files that might persist between sessions
-echo "Clearing any persisted data from previous sessions..."
-node scripts/clear-data.js
-
-# Set environment to production and load the environment file
-export NODE_ENV=production
-
-# Load environment variables from .env if it exists
-if [ -f .env ]; then
- echo "Loading environment from .env"
- set -a
- source .env
- set +a
-fi
-
-# Force mock data to be disabled for production
-export USE_MOCK_DATA=false
-export MOCK_DATA_ENABLED=false
-
-# Build the backend
-echo "Building backend..."
-if [ "$DRY_RUN" = false ]; then
- npm run build
-else
- echo "[DRY RUN] Would run: npm run build"
-fi
-
-# Build the frontend
-echo "Building frontend..."
-if [ "$DRY_RUN" = false ]; then
- cd frontend && npm run build && cd ..
-else
- echo "[DRY RUN] Would run: cd frontend && npm run build && cd .."
-fi
-
-# Start the production server
-echo "Starting production server..."
-if [ "$DRY_RUN" = false ]; then
- node dist/server.js
-else
- echo "[DRY RUN] Would run: node dist/server.js"
-fi
-
-# If we're using mock data, start the mock server
-if [ "$USE_MOCK_DATA" = "true" ] || [ "$MOCK_DATA_ENABLED" = "true" ]; then
- echo "Starting mock data server on port 7656..."
-
- if [ "$DRY_RUN" = false ]; then
- # If running in Docker, we need to bind to 0.0.0.0 instead of localhost
- if [ -n "$DOCKER_CONTAINER" ]; then
- # Create a modified version of the run-server.ts file that binds to 0.0.0.0
- echo "global.HOST = '0.0.0.0';" > /tmp/mock-server-config.js
- # Start the mock server with the modified config
- NODE_OPTIONS="--require /tmp/mock-server-config.js" MOCK_SERVER_PORT=7656 node dist/mock/run-server.js > /tmp/pulse-mock-server.log 2>&1 &
- else
- # Start the mock server normally
- MOCK_SERVER_PORT=7656 node dist/mock/run-server.js > /tmp/pulse-mock-server.log 2>&1 &
- fi
-
- MOCK_SERVER_PID=$!
-
- # Wait a moment for the mock server to start
- sleep 5
-
- # Verify mock server is running
- if [ -n "$DOCKER_CONTAINER" ]; then
- # In Docker, check 0.0.0.0 or the HOST_IP environment variable
- # Use -I to get headers only and check if we got ANY HTTP response (200 or 404 both mean the server is running)
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://0.0.0.0:7656)
- if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "200" ]]; then
- echo "✅ Mock server is running on port 7656 (HTTP code: $HTTP_CODE)"
- else
- echo "❌ Mock server failed to start (HTTP code: $HTTP_CODE)"
- cat /tmp/pulse-mock-server.log
- fi
- else
- # Not in Docker, check localhost
- # Use -I to get headers only and check if we got ANY HTTP response (200 or 404 both mean the server is running)
- HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:7656)
- if [[ "$HTTP_CODE" == "404" || "$HTTP_CODE" == "200" ]]; then
- echo "✅ Mock server is running on port 7656 (HTTP code: $HTTP_CODE)"
- else
- echo "❌ Mock server failed to start (HTTP code: $HTTP_CODE)"
- cat /tmp/pulse-mock-server.log
- fi
- fi
- else
- echo "[DRY RUN] Would start mock data server"
- fi
-fi
-
-# When the server exits, also kill the mock server if it's running
-if [ -n "$MOCK_SERVER_PID" ]; then
- kill $MOCK_SERVER_PID
-fi
\ No newline at end of file
diff --git a/scripts/start.js b/scripts/start.js
deleted file mode 100644
index 96f58ca7d..000000000
--- a/scripts/start.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/**
- * Unified cross-platform script to start Pulse in different environments
- * Usage: node start.js [dev|prod] [--dry-run]
- */
-
-const { spawn } = require('child_process');
-const os = require('os');
-const path = require('path');
-const fs = require('fs');
-const dotenv = require('dotenv');
-
-// Parse command line arguments
-const args = process.argv.slice(2);
-const mode = args[0] || 'dev'; // Default to dev mode if not specified
-const isDryRun = args.includes('--dry-run');
-
-// Detect the platform
-const isWindows = os.platform() === 'win32';
-
-console.log(`Detected platform: ${os.platform()}`);
-console.log(`Starting Pulse in ${mode} mode on ${isWindows ? 'Windows' : 'Unix-like'} system...`);
-if (isDryRun) {
- console.log('Dry run mode enabled - commands will be shown but not executed');
-}
-
-// Load environment variables
-function loadEnvironment() {
- // Load from .env file
- dotenv.config();
-
- // For development mode, override with development settings if not already set
- if (mode === 'dev' && process.env.NODE_ENV !== 'development') {
- process.env.NODE_ENV = 'development';
- process.env.USE_MOCK_DATA = 'true';
- process.env.MOCK_DATA_ENABLED = 'true';
- process.env.MOCK_SERVER_PORT = '7656';
- console.log('Using development settings with mock data');
- } else if (mode === 'prod' && process.env.NODE_ENV !== 'production') {
- process.env.NODE_ENV = 'production';
- process.env.USE_MOCK_DATA = 'false';
- process.env.MOCK_DATA_ENABLED = 'false';
- console.log('Using production settings with real data');
- }
-}
-
-// Ensure logo files are properly copied to frontend/public/logos
-function ensureLogoFiles() {
- console.log('Ensuring logo files are properly available in frontend...');
-
- const sourceDir = path.join(process.cwd(), 'public', 'logos');
- const targetDir = path.join(process.cwd(), 'frontend', 'public', 'logos');
-
- // Create target directory if it doesn't exist
- if (!fs.existsSync(targetDir)) {
- console.log('Creating frontend/public/logos directory...');
- fs.mkdirSync(targetDir, { recursive: true });
- }
-
- // Copy all logo files
- try {
- const files = fs.readdirSync(sourceDir);
- for (const file of files) {
- if (file.endsWith('.png') || file.endsWith('.svg')) {
- const sourcePath = path.join(sourceDir, file);
- const targetPath = path.join(targetDir, file);
-
- // Only copy if the file doesn't exist or is older
- if (!fs.existsSync(targetPath) ||
- fs.statSync(sourcePath).mtime > fs.statSync(targetPath).mtime) {
- console.log(`Copying ${file} to frontend/public/logos...`);
- fs.copyFileSync(sourcePath, targetPath);
- }
- }
- }
- console.log('Logo files are up to date.');
- } catch (error) {
- console.warn(`Warning: Could not copy logo files: ${error.message}`);
- console.warn('This is not critical, continuing with startup...');
- }
-}
-
-// Start the appropriate script based on the platform and mode
-function startScript() {
- let scriptPath;
-
- if (isWindows) {
- // Windows scripts
- if (mode === 'prod') {
- scriptPath = path.join(process.cwd(), 'scripts', 'start-prod.bat');
- } else {
- scriptPath = path.join(process.cwd(), 'scripts', 'start-dev.bat');
- }
- } else {
- // Unix-like scripts
- if (mode === 'prod') {
- scriptPath = path.join(process.cwd(), 'scripts', 'start-prod.sh');
- } else {
- scriptPath = path.join(process.cwd(), 'scripts', 'start-dev.sh');
- }
-
- // Make the script executable
- try {
- fs.chmodSync(scriptPath, '755');
- } catch (error) {
- console.error(`Error making script executable: ${error.message}`);
- process.exit(1);
- }
- }
-
- console.log(`Starting script: ${scriptPath}`);
-
- if (isDryRun) {
- console.log('Dry run mode - not actually executing the script');
- return;
- }
-
- // Execute the script
- const scriptArgs = args.filter(arg => arg !== mode && arg !== '--dry-run');
- const child = isWindows
- ? spawn('cmd.exe', ['/c', scriptPath, ...scriptArgs], { stdio: 'inherit' })
- : spawn(scriptPath, scriptArgs, { stdio: 'inherit' });
-
- child.on('error', (error) => {
- console.error(`Error starting script: ${error.message}`);
- process.exit(1);
- });
-
- child.on('close', (code) => {
- console.log(`Script exited with code ${code}`);
- process.exit(code);
- });
-}
-
-// Main execution
-loadEnvironment();
-ensureLogoFiles();
-startScript();
\ No newline at end of file
diff --git a/scripts/update-screenshots.sh b/scripts/update-screenshots.sh
deleted file mode 100644
index 233485ec4..000000000
--- a/scripts/update-screenshots.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-
-# Make script executable if it isn't already
-chmod +x "$0"
-
-echo "Updating screenshots..."
-
-# Install puppeteer if not already installed
-if ! npm list puppeteer >/dev/null 2>&1; then
- echo "Installing puppeteer..."
- npm install puppeteer
-fi
-
-# Create screenshots directory if it doesn't exist
-mkdir -p docs/screenshots
-
-# Start the application in development mode with mock data
-echo "Starting application with mock data..."
-npm run dev:docker &
-
-# Wait for the application to start
-echo "Waiting for application to start..."
-sleep 10
-
-# Take screenshots
-echo "Taking screenshots..."
-node scripts/take-screenshots.js
-
-# Stop the application
-echo "Stopping application..."
-npm run stop
-
-echo "Screenshot update complete!"
\ No newline at end of file
diff --git a/scripts/verify-cluster-config.js b/scripts/verify-cluster-config.js
deleted file mode 100755
index a0454a9ae..000000000
--- a/scripts/verify-cluster-config.js
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Verify Cluster Configuration Script
- *
- * This script checks the current environment configuration for cluster detection settings
- * and verifies that they are correctly applied.
- *
- * Usage: node scripts/verify-cluster-config.js
- */
-
-const fs = require('fs');
-const path = require('path');
-const dotenv = require('dotenv');
-
-// Load environment variables from .env file
-const envFilePath = path.resolve(process.cwd(), '.env');
-if (!fs.existsSync(envFilePath)) {
- console.error('Error: .env file not found');
- process.exit(1);
-}
-
-const envConfig = dotenv.parse(fs.readFileSync(envFilePath));
-
-// Check cluster configuration
-console.log('=== Cluster Configuration ===');
-console.log(`PROXMOX_AUTO_DETECT_CLUSTER: ${envConfig.PROXMOX_AUTO_DETECT_CLUSTER || 'not set'}`);
-console.log(`PROXMOX_CLUSTER_MODE: ${envConfig.PROXMOX_CLUSTER_MODE || 'not set'}`);
-console.log(`MOCK_CLUSTER_ENABLED: ${envConfig.MOCK_CLUSTER_ENABLED || 'not set'}`);
-
-// Check environment mode
-console.log('\n=== Environment Mode ===');
-console.log(`NODE_ENV: ${envConfig.NODE_ENV || 'not set'}`);
-console.log(`LOG_LEVEL: ${envConfig.LOG_LEVEL || 'not set'}`);
-console.log(`USE_MOCK_DATA: ${envConfig.USE_MOCK_DATA || 'not set'}`);
-console.log(`MOCK_DATA_ENABLED: ${envConfig.MOCK_DATA_ENABLED || 'not set'}`);
-
-// Check if the configuration is consistent
-console.log('\n=== Configuration Analysis ===');
-
-// Check if cluster detection is enabled
-const isClusterDetectionEnabled =
- envConfig.PROXMOX_AUTO_DETECT_CLUSTER === 'true' &&
- envConfig.PROXMOX_CLUSTER_MODE === 'true';
-
-// Check if we're in development mode
-const isDevMode = envConfig.NODE_ENV === 'development';
-
-// Check if we're using mock data
-const isMockDataEnabled =
- envConfig.USE_MOCK_DATA === 'true' &&
- envConfig.MOCK_DATA_ENABLED === 'true';
-
-// Check if mock cluster is enabled
-const isMockClusterEnabled = envConfig.MOCK_CLUSTER_ENABLED === 'true';
-
-// Check if log level is set to info
-const isLogLevelInfo = envConfig.LOG_LEVEL === 'info';
-
-console.log(`Cluster Detection: ${isClusterDetectionEnabled ? 'Enabled' : 'Disabled'}`);
-console.log(`Development Mode: ${isDevMode ? 'Yes' : 'No'}`);
-console.log(`Log Level: ${envConfig.LOG_LEVEL || 'not set'}`);
-console.log(`Mock Data: ${isMockDataEnabled ? 'Enabled' : 'Disabled'}`);
-console.log(`Mock Cluster: ${isMockClusterEnabled ? 'Enabled' : 'Disabled'}`);
-
-// Verify configuration consistency
-console.log('\n=== Configuration Consistency ===');
-
-if (isDevMode) {
- if (isMockDataEnabled) {
- console.log('✓ Development mode is correctly using mock data');
-
- if (isLogLevelInfo) {
- console.log('✓ Log level is correctly set to info for development mode');
- } else {
- console.log('✗ Log level should be set to info for development mode');
- }
-
- if (isClusterDetectionEnabled) {
- console.log('✓ Cluster detection is correctly enabled for development mode');
-
- if (isMockClusterEnabled) {
- console.log('✓ Mock cluster is correctly enabled for development mode with cluster detection');
- } else {
- console.log('✗ Mock cluster should be enabled for development mode with cluster detection');
- }
- } else {
- console.log('✗ Cluster detection should be enabled for development mode');
-
- if (!isMockClusterEnabled) {
- console.log('✓ Mock cluster is correctly disabled for no-cluster mode');
- } else {
- console.log('✗ Mock cluster should be disabled for no-cluster mode');
- }
- }
- } else {
- console.log('✗ Development mode should use mock data');
- }
-} else {
- // Production mode
- if (!isMockDataEnabled) {
- console.log('✓ Production mode is correctly not using mock data');
-
- if (isLogLevelInfo) {
- console.log('✓ Log level is correctly set to info for production mode');
- } else {
- console.log('✗ Log level should be set to info for production mode');
- }
-
- if (isClusterDetectionEnabled) {
- console.log('✓ Cluster detection is correctly enabled for production mode');
- } else {
- console.log('✗ Cluster detection should be enabled for production mode');
- }
- } else {
- console.log('✗ Production mode should not use mock data');
- }
-}
-
-console.log('\nVerification complete.');
\ No newline at end of file
diff --git a/server/.env b/server/.env
new file mode 100644
index 000000000..d4e7f9c2a
--- /dev/null
+++ b/server/.env
@@ -0,0 +1,9 @@
+PROXMOX_HOST=https://proxmox.lan:8006
+PROXMOX_PORT=8006
+PROXMOX_TOKEN_ID=root@pam!pulse
+PROXMOX_TOKEN_SECRET=SECRET_REMOVED
+PROXMOX_ENABLED=true
+PROXMOX_ALLOW_SELF_SIGNED_CERTS=true
+PORT=7654
+USE_MOCK_DATA=false
+VERBOSE=false
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
new file mode 100644
index 000000000..5ae49b3c8
--- /dev/null
+++ b/server/index.js
@@ -0,0 +1,330 @@
+require('dotenv').config(); // Load environment variables from .env file
+
+const express = require('express');
+const http = require('http');
+const path = require('path');
+const cors = require('cors');
+const { Server } = require('socket.io');
+const axios = require('axios');
+const https = require('https');
+
+// Development specific dependencies
+let chokidar;
+if (process.env.NODE_ENV === 'development') {
+ try {
+ chokidar = require('chokidar');
+ } catch (e) {
+ console.warn('chokidar is not installed. Hot reload requires chokidar: npm install --save-dev chokidar');
+ }
+}
+
+// Proxmox node configuration - using the same config as the main server
+const proxmoxConfig = {
+ node1: {
+ name: process.env.PROXMOX_NODE_NAME || 'minipc',
+ host: process.env.PROXMOX_HOST || 'https://192.168.0.132:8006',
+ port: process.env.PROXMOX_PORT || '8006',
+ tokenId: process.env.PROXMOX_TOKEN_ID || 'root@pam!pulse',
+ tokenSecret: process.env.PROXMOX_TOKEN_SECRET || 'e1850350-6afc-4b8e-ae28-472152af84f9',
+ enabled: process.env.PROXMOX_ENABLED !== 'false',
+ allowSelfSignedCerts: process.env.PROXMOX_ALLOW_SELF_SIGNED_CERTS !== 'false',
+ credentials: {
+ username: process.env.PROXMOX_USERNAME || 'root',
+ password: process.env.PROXMOX_PASSWORD || 'password',
+ realm: process.env.PROXMOX_REALM || 'pam'
+ }
+ }
+};
+
+// Server configuration
+const DEBUG_METRICS = false; // Set to true to show detailed metrics logs
+const UPDATE_INTERVAL = 2000; // 2 seconds for updates
+const PORT = 7655; // Using a different port from the main server
+
+// Create Proxmox API client
+const proxmoxApi = axios.create({
+ baseURL: proxmoxConfig.node1.host.includes('://')
+ ? `${proxmoxConfig.node1.host}/api2/json`
+ : `https://${proxmoxConfig.node1.host}:${proxmoxConfig.node1.port}/api2/json`,
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: !proxmoxConfig.node1.allowSelfSignedCerts
+ }),
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+});
+
+// Add request interceptor for authentication
+proxmoxApi.interceptors.request.use(config => {
+ // Add API token authentication
+ if (proxmoxConfig.node1.tokenId && proxmoxConfig.node1.tokenSecret) {
+ config.headers.Authorization = `PVEAPIToken=${proxmoxConfig.node1.tokenId}=${proxmoxConfig.node1.tokenSecret}`;
+ }
+ // Fallback to password auth if configured
+ else if (proxmoxConfig.node1.credentials) {
+ const { username, password, realm } = proxmoxConfig.node1.credentials;
+ config.headers.Authorization = `Basic ${Buffer.from(`${username}@${realm}:${password}`).toString('base64')}`;
+ }
+
+ return config;
+});
+
+// Create Express app
+const app = express();
+
+// Middleware
+app.use(cors());
+app.use(express.json());
+app.use(express.static(path.join(__dirname, '../public')));
+
+// Create HTTP server
+const server = http.createServer(app);
+
+// Create Socket.IO server with CORS configuration
+const io = new Server(server, {
+ cors: {
+ origin: "*",
+ methods: ["GET", "POST"]
+ }
+});
+
+// Variables to track connected clients
+let connectedClients = 0;
+let initialNodesLogged = false; // Flag to log node count only once
+
+// Helper function to get raw Proxmox data
+async function fetchRawProxmoxData() {
+ // const configuredNodeName = proxmoxConfig.node1.name; // No longer needed for API calls
+ const rawData = {
+ nodes: [],
+ vms: [],
+ containers: [],
+ metrics: []
+ };
+
+ let nodesToQuery = [];
+ let discoveredNodeName = null; // To store the node name discovered via /version
+
+ try {
+ // Attempt to fetch cluster nodes
+ const nodesResponse = await proxmoxApi.get('/nodes');
+ nodesToQuery = nodesResponse.data.data || [];
+ rawData.nodes = nodesToQuery; // Store the nodes list if successful
+
+ if (nodesToQuery.length === 0) {
+ console.warn('Proxmox API returned 0 nodes from /nodes endpoint. Attempting single node discovery.');
+ // If /nodes returns empty, still try to discover the single node via /version
+ try {
+ const versionResponse = await proxmoxApi.get('/version');
+ discoveredNodeName = versionResponse.data.data.node;
+ if (!discoveredNodeName) {
+ throw new Error("Could not determine node name from /version endpoint.");
+ }
+ console.log(`Discovered single node name: ${discoveredNodeName}`);
+ nodesToQuery = [{ node: discoveredNodeName }]; // Use the discovered name
+
+ // Optionally, try to get status for the single node
+ try {
+ const statusResponse = await proxmoxApi.get(`/nodes/${discoveredNodeName}/status`);
+ rawData.nodes = [statusResponse.data.data]; // Use actual status if available
+ } catch (statusError) {
+ console.warn(`Could not fetch status for discovered single node ${discoveredNodeName}: ${statusError.message}`);
+ rawData.nodes = [{ node: discoveredNodeName, status: 'unknown' }]; // Fallback to discovered name
+ }
+ } catch (discoveryError) {
+ console.error(`Single node discovery failed: ${discoveryError.message}. Cannot proceed.`);
+ // Return empty data if we can't even discover the node name
+ return { nodes: [], vms: [], containers: [], metrics: [] };
+ }
+ }
+ } catch (error) {
+ console.warn(`Failed to fetch /nodes (attempting single node discovery): ${error.message}`);
+ // Assume single node mode if /nodes fails, try discovery via /version
+ try {
+ const versionResponse = await proxmoxApi.get('/version');
+ discoveredNodeName = versionResponse.data.data.node;
+ if (!discoveredNodeName) {
+ throw new Error("Could not determine node name from /version endpoint.");
+ }
+ console.log(`Discovered single node name: ${discoveredNodeName}`);
+ nodesToQuery = [{ node: discoveredNodeName }]; // Use the discovered name
+
+ // Optionally, try to get status for the single node
+ try {
+ const statusResponse = await proxmoxApi.get(`/nodes/${discoveredNodeName}/status`);
+ rawData.nodes = [statusResponse.data.data]; // Use actual status if available
+ } catch (statusError) {
+ console.warn(`Could not fetch status for discovered single node ${discoveredNodeName}: ${statusError.message}`);
+ rawData.nodes = [{ node: discoveredNodeName, status: 'unknown' }]; // Fallback to discovered name
+ }
+ } catch (discoveryError) {
+ console.error(`Single node discovery failed after /nodes error: ${discoveryError.message}. Cannot proceed.`);
+ // Return empty data if discovery fails
+ return { nodes: [], vms: [], containers: [], metrics: [] };
+ }
+ }
+
+ // For each node (either from /nodes or the single discovered node), fetch guests and metrics
+ for (const node of nodesToQuery) {
+ const nodeName = node.node; // Use the node name from the list (discovered or from /nodes)
+ if (!nodeName) {
+ console.error("Node object missing 'node' property:", node);
+ continue; // Skip if node name is missing
+ }
+ try {
+ // Fetch VMs
+ const vmsResponse = await proxmoxApi.get(`/nodes/${nodeName}/qemu`);
+ if (vmsResponse.data.data && Array.isArray(vmsResponse.data.data)) {
+ rawData.vms.push(...vmsResponse.data.data.map(vm => ({
+ ...vm,
+ node: nodeName // Ensure node name is correctly assigned
+ })));
+
+ // Collect metrics for running VMs
+ for (const vm of vmsResponse.data.data.filter(vm => vm.status === 'running')) {
+ try {
+ // Get traditional RRD data
+ const rrdData = await proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/rrddata`, {
+ params: { timeframe: 'hour', cf: 'AVERAGE' }
+ });
+
+ // Try to get real-time "current" values
+ const currentData = await proxmoxApi.get(`/nodes/${nodeName}/qemu/${vm.vmid}/status/current`);
+
+ let metricData = {
+ id: vm.vmid, name: vm.name, node: nodeName, type: 'qemu', data: [],
+ current: currentData?.data?.data || null
+ };
+ if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data;
+ rawData.metrics.push(metricData);
+
+ if (rrdData?.data?.data?.length > 1 && DEBUG_METRICS) {
+ const newest = rrdData.data.data[rrdData.data.data.length - 1].time;
+ const secondNewest = rrdData.data.data[rrdData.data.data.length - 2].time;
+ console.log(`VM ${vm.name || vm.vmid} metrics update interval: ${newest - secondNewest} seconds`);
+ if (currentData?.data?.data) console.log(`VM ${vm.name || vm.vmid} current metrics: CPU=${currentData.data.data.cpu}, Memory=${currentData.data.data.mem}`, `[RRD last update: ${new Date(newest * 1000).toISOString()}]`);
+ }
+ } catch (err) {
+ console.error(`Failed to get metrics for VM ${vm.vmid} on node ${nodeName}: ${err.message}`);
+ }
+ }
+ }
+ } catch (err) {
+ console.error(`Error fetching VMs from ${nodeName}: ${err.message}`);
+ }
+
+ try {
+ // Fetch containers
+ const ctsResponse = await proxmoxApi.get(`/nodes/${nodeName}/lxc`);
+ if (ctsResponse.data.data && Array.isArray(ctsResponse.data.data)) {
+ rawData.containers.push(...ctsResponse.data.data.map(ct => ({
+ ...ct,
+ node: nodeName // Ensure node name is correctly assigned
+ })));
+
+ // Collect metrics for running containers
+ for (const ct of ctsResponse.data.data.filter(ct => ct.status === 'running')) {
+ try {
+ // Get traditional RRD data
+ const rrdData = await proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/rrddata`, {
+ params: { timeframe: 'hour', cf: 'AVERAGE' }
+ });
+
+ // Try to get real-time "current" values
+ const currentData = await proxmoxApi.get(`/nodes/${nodeName}/lxc/${ct.vmid}/status/current`);
+
+ let metricData = {
+ id: ct.vmid, name: ct.name, node: nodeName, type: 'lxc', data: [],
+ current: currentData?.data?.data || null
+ };
+ if (rrdData?.data?.data?.length > 0) metricData.data = rrdData.data.data;
+ rawData.metrics.push(metricData);
+
+ if (rrdData?.data?.data?.length > 1 && DEBUG_METRICS) {
+ const newest = rrdData.data.data[rrdData.data.data.length - 1].time;
+ const secondNewest = rrdData.data.data[rrdData.data.data.length - 2].time;
+ console.log(`Container ${ct.name || ct.vmid} metrics update interval: ${newest - secondNewest} seconds`);
+ if (currentData?.data?.data) console.log(`Container ${ct.name || ct.vmid} current metrics: CPU=${currentData.data.data.cpu}, Memory=${currentData.data.data.mem}`, `[RRD last update: ${new Date(newest * 1000).toISOString()}]`);
+ }
+ } catch (err) {
+ console.error(`Failed to get metrics for container ${ct.vmid} on node ${nodeName}: ${err.message}`);
+ }
+ }
+ }
+ } catch (err) {
+ console.error(`Error fetching containers from ${nodeName}: ${err.message}`);
+ }
+ } // End loop through nodesToQuery
+
+ // console.log(`Collected raw data: ${rawData.nodes.length} nodes, ${rawData.vms.length} VMs, ${rawData.containers.length} containers, ${rawData.metrics.length} metric sets`);
+ return rawData;
+}
+
+// Socket.io connection handling
+io.on('connection', (socket) => {
+ connectedClients++;
+ // console.log(`Client connected. Total clients: ${connectedClients}`);
+
+ // Fetch and send initial data, log node count on first successful fetch
+ fetchRawProxmoxData().then(data => {
+ socket.emit('rawData', data);
+ if (!initialNodesLogged && data && data.nodes && data.nodes.length > 0) {
+ console.log(`Initial connection successful. Found ${data.nodes.length} Proxmox node(s).`);
+ initialNodesLogged = true;
+ } else if (!initialNodesLogged && data && data.nodes && data.nodes.length === 0) {
+ console.log('Initial connection successful. Found 0 Proxmox nodes.');
+ initialNodesLogged = true;
+ } else if (!initialNodesLogged) {
+ // Log if initial fetch failed but connection handler still ran
+ console.warn('Initial Proxmox data fetch failed or returned no nodes.');
+ initialNodesLogged = true; // Prevent repeated warnings
+ }
+ }).catch(error => {
+ console.error('Error fetching initial Proxmox data for client:', error.message);
+ if (!initialNodesLogged) {
+ initialNodesLogged = true; // Prevent repeated warnings even on error
+ }
+ });
+
+ // Handle disconnect
+ socket.on('disconnect', () => {
+ connectedClients--;
+ // console.log(`Client disconnected. Total clients: ${connectedClients}`);
+ });
+});
+
+// Periodic update interval for all connected clients
+const updateInterval = setInterval(async () => {
+ if (connectedClients > 0) {
+ try {
+ // console.log(`Updating raw data for ${connectedClients} client(s)...`);
+ const data = await fetchRawProxmoxData();
+ io.emit('rawData', data);
+ } catch (error) {
+ console.error(`Error in update interval: ${error.message}`);
+ }
+ }
+}, UPDATE_INTERVAL);
+
+// Start the server
+server.listen(PORT, () => {
+ console.log(`Server listening on port ${PORT}`);
+
+ // Setup hot reload in development mode
+ if (process.env.NODE_ENV === 'development' && chokidar) {
+ const publicPath = path.join(__dirname, '../public');
+ console.log(`Watching for changes in ${publicPath}`);
+ const watcher = chokidar.watch(publicPath, {
+ ignored: /(^|[\\\/])\\./, // ignore dotfiles
+ persistent: true,
+ ignoreInitial: true // Don't trigger on initial scan
+ });
+
+ watcher.on('change', (filePath) => {
+ // console.log(`File changed: ${filePath}. Triggering hot reload.`);
+ io.emit('hotReload'); // Notify clients to reload
+ });
+
+ watcher.on('error', error => console.error(`Watcher error: ${error}`));
+ }
+});
\ No newline at end of file
diff --git a/server/node_modules/.bin/mime b/server/node_modules/.bin/mime
new file mode 120000
index 000000000..fbb7ee0ee
--- /dev/null
+++ b/server/node_modules/.bin/mime
@@ -0,0 +1 @@
+../mime/cli.js
\ No newline at end of file
diff --git a/server/node_modules/.package-lock.json b/server/node_modules/.package-lock.json
new file mode 100644
index 000000000..960537760
--- /dev/null
+++ b/server/node_modules/.package-lock.json
@@ -0,0 +1,1217 @@
+{
+ "name": "proxmox-raw-monitor-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.17",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
+ "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.13.11",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
+ "integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.8.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
+ "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+ "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/engine.io/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/engine.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+ "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "engine.io": "~6.6.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+ "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.17.1"
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/server/node_modules/@socket.io/component-emitter/LICENSE b/server/node_modules/@socket.io/component-emitter/LICENSE
new file mode 100644
index 000000000..de5169273
--- /dev/null
+++ b/server/node_modules/@socket.io/component-emitter/LICENSE
@@ -0,0 +1,24 @@
+(The MIT License)
+
+Copyright (c) 2014 Component contributors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/node_modules/@socket.io/component-emitter/Readme.md b/server/node_modules/@socket.io/component-emitter/Readme.md
new file mode 100644
index 000000000..feb36f191
--- /dev/null
+++ b/server/node_modules/@socket.io/component-emitter/Readme.md
@@ -0,0 +1,79 @@
+# `@socket.io/component-emitter`
+
+ Event emitter component.
+
+This project is a fork of the [`component-emitter`](https://github.com/sindresorhus/component-emitter) project, with [Socket.IO](https://socket.io/)-specific TypeScript typings.
+
+## Installation
+
+```
+$ npm i @socket.io/component-emitter
+```
+
+## API
+
+### Emitter(obj)
+
+ The `Emitter` may also be used as a mixin. For example
+ a "plain" object may become an emitter, or you may
+ extend an existing prototype.
+
+ As an `Emitter` instance:
+
+```js
+import { Emitter } from '@socket.io/component-emitter';
+
+var emitter = new Emitter;
+emitter.emit('something');
+```
+
+ As a mixin:
+
+```js
+import { Emitter } from '@socket.io/component-emitter';
+
+var user = { name: 'tobi' };
+Emitter(user);
+
+user.emit('im a user');
+```
+
+ As a prototype mixin:
+
+```js
+import { Emitter } from '@socket.io/component-emitter';
+
+Emitter(User.prototype);
+```
+
+### Emitter#on(event, fn)
+
+ Register an `event` handler `fn`.
+
+### Emitter#once(event, fn)
+
+ Register a single-shot `event` handler `fn`,
+ removed immediately after it is invoked the
+ first time.
+
+### Emitter#off(event, fn)
+
+ * Pass `event` and `fn` to remove a listener.
+ * Pass `event` to remove all listeners on that event.
+ * Pass nothing to remove all listeners on all events.
+
+### Emitter#emit(event, ...)
+
+ Emit an `event` with variable option args.
+
+### Emitter#listeners(event)
+
+ Return an array of callbacks, or an empty array.
+
+### Emitter#hasListeners(event)
+
+ Check if this emitter has `event` handlers.
+
+## License
+
+MIT
diff --git a/server/node_modules/@socket.io/component-emitter/lib/cjs/index.d.ts b/server/node_modules/@socket.io/component-emitter/lib/cjs/index.d.ts
new file mode 100644
index 000000000..49a74e142
--- /dev/null
+++ b/server/node_modules/@socket.io/component-emitter/lib/cjs/index.d.ts
@@ -0,0 +1,179 @@
+/**
+ * An events map is an interface that maps event names to their value, which
+ * represents the type of the `on` listener.
+ */
+export interface EventsMap {
+ [event: string]: any;
+}
+
+/**
+ * The default events map, used if no EventsMap is given. Using this EventsMap
+ * is equivalent to accepting all event names, and any data.
+ */
+export interface DefaultEventsMap {
+ [event: string]: (...args: any[]) => void;
+}
+
+/**
+ * Returns a union type containing all the keys of an event map.
+ */
+export type EventNames