;
+}
+
+export function withDebugData(
+ WrappedComponent: React.ComponentType
+) {
+ return React.forwardRef((props, ref) => {
+ const debugDataRef = useRef({});
+ const { debugRef, ...componentProps } = props;
+
+ // Create a wrapped component that captures debug data
+ const ComponentWithDebugCapture = (innerProps: P) => {
+ // Capture props for debug
+ debugDataRef.current.props = innerProps;
+
+ return ;
+ };
+
+ // Expose debug data getter
+ useEffect(() => {
+ if (debugRef) {
+ debugRef.current = {
+ getDebugData: () => debugDataRef.current
+ };
+ }
+ }, [debugRef]);
+
+ return ;
+ });
+}
+
+// Helper function to collect debug data from components
+export const collectDebugData = (component: React.ReactNode, componentName: string, fileName: string): any => {
+ // Extract props from component if it's a React element
+ if (React.isValidElement(component)) {
+ const props = component.props || {};
+
+ // Filter out sensitive or large data
+ const { children, ...safeProps } = props;
+
+ return {
+ componentName,
+ fileName,
+ props: safeProps,
+ config: props.config || {},
+ settings: props.widgetSettings || {}
+ };
+ }
+
+ return {
+ componentName,
+ fileName
+ };
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DashboardWidgetDrawer.tsx b/client/src/pages/Dashboard/Components/DashboardWidgetDrawer.tsx
new file mode 100644
index 000000000..850838352
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DashboardWidgetDrawer.tsx
@@ -0,0 +1,318 @@
+import React, { useState, useMemo } from 'react';
+import { Drawer, Tabs, Card, Row, Col, Typography, Button, Space, Input } from 'antd';
+import { PlusOutlined, BarChartOutlined, SettingOutlined, AppstoreOutlined, SearchOutlined } from '@ant-design/icons';
+
+const { Title, Text } = Typography;
+
+interface DashboardWidgetDrawerProps {
+ open: boolean;
+ onClose: () => void;
+ onWidgetAdd: (widgetKey: string, category: string) => void;
+}
+
+interface WidgetItem {
+ key: string;
+ title: string;
+ description: string;
+ icon: React.ReactNode;
+ preview?: string;
+}
+
+const DashboardWidgetDrawer: React.FC = ({
+ open,
+ onClose,
+ onWidgetAdd,
+}) => {
+ const [activeTab, setActiveTab] = useState('monitoring');
+ const [searchText, setSearchText] = useState('');
+
+ const widgetCategories = {
+ monitoring: {
+ title: 'Monitoring & Analytics',
+ icon: ,
+ description: 'Real-time metrics and performance data',
+ widgets: [
+ {
+ key: 'SystemPerformanceCard',
+ title: 'System Performance',
+ description: 'CPU/Memory monitoring with trend analysis',
+ icon: ,
+ preview: 'Real-time performance metrics'
+ },
+ {
+ key: 'AvailabilityCard',
+ title: 'System Availability',
+ description: 'Uptime tracking with monthly comparisons',
+ icon: ,
+ preview: 'Availability percentage display'
+ },
+ {
+ key: 'ContainersCard',
+ title: 'Containers Status',
+ description: 'Container metrics and usage statistics',
+ icon: ,
+ preview: 'Running containers overview'
+ },
+ {
+ key: 'CombinedPowerCard',
+ title: 'Combined Power',
+ description: 'Aggregated device CPU/Memory resources',
+ icon: ,
+ preview: 'Resource distribution chart'
+ },
+ {
+ key: 'MainChartCard',
+ title: 'Historical Analytics',
+ description: 'Time-series charts with device filtering',
+ icon: ,
+ preview: 'Advanced analytics dashboard'
+ }
+ ] as WidgetItem[]
+ },
+ operations: {
+ title: 'Operations & Management',
+ icon: ,
+ description: 'System management and operational tasks',
+ widgets: [
+ {
+ key: 'AnsiblePlaybookRunner',
+ title: 'Ansible Playbook Runner',
+ description: 'Execute playbooks with progress tracking',
+ icon: ,
+ preview: 'Automated task execution'
+ },
+ {
+ key: 'ContainerUpdateCenter',
+ title: 'Container Update Center',
+ description: 'Manage container updates and security alerts',
+ icon: ,
+ preview: 'Container lifecycle management'
+ },
+ {
+ key: 'MaintenanceCalendar',
+ title: 'Maintenance Calendar',
+ description: 'Schedule and track maintenance tasks',
+ icon: ,
+ preview: 'Calendar-based task planning'
+ },
+ {
+ key: 'QuickActionsWidget',
+ title: 'Quick Actions',
+ description: 'Customizable action buttons for common tasks',
+ icon: ,
+ preview: 'One-click operations panel'
+ }
+ ] as WidgetItem[]
+ },
+ productivity: {
+ title: 'Productivity & Integration',
+ icon: ,
+ description: 'Notes, feeds, and external tool integration',
+ widgets: [
+ {
+ key: 'NotebookWidget',
+ title: 'Notebook/Notes',
+ description: 'Markdown-based note-taking and documentation',
+ icon: ,
+ preview: 'Rich text documentation'
+ },
+ {
+ key: 'RSSFeedWidget',
+ title: 'RSS/News Feed',
+ description: 'Aggregate news feeds from multiple sources',
+ icon: ,
+ preview: 'Latest industry updates'
+ },
+ {
+ key: 'IFrameWidget',
+ title: 'iFrame Embed',
+ description: 'Embed external monitoring tools and dashboards',
+ icon: ,
+ preview: 'External tool integration'
+ }
+ ] as WidgetItem[]
+ }
+ };
+
+ // Filter widgets based on search text
+ const getFilteredWidgets = (widgets: WidgetItem[]) => {
+ if (!searchText.trim()) {
+ return widgets;
+ }
+
+ const searchLower = searchText.toLowerCase();
+ return widgets.filter(widget =>
+ widget.title.toLowerCase().includes(searchLower) ||
+ widget.description.toLowerCase().includes(searchLower) ||
+ (widget.preview && widget.preview.toLowerCase().includes(searchLower))
+ );
+ };
+
+ const handleWidgetAdd = (widgetKey: string) => {
+ onWidgetAdd(widgetKey, activeTab);
+ setSearchText(''); // Clear search on widget add
+ onClose();
+ };
+
+ const handleClose = () => {
+ setSearchText(''); // Clear search on drawer close
+ onClose();
+ };
+
+ const renderWidgetCard = (widget: WidgetItem) => (
+
+
+
+
+
+ {widget.icon}
+
+
+ {widget.title}
+
+
+
+ {widget.description}
+
+
+ {widget.preview}
+
+
+ }
+ onClick={() => handleWidgetAdd(widget.key)}
+ style={{ alignSelf: 'flex-end', marginTop: '8px' }}
+ >
+ Add Widget
+
+
+
+ );
+
+ // Calculate total matches across all categories
+ const totalMatches = useMemo(() => {
+ if (!searchText.trim()) return null;
+
+ return Object.values(widgetCategories).reduce((total, category) => {
+ return total + getFilteredWidgets(category.widgets).length;
+ }, 0);
+ }, [searchText]);
+
+ const tabItems = Object.entries(widgetCategories).map(([key, category]) => {
+ const filteredWidgets = getFilteredWidgets(category.widgets);
+
+ return {
+ key,
+ label: (
+
+ {category.icon}
+ {category.title}
+ {searchText && (
+
+ ({filteredWidgets.length})
+
+ )}
+
+ ),
+ children: (
+
+
+
+ {category.title}
+
+
+ {category.description}
+
+
+
+ {filteredWidgets.length > 0 ? (
+ filteredWidgets.map(renderWidgetCard)
+ ) : (
+
+
+
+ No widgets found matching "{searchText}"
+
+
+
+ )}
+
+
+ )
+ };
+ });
+
+ return (
+
+
+ Add Dashboard Widget
+
+ }
+ placement="right"
+ width={720}
+ onClose={handleClose}
+ open={open}
+ styles={{
+ body: { backgroundColor: '#0a0a0a', padding: 0 },
+ header: { backgroundColor: '#1a1a1a', borderBottom: '1px solid #333' }
+ }}
+ >
+
+
}
+ placeholder="Search widgets by name or description..."
+ value={searchText}
+ onChange={(e) => setSearchText(e.target.value)}
+ style={{
+ backgroundColor: '#1a1a1a',
+ borderColor: '#333'
+ }}
+ allowClear
+ size="large"
+ />
+ {searchText && totalMatches !== null && (
+
+
+ Found {totalMatches} widget{totalMatches !== 1 ? 's' : ''} matching "{searchText}"
+
+
+ )}
+ {!searchText && (
+
+ )}
+
+
+
+ );
+};
+
+export default DashboardWidgetDrawer;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DashboardWidgets.tsx b/client/src/pages/Dashboard/Components/DashboardWidgets.tsx
new file mode 100644
index 000000000..7e42dc907
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DashboardWidgets.tsx
@@ -0,0 +1,149 @@
+import React, { useState, useCallback } from 'react';
+import { Col, Row, Button, Space, Typography } from 'antd';
+import { PlusOutlined } from '@ant-design/icons';
+import { motion } from 'framer-motion';
+import NotebookWidget from './NotebookWidget';
+import RSSFeedWidget from './RSSFeedWidget';
+import IFrameWidget from './IFrameWidget';
+import QuickActionsWidget from './QuickActionsWidget';
+import AnsiblePlaybookRunner from './AnsiblePlaybookRunner';
+import ContainerUpdateCenter from './ContainerUpdateCenter';
+import MaintenanceCalendar from './MaintenanceCalendar';
+import SystemPerformanceCard from './SystemPerformanceCard';
+import AvailabilityCard from './AvailabilityCard';
+import ContainersCard from './ContainersCard';
+import CombinedPowerCard from './CombinedPowerCard';
+import TimeSeriesLineChart from './TimeSeriesLineChart';
+import DashboardWidgetDrawer from './DashboardWidgetDrawer';
+
+const widgetColProps = {
+ xs: 24,
+ sm: 24,
+ md: 12,
+ lg: 12,
+ xl: 8,
+ style: { marginBottom: 24 },
+};
+
+const largeWidgetColProps = {
+ xs: 24,
+ sm: 24,
+ md: 24,
+ lg: 16,
+ xl: 12,
+ style: { marginBottom: 24 },
+};
+
+const cardVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: (i: number) => ({
+ opacity: 1,
+ y: 0,
+ transition: {
+ delay: i * 0.1,
+ duration: 0.4,
+ },
+ }),
+};
+
+const DashboardWidgets: React.FC = () => {
+ const [drawerOpen, setDrawerOpen] = useState(false);
+ const [activeWidgets, setActiveWidgets] = useState(() => {
+ const saved = localStorage.getItem('ssm-dashboard-active-widgets');
+ return saved ? JSON.parse(saved) : [
+ 'QuickActionsWidget',
+ 'AnsiblePlaybookRunner',
+ 'ContainerUpdateCenter',
+ 'NotebookWidget',
+ 'RSSFeedWidget',
+ 'IFrameWidget',
+ 'MaintenanceCalendar'
+ ];
+ });
+
+ const handleWidgetAdd = useCallback((widgetKey: string, category: string) => {
+ if (!activeWidgets.includes(widgetKey)) {
+ const newWidgets = [...activeWidgets, widgetKey];
+ setActiveWidgets(newWidgets);
+ localStorage.setItem('ssm-dashboard-active-widgets', JSON.stringify(newWidgets));
+ }
+ }, [activeWidgets]);
+
+ const getWidgetComponent = (widgetKey: string) => {
+ const components: Record = {
+ // Monitoring & Analytics
+ SystemPerformanceCard: ,
+ AvailabilityCard: ,
+ ContainersCard: ,
+ CombinedPowerCard: ,
+ MainChartCard: ,
+
+ // Operations & Management
+ QuickActionsWidget: ,
+ AnsiblePlaybookRunner: ,
+ ContainerUpdateCenter: ,
+ MaintenanceCalendar: ,
+
+ // Productivity & Integration
+ NotebookWidget: ,
+ RSSFeedWidget: ,
+ IFrameWidget: ,
+ };
+ return components[widgetKey] || null;
+ };
+
+ const getColProps = (widgetKey: string) => {
+ const largeWidgets = ['IFrameWidget', 'MaintenanceCalendar', 'MainChartCard'];
+ return largeWidgets.includes(widgetKey) ? largeWidgetColProps : widgetColProps;
+ };
+
+ return (
+ <>
+
+
+ Dashboard Widgets
+
+ }
+ onClick={() => setDrawerOpen(true)}
+ >
+ Add Widget
+
+
+
+
+ {activeWidgets.map((widgetKey, index) => {
+ const component = getWidgetComponent(widgetKey);
+ if (!component) return null;
+
+ return (
+
+
+ {component}
+
+
+ );
+ })}
+
+
+ setDrawerOpen(false)}
+ onWidgetAdd={handleWidgetAdd}
+ />
+ >
+ );
+};
+
+export default DashboardWidgets;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DebugOverlay.tsx b/client/src/pages/Dashboard/Components/DebugOverlay.tsx
new file mode 100644
index 000000000..ef799c63a
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DebugOverlay.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+
+interface DebugOverlayProps {
+ fileName: string;
+ componentName: string;
+}
+
+const DebugOverlay: React.FC = ({ fileName, componentName }) => {
+ if (process.env.NODE_ENV !== 'development') {
+ return null;
+ }
+
+ return (
+
+
{fileName}
+
{componentName}
+
+ );
+};
+
+export default DebugOverlay;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DebugPanel.tsx b/client/src/pages/Dashboard/Components/DebugPanel.tsx
new file mode 100644
index 000000000..9ebf4c596
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DebugPanel.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import { Collapse, Button, Typography, message } from 'antd';
+import { CopyOutlined } from '@ant-design/icons';
+
+interface DebugPanelProps {
+ data: any;
+ title?: string;
+ maxHeight?: number;
+ componentName?: string;
+}
+
+const DebugPanel: React.FC = ({
+ data,
+ title = 'Debug: Raw JSON Data',
+ maxHeight = 300,
+ componentName
+}) => {
+ const handleCopy = () => {
+ const debugData = JSON.stringify({
+ component: componentName,
+ ...data
+ }, null, 2);
+ navigator.clipboard.writeText(debugData);
+ message.success('Debug data copied to clipboard');
+ };
+
+ return (
+
+ {title}
+
+ ),
+ children: (
+
+
}
+ size="small"
+ style={{
+ position: 'absolute',
+ top: '8px',
+ right: '8px',
+ zIndex: 1
+ }}
+ onClick={handleCopy}
+ >
+ Copy
+
+
+
+
+ {JSON.stringify({
+ component: componentName,
+ ...data
+ }, null, 2)}
+
+
+
+
+ ),
+ },
+ ]}
+ />
+ );
+};
+
+export default DebugPanel;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DemoOverlay.tsx b/client/src/pages/Dashboard/Components/DemoOverlay.tsx
new file mode 100644
index 000000000..889bba8ce
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DemoOverlay.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { Typography } from 'antd';
+
+interface DemoOverlayProps {
+ show: boolean;
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
+ size?: 'small' | 'medium';
+}
+
+const DemoOverlay: React.FC = ({
+ show,
+ position = 'bottom-right',
+ size = 'small'
+}) => {
+ if (!show) return null;
+
+ const getPositionStyles = () => {
+ const baseStyles = {
+ position: 'absolute' as const,
+ zIndex: 10,
+ backgroundColor: 'rgba(255, 152, 0, 0.9)',
+ color: '#fff',
+ borderRadius: '4px',
+ fontWeight: 'bold',
+ fontSize: size === 'small' ? '10px' : '12px',
+ padding: size === 'small' ? '2px 6px' : '4px 8px',
+ textTransform: 'uppercase' as const,
+ letterSpacing: '0.5px',
+ boxShadow: '0 2px 4px rgba(0,0,0,0.2)',
+ pointerEvents: 'none' as const,
+ };
+
+ switch (position) {
+ case 'bottom-right':
+ return { ...baseStyles, bottom: '8px', right: '8px' };
+ case 'bottom-left':
+ return { ...baseStyles, bottom: '8px', left: '8px' };
+ case 'top-right':
+ return { ...baseStyles, top: '8px', right: '8px' };
+ case 'top-left':
+ return { ...baseStyles, top: '8px', left: '8px' };
+ default:
+ return { ...baseStyles, bottom: '8px', right: '8px' };
+ }
+ };
+
+ return (
+
+ DEMO
+
+ );
+};
+
+export default DemoOverlay;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DndProvider.tsx b/client/src/pages/Dashboard/Components/DndProvider.tsx
new file mode 100644
index 000000000..bf3ab47d5
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DndProvider.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { DndProvider as ReactDndProvider } from 'react-dnd';
+import { HTML5Backend } from 'react-dnd-html5-backend';
+
+interface DndProviderProps {
+ children: React.ReactNode;
+}
+
+// This is a wrapper component to ensure the DndProvider is only mounted once
+// and to handle any potential issues with module federation
+const DndProvider: React.FC = ({ children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default DndProvider;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/DonutChart.tsx b/client/src/pages/Dashboard/Components/DonutChart.tsx
new file mode 100644
index 000000000..3e5948ad8
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DonutChart.tsx
@@ -0,0 +1,745 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { Card, Typography, Space, Row, Col, Spin } from 'antd';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+import { getPaletteColors } from './utils/colorPalettes';
+import {
+ getDashboardDevicesStats,
+ getDashboardAveragedDevicesStats
+} from '@/services/rest/statistics/stastistics';
+import {
+ getContainerStat
+} from '@/services/rest/containers/container-statistics';
+import { entityCacheService } from '@/services/cache/entityCache.service';
+import moment from 'moment';
+import { useWidgetContext } from './DashboardLayoutEngine/WidgetContext';
+import { useRegisterDebugData } from './DashboardLayoutEngine/DebugDataProvider';
+import DemoOverlay from './DemoOverlay';
+
+interface LegendItem {
+ name: string;
+ value: string;
+ color: string;
+}
+
+interface DonutChartProps {
+ title: string;
+ dataType?: 'device' | 'container';
+ source?: string | string[];
+ metric?: string;
+ totalTours?: number;
+ mainLabel?: string;
+ chartData?: { type: string; value: number; color: string; originalValue?: number }[];
+ legendItems?: LegendItem[];
+ cardStyle?: React.CSSProperties;
+ isPreview?: boolean;
+ colorPalette?: string;
+ customColors?: string[];
+}
+
+const DonutChart: React.FC = ({
+ title,
+ dataType = 'device',
+ source = 'all',
+ metric = 'cpu_usage',
+ totalTours = 0,
+ mainLabel = 'Total',
+ chartData = [],
+ legendItems = [],
+ cardStyle,
+ isPreview = false,
+ colorPalette = 'default',
+ customColors,
+}) => {
+ const [loading, setLoading] = useState(false);
+ const [apiChartData, setApiChartData] = useState<{ type: string; value: number; color: string; originalValue?: number }[]>([]);
+ const [apiLegendItems, setApiLegendItems] = useState([]);
+ const [apiTotalValue, setApiTotalValue] = useState(0);
+ const [isUsingMockData, setIsUsingMockData] = useState(false);
+
+ // Get widget context and debug registration
+ const widgetContext = useWidgetContext();
+ const updateDebugData = useRegisterDebugData(widgetContext?.widgetId);
+
+ // Get colors from palette
+ const paletteColors = useMemo(() => {
+ return customColors && customColors.length > 0 ? customColors : getPaletteColors(colorPalette);
+ }, [colorPalette, customColors]);
+
+ // Determine if we're looking at all items or specific ones
+ const { isAllSelected, sourceIds } = useMemo(() => {
+ const isAll = Array.isArray(source) ? source.includes('all') : source === 'all';
+ let ids: string[] = [];
+
+ if (Array.isArray(source)) {
+ ids = source.filter(s => s && s !== 'all');
+ } else if (source && source !== 'all') {
+ ids = [source];
+ }
+
+ return { isAllSelected: isAll, sourceIds: ids };
+ }, [source]);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ if (loading) return; // Prevent multiple simultaneous fetches
+ setLoading(true);
+ try {
+ // Use provided data in preview mode or when static data is provided
+ if (isPreview || (chartData && chartData.length > 0)) {
+ setApiChartData(chartData);
+ setApiLegendItems(legendItems);
+ setApiTotalValue(totalTours);
+ setIsUsingMockData(isPreview || true); // Mark as mock data when using static props
+ setLoading(false);
+ return;
+ }
+
+ // Fetch data from API
+ const now = moment();
+ const weekAgo = moment().subtract(7, 'days');
+
+ // Reset mock data flag when fetching real data
+ setIsUsingMockData(false);
+
+ console.log('DonutChart date range:', {
+ from: weekAgo.toISOString(),
+ to: now.toISOString(),
+ metric,
+ dataType,
+ source
+ });
+
+ const deviceOrContainerMap = new Map();
+ const apiResponses: any = {};
+
+ if (dataType === 'device' && metric) {
+ if (isAllSelected) {
+ // Get all devices using cache
+ console.log('📊 DonutChart: Getting all devices');
+ const devices = await entityCacheService.getDevices();
+ apiResponses.devices = { data: devices };
+ const allDeviceIds = devices.map(device => device.uuid);
+
+ console.log('📊 DonutChart - Device IDs retrieved:', {
+ count: allDeviceIds.length,
+ deviceIds: allDeviceIds
+ });
+
+ if (allDeviceIds.length > 0) {
+ // Prepare API call parameters
+ const avgStatsParams = {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate(),
+ };
+ const historicalStatsParams = {
+ from: weekAgo.toDate(),
+ to: now.toDate(),
+ };
+
+ console.log('📊 DonutChart API Calls - Parameters:', {
+ avgStats: {
+ method: 'getDashboardAveragedDevicesStats',
+ deviceIds: allDeviceIds,
+ metric: metric,
+ params: avgStatsParams,
+ body: { devices: allDeviceIds }
+ },
+ historicalStats: {
+ method: 'getDashboardDevicesStats',
+ deviceIds: allDeviceIds,
+ metric: metric,
+ params: historicalStatsParams,
+ body: { devices: allDeviceIds }
+ }
+ });
+
+ // Get averaged stats for each device to get their current values
+ const [avgStats, historicalStats] = await Promise.all([
+ getDashboardAveragedDevicesStats(allDeviceIds, metric, avgStatsParams),
+ getDashboardDevicesStats(allDeviceIds, metric, historicalStatsParams),
+ ]);
+
+ apiResponses.avgStats = avgStats;
+ apiResponses.historicalStats = historicalStats;
+
+ console.log('📊 DonutChart API Responses:', {
+ avgStats: {
+ success: avgStats.success,
+ dataLength: avgStats.data?.length || 0,
+ data: avgStats.data
+ },
+ historicalStats: {
+ success: historicalStats.success,
+ dataLength: historicalStats.data?.length || 0,
+ data: historicalStats.data
+ }
+ });
+
+ // Process historical stats to get per-device values
+ if (historicalStats.data && Array.isArray(historicalStats.data)) {
+ console.log('DonutChart: Processing historical data', {
+ dataLength: historicalStats.data.length,
+ sample: historicalStats.data[0]
+ });
+
+ // Group by device and get latest value for each
+ const deviceLatestValues = new Map();
+
+ historicalStats.data.forEach((stat: any) => {
+ // The API returns 'name' field with device UUID
+ const deviceId = stat.name || stat.deviceUuid;
+ if (deviceId && typeof stat.value === 'number') {
+ const existingEntry = deviceLatestValues.get(deviceId);
+
+ if (!existingEntry || stat.date > existingEntry.date) {
+ deviceLatestValues.set(deviceId, {
+ value: stat.value,
+ date: stat.date
+ });
+ }
+ }
+ });
+
+ console.log('DonutChart: Device latest values', {
+ deviceCount: deviceLatestValues.size,
+ values: Array.from(deviceLatestValues.entries())
+ });
+
+ // Create chart data from latest values
+ const nameMap = await entityCacheService.getEntitiesByIds('device', Array.from(deviceLatestValues.keys()));
+
+ deviceLatestValues.forEach((entry, deviceId) => {
+ const deviceName = nameMap.get(deviceId) || deviceId;
+ deviceOrContainerMap.set(deviceId, {
+ name: deviceName,
+ value: entry.value
+ });
+ });
+ } else {
+ console.log('DonutChart: No historical data available');
+ }
+ }
+ } else {
+ // Fetch specific devices
+ console.log('📊 DonutChart - Fetching specific devices:', sourceIds);
+
+ // Get device details from cache
+ const devices = await entityCacheService.getDevices();
+ apiResponses.devices = { data: devices };
+
+ if (sourceIds.length > 0) {
+ // Use the same API calls as for "all" but with specific device IDs
+ const avgStatsParams = {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate(),
+ };
+ const historicalStatsParams = {
+ from: weekAgo.toDate(),
+ to: now.toDate(),
+ };
+
+ console.log('📊 DonutChart API Calls (Specific Devices) - Parameters:', {
+ deviceIds: sourceIds,
+ avgStats: {
+ method: 'getDashboardAveragedDevicesStats',
+ deviceIds: sourceIds,
+ metric: metric,
+ params: avgStatsParams,
+ body: { devices: sourceIds }
+ },
+ historicalStats: {
+ method: 'getDashboardDevicesStats',
+ deviceIds: sourceIds,
+ metric: metric,
+ params: historicalStatsParams,
+ body: { devices: sourceIds }
+ }
+ });
+
+ const [avgStats, historicalStats] = await Promise.all([
+ getDashboardAveragedDevicesStats(sourceIds, metric, avgStatsParams),
+ getDashboardDevicesStats(sourceIds, metric, historicalStatsParams),
+ ]);
+
+ apiResponses.avgStats = avgStats;
+ apiResponses.historicalStats = historicalStats;
+
+ console.log('📊 DonutChart API Responses (Specific Devices):', {
+ avgStats: {
+ success: avgStats.success,
+ dataLength: avgStats.data?.length || 0,
+ data: avgStats.data
+ },
+ historicalStats: {
+ success: historicalStats.success,
+ dataLength: historicalStats.data?.length || 0,
+ data: historicalStats.data
+ }
+ });
+
+ // Process historical stats to get per-device values
+ if (historicalStats.data && Array.isArray(historicalStats.data)) {
+ console.log('DonutChart: Processing historical data for specific devices', {
+ dataLength: historicalStats.data.length,
+ sample: historicalStats.data[0]
+ });
+
+ // Group by device and get latest value for each
+ const deviceLatestValues = new Map();
+
+ historicalStats.data.forEach((stat: any) => {
+ // The API returns 'name' field with device UUID
+ const deviceId = stat.name || stat.deviceUuid;
+ if (deviceId && typeof stat.value === 'number') {
+ const existingEntry = deviceLatestValues.get(deviceId);
+
+ if (!existingEntry || stat.date > existingEntry.date) {
+ deviceLatestValues.set(deviceId, {
+ value: stat.value,
+ date: stat.date
+ });
+ }
+ }
+ });
+
+ console.log('DonutChart: Device latest values', {
+ deviceCount: deviceLatestValues.size,
+ values: Array.from(deviceLatestValues.entries())
+ });
+
+ // Create chart data from latest values
+ const nameMap = await entityCacheService.getEntitiesByIds('device', Array.from(deviceLatestValues.keys()));
+
+ deviceLatestValues.forEach((entry, deviceId) => {
+ const deviceName = nameMap.get(deviceId) || deviceId;
+ deviceOrContainerMap.set(deviceId, {
+ name: deviceName,
+ value: entry.value
+ });
+ });
+ }
+ }
+ }
+ } else if (dataType === 'container' && metric) {
+ if (isAllSelected) {
+ // Get all containers from cache
+ const containers = await entityCacheService.getContainers();
+ apiResponses.containers = { data: containers };
+ const allContainerIds = containers.map(container => container.id);
+
+ // Limit to first 10 containers to prevent overload
+ const limitedContainerIds = allContainerIds.slice(0, 10);
+
+ // Get container names from cache
+ const containerNameMap = await entityCacheService.getEntitiesByIds('container', limitedContainerIds);
+
+ // Fetch current value for each container
+ for (const containerId of limitedContainerIds) {
+ const containerName = containerNameMap.get(containerId) || containerId;
+ const statsResponse = await getContainerStat(
+ containerId,
+ metric,
+ {
+ from: weekAgo.toISOString(),
+ to: now.toISOString()
+ }
+ );
+
+ if (statsResponse.success && statsResponse.data?.length > 0) {
+ const latestValue = statsResponse.data[statsResponse.data.length - 1].value;
+ deviceOrContainerMap.set(containerId, {
+ name: containerName,
+ value: latestValue
+ });
+ }
+ }
+ } else {
+ // Fetch specific containers
+ const containerNameMap = await entityCacheService.getEntitiesByIds('container', sourceIds);
+
+ for (const containerId of sourceIds) {
+ const containerName = containerNameMap.get(containerId) || containerId;
+ const statsResponse = await getContainerStat(
+ containerId,
+ metric,
+ {
+ from: weekAgo.toISOString(),
+ to: now.toISOString()
+ }
+ );
+
+ if (statsResponse.success && statsResponse.data?.length > 0) {
+ const latestValue = statsResponse.data[statsResponse.data.length - 1].value;
+ deviceOrContainerMap.set(containerId, {
+ name: containerName,
+ value: latestValue
+ });
+ }
+ }
+ }
+ }
+
+ // Convert map to chart data
+ const items = Array.from(deviceOrContainerMap.values());
+
+ let newChartData: { type: string; value: number; color: string; originalValue?: number }[] = [];
+ let newLegendItems: LegendItem[] = [];
+ let avgTotal = 0;
+
+ // If we have items, show them; otherwise show "No data"
+ if (items.length > 0) {
+ const total = items.reduce((sum, item) => sum + item.value, 0);
+ avgTotal = total / items.length; // Average percentage
+
+ // Special case: Single device should show as progress circle (actual percentage)
+ // Multiple devices should show as comparative donut (normalized proportions)
+ if (items.length === 1) {
+ // Single device: show actual percentage as progress circle
+ const singleItem = items[0];
+ const actualPercentage = singleItem.value;
+ const remainingPercentage = 100 - actualPercentage;
+
+ newChartData = [
+ {
+ type: singleItem.name || 'Used',
+ value: actualPercentage, // Actual percentage (e.g., 3%)
+ originalValue: actualPercentage,
+ color: paletteColors[0] || '#52c41a'
+ },
+ {
+ type: 'Available',
+ value: remainingPercentage, // Remaining percentage (e.g., 97%)
+ originalValue: remainingPercentage,
+ color: 'rgba(255,255,255,0.1)' // Semi-transparent background
+ }
+ ];
+
+ newLegendItems = [{
+ name: singleItem.name || 'Usage',
+ value: `${actualPercentage.toFixed(1)}%`,
+ color: paletteColors[0] || '#52c41a'
+ }];
+ } else {
+ // Multiple devices: normalize for comparative donut chart
+ const totalValue = items.reduce((sum, item) => sum + item.value, 0);
+
+ // If totalValue is 0, give equal segments to all devices
+ const normalizedItems = totalValue > 0
+ ? items.map(item => ({
+ ...item,
+ // Each device gets a proportion of 100 based on its relative value
+ normalizedValue: (item.value / totalValue) * 100,
+ originalValue: item.value
+ }))
+ : items.map(item => ({
+ ...item,
+ // Equal segments if all values are 0
+ normalizedValue: 100 / items.length,
+ originalValue: item.value
+ }));
+
+ newChartData = normalizedItems.map((item, index) => ({
+ type: item.name || `Device ${index + 1}`,
+ value: item.normalizedValue, // Normalized to sum to 100 for full circle
+ originalValue: item.originalValue, // Keep original percentage for display
+ color: paletteColors[index % paletteColors.length] || '#cccccc'
+ }));
+
+ newLegendItems = normalizedItems.map((item, index) => ({
+ name: item.name || `Device ${index + 1}`,
+ value: `${item.originalValue.toFixed(1)}%`, // Show original percentage in legend
+ color: paletteColors[index % paletteColors.length] || '#cccccc'
+ }));
+ }
+
+ setApiChartData(newChartData);
+ setApiLegendItems(newLegendItems);
+ setApiTotalValue(avgTotal);
+ } else {
+ // No data available
+ setApiChartData([]);
+ setApiLegendItems([]);
+ setApiTotalValue(0);
+ }
+
+ console.log('DonutChart final processed data:', {
+ itemCount: items.length,
+ items: items,
+ originalTotal: items.reduce((sum, item) => sum + item.value, 0),
+ normalizedTotal: newChartData.reduce((sum, item) => sum + item.value, 0),
+ chartData: newChartData
+ });
+
+ // Include API call parameters in raw data for debugging
+ const deviceIds = isAllSelected
+ ? (apiResponses.devices?.data?.map((d: any) => d.uuid) || [])
+ : sourceIds;
+
+ const debugData = {
+ apiCalls: {
+ source: source,
+ isAllSelected: isAllSelected,
+ sourceIds: sourceIds,
+ getAllDevices: {
+ method: 'GET',
+ endpoint: '/api/devices'
+ },
+ getDashboardDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/stats/${metric}`,
+ params: {
+ from: weekAgo.toDate(),
+ to: now.toDate()
+ },
+ body: {
+ devices: deviceIds
+ }
+ },
+ getDashboardAveragedDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/averaged/${metric}`,
+ params: {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate()
+ },
+ body: {
+ devices: deviceIds
+ }
+ }
+ },
+ responses: apiResponses
+ };
+
+ // Update debug data in the provider
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'DonutChart',
+ fileName: 'DonutChart.tsx',
+ rawApiData: debugData as Record,
+ processedData: {
+ chartData: newChartData,
+ legendItems: newLegendItems,
+ totalValue: avgTotal
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric
+ } as Record
+ });
+ }
+
+ } catch (error) {
+ console.error('Error fetching donut chart data:', error);
+ // Fallback to provided data if available
+ setApiChartData(chartData);
+ setApiLegendItems(legendItems);
+ setApiTotalValue(totalTours);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }, [dataType, source, metric, isAllSelected, sourceIds.join(','), isPreview, colorPalette, customColors?.join(',')]);
+
+ // Use API data if available, otherwise fall back to provided data
+ const displayChartData = apiChartData.length > 0 ? apiChartData : chartData;
+ const displayLegendItems = apiLegendItems.length > 0 ? apiLegendItems : legendItems;
+ const displayTotalValue = apiTotalValue > 0 ? apiTotalValue : totalTours;
+ // Prepare data for ApexCharts - use display data
+ const chartSeries = displayChartData?.map(item => item.value) || [];
+ const chartLabels = displayChartData?.map(item => item.type) || [];
+ const chartColors = displayChartData?.map(item => item.color) || [];
+
+ console.log('DonutChart render data:', {
+ displayChartData,
+ chartSeries,
+ chartLabels,
+ loading
+ });
+
+ const chartOptions: ApexOptions = {
+ chart: {
+ type: 'donut',
+ height: 120,
+ background: 'transparent',
+ animations: {
+ enabled: false
+ }
+ },
+ labels: chartLabels,
+ colors: chartColors,
+ legend: {
+ show: false
+ },
+ dataLabels: {
+ enabled: false
+ },
+ plotOptions: {
+ pie: {
+ donut: {
+ size: '65%',
+ labels: {
+ show: true,
+ total: {
+ show: true,
+ showAlways: true,
+ label: mainLabel,
+ fontSize: '12px',
+ fontWeight: 400,
+ color: '#8c8c8c',
+ formatter: function() {
+ return displayTotalValue.toFixed(1) + '%';
+ }
+ },
+ value: {
+ show: true,
+ fontSize: '20px',
+ fontWeight: 600,
+ color: '#ffffff',
+ offsetY: 8,
+ formatter: function() {
+ return '';
+ }
+ },
+ name: {
+ show: false
+ }
+ }
+ }
+ }
+ },
+ stroke: {
+ width: 0
+ },
+ tooltip: {
+ theme: 'dark',
+ style: {
+ fontSize: '12px'
+ },
+ y: {
+ formatter: function(_val: number) {
+ return _val.toFixed(1) + '%';
+ }
+ },
+ custom: function({ series, seriesIndex, w }) {
+ const label = w.globals.labels[seriesIndex];
+ // Use original value if available, otherwise use the normalized value
+ const originalValue = displayChartData[seriesIndex]?.originalValue;
+ const displayValue = originalValue !== undefined ? originalValue : series[seriesIndex];
+
+ // Hide tooltip for "Available" segment in single-device progress mode
+ if (label === 'Available') {
+ return '';
+ }
+
+ return `
+
+
${label}: ${displayValue.toFixed(1)}%
+
`;
+ }
+ },
+ states: {
+ hover: {
+ filter: {
+ type: 'lighten',
+ value: 0.1
+ }
+ },
+ active: {
+ filter: {
+ type: 'darken',
+ value: 0.1
+ }
+ }
+ }
+ };
+
+ return (
+
+
+
+
+
+ {title}
+
+
+
+ {loading ? (
+
+
+
+ ) : chartSeries.length > 0 && chartSeries.every(v => typeof v === 'number' && !isNaN(v)) ? (
+
+ ) : (
+
+ No data available
+
+ )}
+
+
+
+ {displayLegendItems?.map((item) => (
+
+
+
+
+
+ {item.name}
+
+
+
+
+
+ {item.value}
+
+
+
+ ))}
+
+
+
+ );
+};
+export default DonutChart;
diff --git a/client/src/pages/Dashboard/Components/DonutChartWithTable.tsx b/client/src/pages/Dashboard/Components/DonutChartWithTable.tsx
new file mode 100644
index 000000000..18df231b2
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/DonutChartWithTable.tsx
@@ -0,0 +1,155 @@
+import React from 'react';
+import { Card, Typography, Space } from 'antd';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+
+interface OSDownloadData {
+ type: string; // OS Name
+ value: number; // Count or percentage
+ color: string;
+}
+
+interface DonutChartWithTableProps {
+ title: string;
+ subtitle: string;
+ totalDownloadsLabel: string; // e.g. "Total"
+ totalDownloadsValue: string;
+ chartData: OSDownloadData[];
+ cardStyle?: React.CSSProperties;
+}
+
+const DonutChartWithTable: React.FC = ({
+ title,
+ subtitle,
+ totalDownloadsLabel,
+ totalDownloadsValue,
+ chartData,
+ cardStyle,
+}) => {
+ // Prepare data for ApexCharts
+ const chartSeries = chartData?.map(item => item.value) || [];
+ const chartLabels = chartData?.map(item => item.type) || [];
+ const chartColors = chartData?.map(item => item.color) || [];
+
+ const chartOptions: ApexOptions = {
+ chart: {
+ type: 'donut',
+ height: 230,
+ background: 'transparent',
+ animations: {
+ enabled: false
+ }
+ },
+ labels: chartLabels,
+ colors: chartColors,
+ legend: {
+ show: false
+ },
+ dataLabels: {
+ enabled: false
+ },
+ plotOptions: {
+ pie: {
+ donut: {
+ size: '75%',
+ labels: {
+ show: true,
+ total: {
+ show: true,
+ showAlways: true,
+ label: totalDownloadsLabel,
+ fontSize: '14px',
+ fontWeight: 400,
+ color: '#8c8c8c',
+ formatter: function() {
+ return totalDownloadsValue;
+ }
+ },
+ value: {
+ show: true,
+ fontSize: '28px',
+ fontWeight: 600,
+ color: '#f0f0f0',
+ offsetY: -12
+ },
+ name: {
+ show: false
+ }
+ }
+ }
+ }
+ },
+ stroke: {
+ width: 0
+ },
+ tooltip: {
+ theme: 'dark',
+ style: {
+ fontSize: '12px'
+ },
+ y: {
+ formatter: function(val: number) {
+ return val.toString();
+ }
+ },
+ custom: function({ series, seriesIndex, dataPointIndex, w }) {
+ const label = w.globals.labels[seriesIndex];
+ const value = series[seriesIndex];
+ return `
+ `;
+ }
+ },
+ states: {
+ hover: {
+ filter: {
+ type: 'lighten',
+ value: 0.1
+ }
+ },
+ active: {
+ filter: {
+ type: 'darken',
+ value: 0.1
+ }
+ }
+ }
+ };
+
+ return (
+
+
+ {' '}
+ {/* Adjusted spacing */}
+
+ {title}
+
+
+ {subtitle}
+
+
+
+ {/* Legend can be added here if needed, similar to DonutChart */}
+
+ );
+};
+
+export default DonutChartWithTable;
diff --git a/client/src/pages/Dashboard/Components/FeaturedAppCard.tsx b/client/src/pages/Dashboard/Components/FeaturedAppCard.tsx
new file mode 100644
index 000000000..a90b76f6d
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/FeaturedAppCard.tsx
@@ -0,0 +1,376 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import { Card, Typography, Tag, Tooltip } from 'antd';
+import { LeftOutlined, RightOutlined } from '@ant-design/icons';
+
+interface Tip {
+ tagText: string;
+ title: string;
+ description: string;
+ imageUrl?: string;
+ docLink?: string;
+}
+
+interface FeaturedAppCardProps {
+ tips?: Tip[];
+ // Keep backward compatibility
+ tagText?: string;
+ title?: string;
+ description?: string;
+ imageUrl?: string;
+ cardStyle?: React.CSSProperties;
+}
+
+const FeaturedAppCard: React.FC = ({
+ tips,
+ tagText,
+ title,
+ description,
+ imageUrl,
+ cardStyle,
+}) => {
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [selectedTips, setSelectedTips] = useState([]);
+ const [isTransitioning, setIsTransitioning] = useState(false);
+ const [isPaused, setIsPaused] = useState(false);
+
+ // For backward compatibility, create tips array from individual props if tips not provided
+ const allTips = tips || [{
+ tagText: tagText || 'FEATURED APP',
+ title: title || 'Default Title',
+ description: description || 'Default description',
+ imageUrl: imageUrl || '/assets/images/dashboard/featured-app-image.png',
+ }];
+
+ // Function to get a random background image (bck1 to bck5)
+ const getRandomBackground = () => {
+ const randomNum = Math.floor(Math.random() * 5) + 1;
+ return `/assets/images/dashboard/tips/bck${randomNum}.png`;
+ };
+
+ // Initialize with 3 random tips from the 100
+ useEffect(() => {
+ if (allTips.length > 3) {
+ // Get 3 random tips with random backgrounds
+ const shuffled = [...allTips].sort(() => 0.5 - Math.random());
+ const tipsWithRandomBg = shuffled.slice(0, 3).map(tip => ({
+ ...tip,
+ imageUrl: getRandomBackground()
+ }));
+ setSelectedTips(tipsWithRandomBg);
+ } else {
+ const tipsWithRandomBg = allTips.map(tip => ({
+ ...tip,
+ imageUrl: getRandomBackground()
+ }));
+ setSelectedTips(tipsWithRandomBg);
+ }
+ }, [allTips]);
+
+ const tipsData = selectedTips.length > 0 ? selectedTips : allTips;
+
+ const handlePrevious = useCallback(() => {
+ if (isTransitioning) return;
+ setIsTransitioning(true);
+ setCurrentIndex((prev) => (prev === 0 ? tipsData.length - 1 : prev - 1));
+ setTimeout(() => setIsTransitioning(false), 600);
+ }, [isTransitioning, tipsData.length]);
+
+ const handleNext = useCallback(() => {
+ if (isTransitioning) return;
+ setIsTransitioning(true);
+ setCurrentIndex((prev) => (prev === tipsData.length - 1 ? 0 : prev + 1));
+ setTimeout(() => setIsTransitioning(false), 600);
+ }, [isTransitioning, tipsData.length]);
+
+ const handleDotClick = useCallback((index: number) => {
+ if (isTransitioning || index === currentIndex) return;
+ setIsTransitioning(true);
+ setCurrentIndex(index);
+ setTimeout(() => setIsTransitioning(false), 600);
+ }, [isTransitioning, currentIndex]);
+
+ // Auto-play carousel effect similar to Next.js examples
+ useEffect(() => {
+ if (!isPaused && tipsData.length > 1) {
+ const interval = setInterval(() => {
+ handleNext();
+ }, 4000); // 4 seconds like the Next.js example
+
+ return () => clearInterval(interval);
+ }
+ }, [currentIndex, isPaused, tipsData.length, handleNext]);
+
+ const currentTip = tipsData[currentIndex];
+
+ const handleTipClick = () => {
+ if (currentTip?.docLink) {
+ const event = new CustomEvent('openDocumentation', {
+ detail: { link: currentTip.docLink },
+ });
+ window.dispatchEvent(event);
+ }
+ };
+
+ // Safety check - if no current tip, don't render anything
+ if (!currentTip) {
+ return (
+
+
+ Loading tips...
+
+
+ );
+ }
+
+ return (
+ {
+ e.currentTarget.style.transform = 'translateY(-4px)';
+ e.currentTarget.style.boxShadow = '0 12px 48px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.15)';
+ setIsPaused(true);
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.transform = 'translateY(0)';
+ e.currentTarget.style.boxShadow = '0 8px 32px rgba(0,0,0,0.2), 0 4px 16px rgba(0,0,0,0.1)';
+ setIsPaused(false);
+ }}
+ >
+ {/* Sliding Cards Container */}
+
+ {tipsData.map((tip, index) => (
+
+ {/* Background Image */}
+
+
+ {/* Gradient Overlay */}
+
+
+ {/* Content */}
+
{
+ if (tip?.docLink) {
+ const event = new CustomEvent('openDocumentation', {
+ detail: { link: tip.docLink },
+ });
+ window.dispatchEvent(event);
+ }
+ }}
+ >
+
+ {tip?.tagText?.toUpperCase() || 'TIP'}
+
+
+
+ {tip?.title || 'Tip Title'}
+
+
+
+
+ {tip?.description || 'No description available'}
+
+
+
+
+ ))}
+
+
+ {/* Navigation Controls - Only show if multiple tips */}
+ {tipsData.length > 1 && (
+
+ {/* Dots Indicator */}
+
+ {tipsData.map((_, index) => (
+
handleDotClick(index)}
+ />
+ ))}
+
+
+ {/* Chevron Navigation */}
+
+ {
+ e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.1)';
+ }}
+ >
+
+
+ {
+ e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
+ }}
+ onMouseLeave={(e) => {
+ e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.1)';
+ }}
+ >
+
+
+
+
+ )}
+
+ );
+};
+
+export default FeaturedAppCard;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/GroupedBarChart.tsx b/client/src/pages/Dashboard/Components/GroupedBarChart.tsx
new file mode 100644
index 000000000..354e9397f
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/GroupedBarChart.tsx
@@ -0,0 +1,524 @@
+import React, { useEffect, useState, useCallback, useMemo } from 'react';
+import { Card, Typography, Space, Select, Spin, Empty } from 'antd';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+import {
+ getDashboardDevicesStats,
+ getDashboardAveragedDevicesStats
+} from '@/services/rest/statistics/stastistics';
+import {
+ getContainerStats,
+ getAveragedStats
+} from '@/services/rest/containers/container-statistics';
+import { getAllDevices } from '@/services/rest/devices/devices';
+import { getContainers as getAllContainers } from '@/services/rest/containers/containers';
+import { getTimeDistance } from '@/utils/time';
+import { API, StatsType } from 'ssm-shared-lib';
+import moment from 'moment';
+
+// Static mapping outside component to avoid useCallback dependency issues
+const STATS_TYPE_MAPPING: Record
= {
+ cpu_usage: StatsType.DeviceStatsType.CPU,
+ memory_usage: StatsType.DeviceStatsType.MEM_USED,
+ memory_free: StatsType.DeviceStatsType.MEM_FREE,
+ storage_usage: StatsType.DeviceStatsType.DISK_USED,
+ storage_free: StatsType.DeviceStatsType.DISK_FREE,
+ containers: StatsType.DeviceStatsType.CONTAINERS,
+};
+
+interface VisitEntry {
+ month: string;
+ team: string;
+ visits: number;
+}
+
+interface GroupedBarChartProps {
+ title: string;
+ subtitle: string;
+ // Legacy static props (optional for backward compatibility)
+ chartData?: VisitEntry[];
+ categoryColors?: Record;
+ cardStyle?: React.CSSProperties;
+ // API-driven props
+ dataType?: 'device' | 'container';
+ source?: string | string[];
+ metrics?: string[];
+ dateRangePreset?: string;
+ customDateRange?: [moment.Moment, moment.Moment];
+ isPreview?: boolean;
+}
+
+const GroupedBarChart: React.FC = ({
+ title,
+ subtitle,
+ // Legacy props
+ chartData,
+ categoryColors,
+ cardStyle,
+ // API props
+ dataType = 'device',
+ source = 'all',
+ metrics = ['cpu_usage', 'memory_usage'],
+ dateRangePreset = 'last7days',
+ customDateRange,
+ isPreview = false,
+}) => {
+ const [loading, setLoading] = useState(false);
+ const [graphData, setGraphData] = useState([]);
+ const [currentPeriod, setCurrentPeriod] = useState(dateRangePreset);
+ const [rangePickerValue, setRangePickerValue] = useState(null);
+ const [deviceNameMap, setDeviceNameMap] = useState>({});
+ const [containerNameMap, setContainerNameMap] = useState>({});
+ const [rawApiData, setRawApiData] = useState([]);
+
+ // If legacy chartData is provided, use it instead of API
+ const isLegacyMode = chartData && chartData.length > 0;
+
+ // Convert date range preset to actual dates (stable function)
+ const getDateRangeFromPreset = useCallback((preset: string, customRange?: any) => {
+ if (preset === 'custom' && customRange) {
+ return customRange;
+ }
+
+ switch (preset) {
+ case 'last24hours':
+ return getTimeDistance('today');
+ case 'last7days':
+ return getTimeDistance('week');
+ case 'last30days':
+ return getTimeDistance('month');
+ case 'last3months':
+ return [moment().subtract(3, 'months'), moment()];
+ case 'last6months':
+ return [moment().subtract(6, 'months'), moment()];
+ case 'lastyear':
+ return getTimeDistance('year');
+ default:
+ return getTimeDistance('week');
+ }
+ }, []);
+
+ // Initialize date range only once
+ useEffect(() => {
+ if (!rangePickerValue && !isLegacyMode) {
+ const initialRange = getDateRangeFromPreset(dateRangePreset, customDateRange);
+ setRangePickerValue(initialRange);
+ }
+ }, [dateRangePreset, customDateRange, getDateRangeFromPreset, isLegacyMode]);
+
+
+ // Determine if we're looking at all items or specific ones (memoized)
+ const { isAllSelected, sourceIds } = useMemo(() => {
+ const isAll = Array.isArray(source) ? source.includes('all') : source === 'all';
+ let ids: string[] = [];
+
+ if (Array.isArray(source)) {
+ ids = source.filter(s => s && s !== 'all');
+ } else if (source && source !== 'all') {
+ ids = [source];
+ }
+
+ return { isAllSelected: isAll, sourceIds: ids };
+ }, [source]);
+
+ // Fetch data when dependencies change
+ useEffect(() => {
+ if (rangePickerValue && !isLegacyMode) {
+ if (isPreview) {
+ // Use mock data in preview mode
+ const mockData = Array.from({ length: 6 }, (_, i) => ({
+ name: `device-${i % 2 + 1}`,
+ value: Math.floor(Math.random() * 40) + 30,
+ date: moment().subtract(6 - i, 'months').format('YYYY-MM'),
+ }));
+ setGraphData(mockData);
+ setLoading(false);
+ } else {
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ if (dataType === 'device') {
+ let deviceIds = sourceIds;
+
+ // If "all" is selected, fetch all device IDs
+ if (isAllSelected) {
+ const devicesResponse = await getAllDevices();
+ const devices = devicesResponse.data || [];
+ deviceIds = devices.map(device => device.uuid);
+
+ // Build device name mapping
+ const nameMap: Record = {};
+ devices.forEach(device => {
+ nameMap[device.uuid] = device.fqdn || device.ip || device.uuid;
+ });
+ setDeviceNameMap(nameMap);
+ } else {
+ // For specific devices, fetch their details to get names
+ const devicesResponse = await getAllDevices();
+ const devices = devicesResponse.data || [];
+ const nameMap: Record = {};
+ devices.forEach(device => {
+ if (sourceIds.includes(device.uuid)) {
+ nameMap[device.uuid] = device.fqdn || device.ip || device.uuid;
+ }
+ });
+ setDeviceNameMap(nameMap);
+ }
+
+ if (deviceIds.length === 0) {
+ setGraphData([]);
+ return;
+ }
+
+ // Use the primary metric for the chart
+ const primaryMetric = metrics[0] || 'cpu_usage';
+ const statsType = STATS_TYPE_MAPPING[primaryMetric] || StatsType.DeviceStatsType.CPU;
+
+ // Fetch data using the same pattern as MainChartCard
+ const deviceStats = await getDashboardDevicesStats(
+ deviceIds as string[],
+ statsType,
+ {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate(),
+ }
+ );
+
+ // Store raw API data for debugging
+ setRawApiData(deviceStats.data || []);
+
+ // Process the data - keep the original structure
+ const processedData = deviceStats.data || [];
+ setGraphData(processedData);
+ }
+ } catch (error) {
+ console.error('Failed to fetch chart data:', error);
+ setGraphData([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }
+ }
+ }, [dataType, sourceIds, isAllSelected, metrics, rangePickerValue, isLegacyMode, isPreview]);
+
+ // Legacy data processing for static chartData
+ const legacyProcessedData = useMemo(() => {
+ if (!isLegacyMode || !chartData) return { categories: [], series: [] };
+
+ // Get unique months in order
+ const months = Array.from(new Set(chartData.map(item => item.month)));
+
+ // Get unique teams
+ const teams = Array.from(new Set(chartData.map(item => item.team)));
+
+ // Create series data for each team
+ const seriesData = teams.map(team => {
+ const data = months.map(month => {
+ const entry = chartData.find(item => item.month === month && item.team === team);
+ return entry ? entry.visits : 0;
+ });
+
+ return {
+ name: team,
+ data: data
+ };
+ });
+
+ return {
+ categories: months,
+ series: seriesData
+ };
+ }, [chartData, isLegacyMode]);
+
+ // API data processing - group by device and create monthly aggregations
+ const apiProcessedData = useMemo(() => {
+ if (isLegacyMode || !graphData || graphData.length === 0) return { categories: [], series: [] };
+
+ // Group data by device and aggregate by month
+ const dataByDevice: Record> = {};
+
+ graphData.forEach(stat => {
+ const deviceName = deviceNameMap[stat.name] || stat.name;
+
+ // Parse the custom date format and get month
+ let parsedDate;
+ if (typeof stat.date === 'string' && stat.date.includes('-') && stat.date.split('-').length > 3) {
+ const parts = stat.date.split('-');
+ if (parts.length >= 4) {
+ const dateStr = `${parts[0]}-${parts[1]}-${parts[2]}T${parts[3]}`;
+ parsedDate = moment(dateStr);
+ }
+ } else {
+ parsedDate = moment(stat.date);
+ }
+
+ if (!parsedDate.isValid()) return;
+
+ const monthKey = parsedDate.format('MMM YYYY');
+
+ if (!dataByDevice[deviceName]) {
+ dataByDevice[deviceName] = {};
+ }
+
+ if (!dataByDevice[deviceName][monthKey]) {
+ dataByDevice[deviceName][monthKey] = [];
+ }
+
+ dataByDevice[deviceName][monthKey].push(stat.value || 0);
+ });
+
+ // Get all unique months and sort them
+ const allMonths = new Set();
+ Object.values(dataByDevice).forEach(deviceData => {
+ Object.keys(deviceData).forEach(month => allMonths.add(month));
+ });
+
+ const sortedMonths = Array.from(allMonths).sort((a, b) =>
+ moment(a, 'MMM YYYY').valueOf() - moment(b, 'MMM YYYY').valueOf()
+ );
+
+ // Create series for ApexCharts - average values per month per device
+ const series = Object.entries(dataByDevice).slice(0, 2).map(([deviceName, monthData]) => {
+ const data = sortedMonths.map(month => {
+ const values = monthData[month] || [];
+ return values.length > 0
+ ? values.reduce((sum, val) => sum + val, 0) / values.length
+ : 0;
+ });
+
+ return {
+ name: deviceName,
+ data: data
+ };
+ });
+
+ return {
+ categories: sortedMonths,
+ series: series
+ };
+ }, [graphData, deviceNameMap, isLegacyMode]);
+
+ // Use the appropriate processed data
+ const { categories, series } = isLegacyMode ? legacyProcessedData : apiProcessedData;
+
+ // Handle period changes
+ const handlePeriodChange = useCallback((newPeriod: string) => {
+ setCurrentPeriod(newPeriod);
+ const newRange = getDateRangeFromPreset(newPeriod, customDateRange);
+ setRangePickerValue(newRange);
+ }, [customDateRange, getDateRangeFromPreset]);
+
+ // Period options (stable)
+ const periodOptions = useMemo(() => [
+ { label: 'Last 24 Hours', value: 'last24hours' },
+ { label: 'Last 7 Days', value: 'last7days' },
+ { label: 'Last 30 Days', value: 'last30days' },
+ { label: 'Last 3 Months', value: 'last3months' },
+ { label: 'Last 6 Months', value: 'last6months' },
+ ], []);
+
+ const getMetricLabel = useCallback((metric: string): string => {
+ const labels: Record = {
+ cpu_usage: 'CPU Usage',
+ memory_usage: 'Memory Usage',
+ memory_free: 'Memory Free',
+ storage_usage: 'Storage Usage',
+ storage_free: 'Storage Free',
+ containers: 'Containers',
+ };
+ return labels[metric] || metric;
+ }, []);
+
+ const chartOptions: ApexOptions = {
+ chart: {
+ type: 'bar',
+ height: 300,
+ toolbar: {
+ show: false
+ },
+ background: 'transparent',
+ animations: {
+ enabled: false
+ }
+ },
+ plotOptions: {
+ bar: {
+ horizontal: false,
+ columnWidth: '60%',
+ borderRadius: 4,
+ borderRadiusApplication: 'end',
+ dataLabels: {
+ position: 'top'
+ }
+ }
+ },
+ colors: ['#52c41a', '#faad14'],
+ dataLabels: {
+ enabled: false
+ },
+ stroke: {
+ show: true,
+ width: 2,
+ colors: ['transparent']
+ },
+ xaxis: {
+ categories: categories,
+ labels: {
+ style: {
+ colors: '#8c8c8c',
+ fontSize: '12px'
+ }
+ },
+ axisBorder: {
+ color: '#3a3a3e'
+ },
+ axisTicks: {
+ color: '#3a3a3e'
+ }
+ },
+ yaxis: {
+ max: isLegacyMode ? 80 : (metrics.includes('containers') ? undefined : 100),
+ tickAmount: 4,
+ labels: {
+ style: {
+ colors: '#8c8c8c',
+ fontSize: '11px'
+ },
+ formatter: (value) => {
+ if (isLegacyMode) {
+ return value.toString();
+ }
+ if (metrics.includes('containers')) {
+ return Math.round(value).toString();
+ }
+ return `${value}%`;
+ }
+ }
+ },
+ grid: {
+ borderColor: '#3a3a3e',
+ strokeDashArray: 4,
+ yaxis: {
+ lines: {
+ show: true,
+ opacity: 0.3
+ }
+ },
+ xaxis: {
+ lines: {
+ show: false
+ }
+ }
+ },
+ legend: {
+ show: false
+ },
+ tooltip: {
+ theme: 'dark',
+ shared: true,
+ intersect: false,
+ y: {
+ formatter: function(val: number) {
+ if (isLegacyMode) {
+ return val + ' visits';
+ }
+ if (metrics.includes('containers')) {
+ return Math.round(val).toLocaleString();
+ }
+ return `${val.toFixed(2)}%`;
+ }
+ }
+ },
+ fill: {
+ opacity: 1
+ }
+ };
+
+ return (
+
+ {/* Header with title and period selector */}
+
+
+
+ {title}
+
+
+ {isLegacyMode ? subtitle : metrics.map(m => getMetricLabel(m)).join(' vs ')}
+
+
+ {!isLegacyMode && dateRangePreset !== 'custom' && (
+
+ )}
+
+
+ {/* Legend */}
+
+ {series.map((seriesItem, index) => (
+
+
+
+ {isLegacyMode ? seriesItem.name : seriesItem.name}
+
+
+ ))}
+
+
+
+ {loading ? (
+
+
+
+ ) : series.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default GroupedBarChart;
diff --git a/client/src/pages/Dashboard/Components/IFrameWidget.tsx b/client/src/pages/Dashboard/Components/IFrameWidget.tsx
new file mode 100644
index 000000000..c99b119c8
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/IFrameWidget.tsx
@@ -0,0 +1,308 @@
+import React, { useState, useRef } from 'react';
+import { Card, Typography, Button, Input, message, Spin, Alert } from 'antd';
+import { ReloadOutlined, ExpandOutlined, SettingOutlined, LinkOutlined } from '@ant-design/icons';
+
+interface IFrameWidgetProps {
+ title?: string;
+ defaultUrl?: string;
+ allowedDomains?: string[];
+ cardStyle?: React.CSSProperties;
+}
+
+const IFrameWidget: React.FC = ({
+ title = 'External Service',
+ defaultUrl = '',
+ allowedDomains = [],
+ cardStyle,
+}) => {
+ const [url, setUrl] = useState(() => {
+ const savedUrl = localStorage.getItem(`ssm-iframe-url-${title}`);
+ return savedUrl || defaultUrl;
+ });
+ const [inputUrl, setInputUrl] = useState(url);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [showSettings, setShowSettings] = useState(false);
+ const iframeRef = useRef(null);
+
+ // Common monitoring tools and documentation URLs
+ const quickUrls = [
+ { name: 'Grafana', url: 'http://localhost:3000' },
+ { name: 'Prometheus', url: 'http://localhost:9090' },
+ { name: 'Portainer', url: 'http://localhost:9000' },
+ { name: 'Netdata', url: 'http://localhost:19999' },
+ { name: 'Uptime Kuma', url: 'http://localhost:3001' },
+ { name: 'SSM Docs', url: 'https://squirrelserversmanager.io/docs/' },
+ ];
+
+ const isValidUrl = (urlString: string) => {
+ try {
+ const urlObj = new URL(urlString);
+
+ // Check if domain is allowed (if allowedDomains is specified)
+ if (allowedDomains.length > 0) {
+ const hostname = urlObj.hostname;
+ const isAllowed = allowedDomains.some(domain =>
+ hostname === domain || hostname.endsWith(`.${domain}`) || domain === '*'
+ );
+ if (!isAllowed) {
+ setError(`Domain ${hostname} is not in the allowed domains list`);
+ return false;
+ }
+ }
+
+ // Only allow http and https protocols
+ if (!['http:', 'https:'].includes(urlObj.protocol)) {
+ setError('Only HTTP and HTTPS URLs are allowed');
+ return false;
+ }
+
+ return true;
+ } catch {
+ setError('Invalid URL format');
+ return false;
+ }
+ };
+
+ const loadUrl = (newUrl: string) => {
+ if (!newUrl.trim()) {
+ setError('Please enter a URL');
+ return;
+ }
+
+ setError(null);
+
+ if (!isValidUrl(newUrl)) {
+ return;
+ }
+
+ setLoading(true);
+ setUrl(newUrl);
+ setInputUrl(newUrl);
+
+ // Save to localStorage
+ localStorage.setItem(`ssm-iframe-url-${title}`, newUrl);
+
+ // Reset loading state after iframe loads
+ setTimeout(() => setLoading(false), 2000);
+
+ message.success('URL loaded');
+ };
+
+ const reloadIframe = () => {
+ if (iframeRef.current) {
+ setLoading(true);
+ iframeRef.current.src = iframeRef.current.src;
+ setTimeout(() => setLoading(false), 2000);
+ }
+ };
+
+ const openInNewTab = () => {
+ if (url) {
+ window.open(url, '_blank');
+ }
+ };
+
+ const handleIframeLoad = () => {
+ setLoading(false);
+ setError(null);
+ };
+
+ const handleIframeError = () => {
+ setLoading(false);
+ setError('Failed to load the page. Check if the URL is accessible and allows embedding.');
+ };
+
+ return (
+
+
+
+ {title}
+
+
+ }
+ size="small"
+ onClick={() => setShowSettings(!showSettings)}
+ style={{ color: '#8c8c8c' }}
+ />
+ }
+ size="small"
+ onClick={reloadIframe}
+ disabled={!url}
+ style={{ color: '#8c8c8c' }}
+ />
+ }
+ size="small"
+ onClick={openInNewTab}
+ disabled={!url}
+ style={{ color: '#8c8c8c' }}
+ />
+
+
+
+ {showSettings && (
+
+
+ Enter URL to embed
+
+
+
+ setInputUrl(e.target.value)}
+ onPressEnter={() => loadUrl(inputUrl)}
+ style={{
+ backgroundColor: '#1a1a1a',
+ borderColor: '#3a3a3a',
+ color: 'white',
+ flex: 1
+ }}
+ size="small"
+ />
+ loadUrl(inputUrl)}
+ size="small"
+ style={{ backgroundColor: '#4ecb71', borderColor: '#4ecb71' }}
+ >
+ Load
+
+
+
+
+ Quick access:
+
+
+ {quickUrls.map((quickUrl) => (
+ {
+ setInputUrl(quickUrl.url);
+ loadUrl(quickUrl.url);
+ }}
+ style={{
+ color: '#4ecb71',
+ fontSize: '11px',
+ height: '24px',
+ padding: '0 8px',
+ }}
+ >
+ {quickUrl.name}
+
+ ))}
+
+
+ {allowedDomains.length > 0 && (
+
+
+ Allowed domains: {allowedDomains.join(', ')}
+
+
+ )}
+
+ )}
+
+ {error && (
+ setError(null)}
+ style={{ marginBottom: '16px' }}
+ />
+ )}
+
+
+ {loading && (
+
+
+
+ )}
+
+ {url ? (
+
+ ) : (
+
+
+
+ No URL configured
+
+
+ Click the settings icon to add a URL
+
+
+ )}
+
+
+ {url && (
+
+ {url}
+
+ )}
+
+ );
+};
+
+export default IFrameWidget;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/LineChart.css b/client/src/pages/Dashboard/Components/LineChart.css
new file mode 100644
index 000000000..879fecc50
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/LineChart.css
@@ -0,0 +1,48 @@
+/* Line chart container - proper width constraints */
+.line-chart-container {
+ position: relative;
+ width: 100% !important;
+ max-width: 100% !important;
+ min-width: 100% !important;
+ overflow: hidden;
+ display: block;
+}
+
+/* Ensure ApexCharts respects container width */
+.line-chart-container .apexcharts-canvas {
+ width: 100% !important;
+ max-width: 100% !important;
+ min-width: 100% !important;
+}
+
+/* Force SVG to fill container */
+.line-chart-container svg {
+ width: 100% !important;
+ max-width: 100% !important;
+ display: block;
+}
+
+/* Ensure the chart wrapper also respects width */
+.line-chart-container > div {
+ width: 100% !important;
+ max-width: 100% !important;
+}
+
+/* Add gradient shadow effect below lines */
+.line-chart-container .apexcharts-line-series path {
+ filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.15)) drop-shadow(0 4px 8px rgba(0, 0, 0, 0.1));
+}
+
+/* Enhanced shadow for better visibility */
+.line-chart-container .apexcharts-series[seriesName] path {
+ filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.2));
+}
+
+/* Specific shadow for green and orange lines */
+.line-chart-container .apexcharts-series path[stroke*="#52c41a"] {
+ filter: drop-shadow(0 8px 16px rgba(82, 196, 26, 0.3));
+}
+
+.line-chart-container .apexcharts-series path[stroke*="#faad14"] {
+ filter: drop-shadow(0 8px 16px rgba(250, 173, 20, 0.3));
+}
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/LineChart.tsx b/client/src/pages/Dashboard/Components/LineChart.tsx
new file mode 100644
index 000000000..cdfb39094
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/LineChart.tsx
@@ -0,0 +1,804 @@
+import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
+import { Card, Typography, Space, Select, Spin, Empty } from 'antd';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+import {
+ getDashboardDevicesStats,
+ getDashboardAveragedDevicesStats
+} from '@/services/rest/statistics/stastistics';
+import {
+ getContainerStats,
+ getAveragedStats
+} from '@/services/rest/containers/container-statistics';
+import { getAllDevices } from '@/services/rest/devices/devices';
+import { getContainers as getAllContainers } from '@/services/rest/containers/containers';
+import { getTimeDistance } from '@/utils/time';
+import { LoadingOutlined } from '@ant-design/icons';
+import { API, StatsType } from 'ssm-shared-lib';
+import moment from 'moment';
+import './LineChart.css';
+import { useWidgetContext } from './DashboardLayoutEngine/WidgetContext';
+import { useRegisterDebugData } from './DashboardLayoutEngine/DebugDataProvider';
+import DemoOverlay from './DemoOverlay';
+
+// Static mapping outside component to avoid useCallback dependency issues
+const STATS_TYPE_MAPPING: Record = {
+ cpu_usage: StatsType.DeviceStatsType.CPU,
+ memory_usage: StatsType.DeviceStatsType.MEM_USED,
+ memory_free: StatsType.DeviceStatsType.MEM_FREE,
+ storage_usage: StatsType.DeviceStatsType.DISK_USED,
+ storage_free: StatsType.DeviceStatsType.DISK_FREE,
+ containers: StatsType.DeviceStatsType.CONTAINERS,
+};
+
+interface LineChartProps {
+ title: string;
+ cardStyle?: React.CSSProperties;
+ // API-driven props
+ dataType?: 'device' | 'container';
+ source?: string | string[];
+ metrics?: string[];
+ dateRangePreset?: string;
+ customDateRange?: [moment.Moment, moment.Moment];
+ isPreview?: boolean; // Add flag to prevent data fetching in preview mode
+ colorPalette?: string;
+ customColors?: string[];
+}
+
+const LineChart: React.FC = ({
+ title,
+ cardStyle,
+ // API props
+ dataType = 'device',
+ source = 'all',
+ metrics = ['cpu_usage', 'memory_usage'],
+ dateRangePreset = 'last7days',
+ customDateRange,
+ isPreview = false,
+ colorPalette = 'default',
+ customColors = [],
+}) => {
+ console.log('🎨 LineChart received props:', { colorPalette, customColors });
+ const [loading, setLoading] = useState(false);
+ const [graphData, setGraphData] = useState([]);
+ const [currentPeriod, setCurrentPeriod] = useState(dateRangePreset);
+ const [rangePickerValue, setRangePickerValue] = useState(null);
+ const [deviceNameMap, setDeviceNameMap] = useState>({});
+ const [containerNameMap, setContainerNameMap] = useState>({});
+ const [chartDimensions, setChartDimensions] = useState({ width: '100%', height: 300 });
+ const [forceRerender, setForceRerender] = useState(0);
+ const [isUsingMockData, setIsUsingMockData] = useState(false);
+ const chartContainerRef = useRef(null);
+ const chartRef = useRef(null);
+
+ // Get widget context and debug registration
+ const widgetContext = useWidgetContext();
+ const updateDebugData = useRegisterDebugData(widgetContext?.widgetId);
+
+
+ // Convert date range preset to actual dates (stable function)
+ const getDateRangeFromPreset = useCallback((preset: string, customRange?: any) => {
+ if (preset === 'custom' && customRange) {
+ return customRange;
+ }
+
+ switch (preset) {
+ case 'last24hours':
+ return getTimeDistance('today');
+ case 'last7days':
+ return getTimeDistance('week');
+ case 'last30days':
+ return getTimeDistance('month');
+ case 'last3months':
+ return [moment().subtract(3, 'months'), moment()];
+ case 'last6months':
+ return [moment().subtract(6, 'months'), moment()];
+ case 'lastyear':
+ return getTimeDistance('year');
+ default:
+ return getTimeDistance('week');
+ }
+ }, []);
+
+ // Initialize date range only once
+ useEffect(() => {
+ if (!rangePickerValue) {
+ const initialRange = getDateRangeFromPreset(dateRangePreset, customDateRange);
+ setRangePickerValue(initialRange);
+ }
+ }, [dateRangePreset, customDateRange, getDateRangeFromPreset]);
+
+ // Set up resize observer to handle container size changes
+ useEffect(() => {
+ if (!chartContainerRef.current) return;
+
+ const container = chartContainerRef.current;
+
+ // ResizeObserver for size changes
+ const resizeObserver = new ResizeObserver((entries) => {
+ for (const entry of entries) {
+ const { width } = entry.contentRect;
+ if (width > 0 && chartRef.current) {
+ console.log('📐 LineChart: Container resized to width:', width);
+ // Update ApexCharts dimensions
+ chartRef.current.updateOptions({
+ chart: {
+ width: width,
+ height: 300
+ }
+ }, false, false);
+ }
+ }
+ });
+
+ // MutationObserver to detect when chart is added to DOM
+ const mutationObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'childList' && chartRef.current) {
+ const containerWidth = container.offsetWidth;
+ if (containerWidth > 0) {
+ console.log('📐 LineChart: DOM mutation detected, resizing to:', containerWidth);
+ chartRef.current.updateOptions({
+ chart: {
+ width: containerWidth,
+ height: 300
+ }
+ }, false, true);
+ }
+ }
+ });
+ });
+
+ resizeObserver.observe(container);
+ mutationObserver.observe(container, { childList: true, subtree: true });
+
+ return () => {
+ resizeObserver.disconnect();
+ mutationObserver.disconnect();
+ };
+ }, []);
+
+ // Force chart resize on mount and data changes
+ useEffect(() => {
+ if (chartRef.current && chartContainerRef.current && chartSeries.length > 0) {
+ // Multiple resize attempts to ensure proper width
+ const resizeChart = () => {
+ const containerWidth = chartContainerRef.current?.offsetWidth;
+ if (containerWidth && containerWidth > 0 && chartRef.current) {
+ console.log('📐 LineChart: Forcing resize to width:', containerWidth);
+ // Force complete chart re-render with new dimensions
+ chartRef.current.updateOptions({
+ chart: {
+ width: containerWidth,
+ height: 300
+ }
+ }, false, true);
+
+ // Also trigger window resize event as a fallback
+ window.dispatchEvent(new Event('resize'));
+ }
+ };
+
+ // Try multiple times to ensure the container has settled
+ const timers = [
+ setTimeout(resizeChart, 0),
+ setTimeout(resizeChart, 100),
+ setTimeout(resizeChart, 300),
+ setTimeout(resizeChart, 500),
+ ];
+
+ return () => {
+ timers.forEach(timer => clearTimeout(timer));
+ };
+ }
+ }, [chartSeries]);
+
+ // Handle page visibility changes (fixes reload width issues)
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ if (!document.hidden && chartRef.current && chartContainerRef.current) {
+ // Page became visible, force chart resize
+ setTimeout(() => {
+ const containerWidth = chartContainerRef.current?.offsetWidth;
+ if (containerWidth && containerWidth > 0) {
+ console.log('📐 LineChart: Page visibility change, resizing to:', containerWidth);
+ chartRef.current.updateOptions({
+ chart: {
+ width: containerWidth,
+ height: 300
+ }
+ }, false, true);
+
+ // Force rerender as last resort
+ setForceRerender(prev => prev + 1);
+ }
+ }, 200);
+ }
+ };
+
+ document.addEventListener('visibilitychange', handleVisibilityChange);
+ window.addEventListener('focus', handleVisibilityChange);
+
+ return () => {
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
+ window.removeEventListener('focus', handleVisibilityChange);
+ };
+ }, []);
+
+
+ // Determine if we're looking at all items or specific ones (memoized)
+ const { isAllSelected, sourceIds } = useMemo(() => {
+ const isAll = Array.isArray(source) ? source.includes('all') : source === 'all';
+ let ids: string[] = [];
+
+ if (Array.isArray(source)) {
+ ids = source.filter(s => s && s !== 'all');
+ } else if (source && source !== 'all') {
+ ids = [source];
+ }
+
+ return { isAllSelected: isAll, sourceIds: ids };
+ }, [source]);
+
+ // Memoize metrics array to prevent unnecessary re-renders
+ const memoizedMetrics = useMemo(() => metrics, [JSON.stringify(metrics)]);
+
+ // Create stable string representation for date range to prevent infinite loops
+ const rangePickerKey = useMemo(() => {
+ if (!rangePickerValue) return null;
+ return `${rangePickerValue[0]?.toISOString()}-${rangePickerValue[1]?.toISOString()}`;
+ }, [rangePickerValue]);
+
+ // Fetch data when dependencies change
+ useEffect(() => {
+ console.log('🔄 LineChart useEffect triggered with dependencies:', {
+ dataType,
+ sourceIds,
+ isAllSelected,
+ memoizedMetrics,
+ rangePickerKey: rangePickerKey ? 'exists' : 'null',
+ isPreview
+ });
+
+ if (rangePickerValue && rangePickerKey) {
+ if (isPreview) {
+ console.log('📊 Using mock data in preview mode');
+ // Use mock data in preview mode
+ const mockData = Array.from({ length: 20 }, (_, i) => ({
+ name: `device-${i % 2 + 1}`,
+ value: Math.floor(Math.random() * 40) + 30,
+ date: moment().subtract(20 - i, 'hours').toISOString(),
+ }));
+ setGraphData(mockData);
+ setIsUsingMockData(true);
+ setLoading(false);
+ } else {
+ console.log('🌐 Starting data fetch for LineChart');
+ const fetchData = async () => {
+ console.log('⏳ Setting loading to true');
+ setLoading(true);
+
+ // Reset mock data flag when fetching real data
+ setIsUsingMockData(false);
+
+ const apiResponses: Record = {};
+ try {
+ if (dataType === 'device') {
+ console.log('🖥️ Processing device data type');
+ let deviceIds = sourceIds;
+
+ // If "all" is selected, fetch all device IDs
+ if (isAllSelected) {
+ console.log('📊 LineChart API Call: getAllDevices', {
+ component: 'LineChart',
+ title,
+ timestamp: new Date().toISOString()
+ });
+ const devicesResponse = await getAllDevices();
+ apiResponses.devices = devicesResponse;
+ const devices = devicesResponse.data || [];
+ deviceIds = devices.map(device => device.uuid);
+ console.log('📊 LineChart API Response: getAllDevices', {
+ component: 'LineChart',
+ title,
+ deviceCount: devices.length,
+ deviceIds,
+ timestamp: new Date().toISOString()
+ });
+
+ // Build device name mapping
+ const nameMap: Record = {};
+ devices.forEach(device => {
+ nameMap[device.uuid] = device.fqdn || device.ip || device.uuid;
+ });
+ console.log('🏷️ Setting device name map:', nameMap);
+ setDeviceNameMap(nameMap);
+ } else {
+ // For specific devices, fetch their details to get names
+ const devicesResponse = await getAllDevices();
+ const devices = devicesResponse.data || [];
+ const nameMap: Record = {};
+ devices.forEach(device => {
+ if (sourceIds.includes(device.uuid)) {
+ nameMap[device.uuid] = device.fqdn || device.ip || device.uuid;
+ }
+ });
+ setDeviceNameMap(nameMap);
+ }
+
+ if (deviceIds.length === 0) {
+ setGraphData([]);
+ return;
+ }
+
+ // Use the primary metric for the chart (following MainChartCard pattern)
+ const primaryMetric = memoizedMetrics[0] || 'cpu_usage';
+ const statsType = STATS_TYPE_MAPPING[primaryMetric] || StatsType.DeviceStatsType.CPU;
+ console.log(`📊 Using metric: ${primaryMetric}, statsType: ${statsType}`);
+
+ // Fetch data using the same pattern as MainChartCard
+ console.log('📊 LineChart API Call: getDashboardDevicesStats', {
+ component: 'LineChart',
+ title,
+ deviceIds,
+ statsType,
+ metric: primaryMetric,
+ dateRange: {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate()
+ },
+ timestamp: new Date().toISOString()
+ });
+
+ const deviceStats = await getDashboardDevicesStats(
+ deviceIds as string[],
+ statsType,
+ {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate(),
+ }
+ );
+
+ apiResponses.deviceStats = deviceStats;
+
+ console.log('📊 LineChart API Response: getDashboardDevicesStats', {
+ component: 'LineChart',
+ title,
+ dataLength: deviceStats.data?.length || 0,
+ rawData: deviceStats.data,
+ timestamp: new Date().toISOString()
+ });
+ // Process the data - keep the original structure
+ const processedData = deviceStats.data || [];
+ console.log('💾 Setting graph data:', processedData);
+ setGraphData(processedData);
+
+ // Update debug data
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'LineChart',
+ fileName: 'LineChart.tsx',
+ rawApiData: {
+ apiCalls: {
+ source,
+ isAllSelected,
+ sourceIds,
+ getAllDevices: isAllSelected ? {
+ method: 'GET',
+ endpoint: '/api/devices'
+ } : null,
+ getDashboardDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/stats/${primaryMetric}`,
+ params: {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate()
+ },
+ body: {
+ devices: deviceIds,
+ statsType
+ }
+ }
+ },
+ responses: apiResponses
+ } as Record,
+ processedData: {
+ graphData: processedData,
+ deviceCount: deviceIds.length,
+ dateRange: {
+ from: rangePickerValue[0].toISOString(),
+ to: rangePickerValue[1].toISOString()
+ }
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metrics: memoizedMetrics,
+ dateRangePreset,
+ colorPalette
+ } as Record
+ });
+ }
+ } else if (dataType === 'container') {
+ // For containers, we'll need to adapt the data format to match API.DeviceStat
+ let containerIds = sourceIds;
+
+ if (isAllSelected) {
+ const containersResponse = await getAllContainers();
+ const containers = containersResponse.data || [];
+ containerIds = containers.map(container => container.id);
+
+ // Build container name mapping
+ const nameMap: Record = {};
+ containers.forEach(container => {
+ nameMap[container.id] = container.customName || container.name || container.id;
+ });
+ setContainerNameMap(nameMap);
+ } else {
+ // For specific containers, fetch their details to get names
+ const containersResponse = await getAllContainers();
+ const containers = containersResponse.data || [];
+ const nameMap: Record = {};
+ containers.forEach(container => {
+ if (sourceIds.includes(container.id)) {
+ nameMap[container.id] = container.customName || container.name || container.id;
+ }
+ });
+ setContainerNameMap(nameMap);
+ }
+
+ if (containerIds.length === 0) {
+ setGraphData([]);
+ return;
+ }
+
+ // For now, we'll use a simplified approach for containers
+ // This can be expanded to use container-specific APIs
+ setGraphData([]);
+ }
+ } catch (error) {
+ console.error('Failed to fetch chart data:', error);
+ setGraphData([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }
+ }
+ }, [dataType, sourceIds, isAllSelected, memoizedMetrics, rangePickerKey, isPreview]);
+
+ const getMetricLabel = useCallback((metric: string): string => {
+ const labels: Record = {
+ cpu_usage: 'CPU Usage',
+ memory_usage: 'Memory Usage',
+ memory_free: 'Memory Free',
+ storage_usage: 'Storage Usage',
+ storage_free: 'Storage Free',
+ containers: 'Containers',
+ container_cpu_usage: 'CPU Usage',
+ container_memory_usage: 'Memory Usage',
+ };
+ return labels[metric] || metric;
+ }, []);
+
+ // Process data for charts - separate by device
+ const processedChartData = useMemo(() => {
+ if (!graphData || graphData.length === 0) return {};
+
+ // Group data by device
+ const dataByDevice: Record> = {};
+
+ // First, get all unique device names
+ const deviceNames = new Set();
+ graphData.forEach(stat => {
+ const deviceName = deviceNameMap[stat.name] || stat.name;
+ deviceNames.add(deviceName);
+ });
+
+ // Initialize empty arrays for each device
+ deviceNames.forEach(deviceName => {
+ dataByDevice[deviceName] = [];
+ });
+
+ // Sort all data by date first to ensure consistent indexing
+ const sortedData = [...graphData].sort((a, b) =>
+ new Date(a.date).getTime() - new Date(b.date).getTime()
+ );
+
+ // Get unique dates for consistent indexing across devices
+ const uniqueDates = Array.from(new Set(sortedData.map(stat => stat.date)));
+
+ // Populate data for each device with consistent indexing
+ deviceNames.forEach(deviceName => {
+ uniqueDates.forEach((date, index) => {
+ const stat = sortedData.find(s =>
+ s.date === date && (deviceNameMap[s.name] || s.name) === deviceName
+ );
+ if (stat) {
+ dataByDevice[deviceName].push({
+ value: stat.value || 0,
+ index: index,
+ date: stat.date
+ });
+ }
+ });
+ });
+
+ return dataByDevice;
+ }, [graphData, deviceNameMap]);
+
+ // Handle period changes
+ const handlePeriodChange = useCallback((newPeriod: string) => {
+ setCurrentPeriod(newPeriod);
+ const newRange = getDateRangeFromPreset(newPeriod, customDateRange);
+ setRangePickerValue(newRange);
+ }, [customDateRange, getDateRangeFromPreset]);
+
+ // Color palette logic
+ const getChartColors = useCallback(() => {
+ if (customColors && customColors.length > 0) {
+ return customColors;
+ }
+
+ const colorPalettes = {
+ default: ['#52c41a', '#faad14', '#40a9ff', '#ff4d4f', '#722ed1'],
+ vibrant: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#6c5ce7'],
+ cool: ['#74b9ff', '#0984e3', '#6c5ce7', '#a29bfe', '#fd79a8'],
+ warm: ['#fd79a8', '#fdcb6e', '#e84393', '#fd79a8', '#ffeaa7'],
+ nature: ['#00b894', '#55a3ff', '#fd79a8', '#fdcb6e', '#74b9ff'],
+ };
+
+ return colorPalettes[colorPalette] || colorPalettes.default;
+ }, [colorPalette, customColors]);
+
+ // Period options (stable)
+ const periodOptions = useMemo(() => [
+ { label: 'Last 24 Hours', value: 'last24hours' },
+ { label: 'Last 7 Days', value: 'last7days' },
+ { label: 'Last 30 Days', value: 'last30days' },
+ { label: 'Last 3 Months', value: 'last3months' },
+ ], []);
+
+ // Generate stable mock data for preview mode
+ const mockData = useMemo(() => {
+ if (isPreview) {
+ return [
+ {
+ name: 'Device 1',
+ data: Array.from({ length: 20 }, (_, i) => ({
+ x: new Date(Date.now() - (20 - i) * 3600000).toISOString(),
+ y: 30 + (i * 2) + Math.sin(i * 0.5) * 10 // Stable deterministic data
+ }))
+ },
+ {
+ name: 'Device 2',
+ data: Array.from({ length: 20 }, (_, i) => ({
+ x: new Date(Date.now() - (20 - i) * 3600000).toISOString(),
+ y: 50 + (i * 1.5) + Math.cos(i * 0.3) * 8 // Stable deterministic data
+ }))
+ }
+ ];
+ }
+ return [];
+ }, [isPreview]);
+
+ // Prepare data for ApexCharts
+ const chartSeries = useMemo(() => {
+ if (isPreview) {
+ console.log('📊 LineChart using mockData:', mockData);
+ return mockData;
+ }
+
+ if (Object.keys(processedChartData).length === 0) {
+ console.log('⚠️ LineChart: No processed chart data available');
+ return [];
+ }
+
+ const series = Object.entries(processedChartData).slice(0, 2).map((entry, index) => {
+ const [deviceName, data] = entry;
+ return {
+ name: deviceName,
+ data: data.map(point => ({
+ x: point.date,
+ y: point.value
+ }))
+ };
+ });
+
+ console.log('📈 LineChart series data:', series);
+ return series;
+ }, [processedChartData, isPreview, mockData]);
+
+ const chartOptions: ApexOptions = useMemo(() => {
+ const colors = getChartColors();
+ console.log('🎨 Chart colors for palette:', colorPalette, '→', colors);
+
+ const options: ApexOptions = {
+ chart: {
+ type: 'area',
+ height: 300,
+ toolbar: { show: false },
+ background: 'transparent',
+ dropShadow: {
+ enabled: false
+ },
+ redrawOnParentResize: true,
+ redrawOnWindowResize: true
+ },
+ stroke: {
+ width: 2.5,
+ curve: 'smooth',
+ lineCap: 'round'
+ },
+ colors: colors,
+ dataLabels: {
+ enabled: false
+ },
+ markers: {
+ size: 0,
+ strokeWidth: 3,
+ hover: {
+ size: 6
+ }
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ style: {
+ colors: '#8c8c8c',
+ fontSize: '12px'
+ }
+ },
+ axisBorder: {
+ show: false
+ },
+ axisTicks: {
+ show: false
+ }
+ },
+ yaxis: {
+ labels: {
+ style: {
+ colors: '#8c8c8c',
+ fontSize: '12px'
+ },
+ formatter: function(val) {
+ if (memoizedMetrics.includes('containers')) {
+ return Math.round(val).toLocaleString();
+ }
+ return val.toFixed(1) + '%';
+ }
+ }
+ },
+ grid: {
+ borderColor: '#3a3a3e',
+ strokeDashArray: 3
+ },
+ legend: {
+ show: false
+ },
+ tooltip: {
+ theme: 'dark'
+ },
+ fill: {
+ opacity: 1,
+ gradient: {
+ type: 'vertical',
+ shadeIntensity: 0,
+ opacityFrom: 0.4,
+ opacityTo: 0,
+ stops: [0, 100]
+ }
+ }
+ };
+
+ console.log('⚙️ Final chart options:', options);
+ return options;
+ }, [memoizedMetrics, getChartColors, colorPalette, customColors]);
+
+ return (
+
+
+
+ {/* Header with title and period selector */}
+
+
+
+ {title}
+
+
+ {memoizedMetrics.map(m => getMetricLabel(m)).join(' vs ')}
+
+
+ {dateRangePreset !== 'custom' && (
+
+ )}
+
+
+ {/* Chart with device stats and legend */}
+
+ {/* Legend */}
+
+ {Object.entries(processedChartData).slice(0, 2).map((entry, index) => {
+ const [deviceName, data] = entry;
+ const lastValue = data.length > 0 ? data[data.length - 1]?.value || 0 : 0;
+ const valueStr = memoizedMetrics.includes('containers')
+ ? Math.round(lastValue).toLocaleString()
+ : `${lastValue.toFixed(1)}%`;
+
+ const chartColors = getChartColors();
+ const currentColor = chartColors[index] || chartColors[0];
+
+ return (
+
+
+
+
+ {valueStr}
+
+
+ {deviceName}
+
+
+
+ );
+ })}
+
+
+ {/* Charts */}
+
+ {loading ? (
+
+
+
+ ) : chartSeries.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default LineChart;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/MainChartCard.tsx b/client/src/pages/Dashboard/Components/MainChartCard.tsx
deleted file mode 100644
index 19c323bca..000000000
--- a/client/src/pages/Dashboard/Components/MainChartCard.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-import {
- getDashboardAveragedDevicesStats,
- getDashboardDevicesStats,
-} from '@/services/rest/statistics/stastistics';
-import Devicestatus from '@/utils/devicestatus';
-import { getTimeDistance } from '@/utils/time';
-import { LoadingOutlined } from '@ant-design/icons';
-import { Line, LineConfig } from '@ant-design/charts';
-import { useModel } from '@umijs/max';
-import {
- Card,
- Col,
- DatePicker,
- Flex,
- Row,
- Select,
- Tabs,
- TabsProps,
- Typography,
-} from 'antd';
-import React, { useEffect, useState, useCallback, useMemo } from 'react';
-import { API, StatsType } from 'ssm-shared-lib';
-import styles from '../Analysis.less';
-
-const { RangePicker } = DatePicker;
-
-const MainChartCard: React.FC = () => {
- const { initialState } = useModel('@@initialState');
- const { currentUser }: { currentUser?: API.CurrentUser } = initialState || {};
-
- const [loading, setLoading] = useState(false);
- const [graphData, setGraphData] = useState([]);
- const [graphMemData, setGraphMemData] = useState<
- API.DeviceStat[] | undefined
- >([]);
- const [graphStorageData, setGraphStorageData] = useState<
- API.DeviceStat[] | undefined
- >([]);
-
- const [topTenData, setTopTenData] = useState<
- { name: string; value: number }[] | undefined
- >([]);
- const [devices, setDevices] = useState(
- currentUser?.devices?.overview
- ?.filter((e) => e.status !== Devicestatus.UNMANAGED)
- .map((e) => e.uuid) || [],
- );
- const [type, setType] = useState(
- StatsType.DeviceStatsType.CPU,
- );
- const [rangePickerValue, setRangePickerValue] = useState(
- getTimeDistance('year'),
- );
-
- const isActive = useCallback(
- (dateType: string) => {
- const value = getTimeDistance(dateType);
- if (!rangePickerValue[0] || !rangePickerValue[1]) {
- return '';
- }
- if (
- rangePickerValue[0].isSame(value[0], 'day') &&
- rangePickerValue[1].isSame(value[1], 'day')
- ) {
- return styles.currentDate;
- }
- return '';
- },
- [rangePickerValue],
- );
-
- const handleRangePickerChange = useCallback((dates: any) => {
- setRangePickerValue(dates);
- }, []);
-
- const selectDate = useCallback((dateType: string) => {
- setRangePickerValue(getTimeDistance(dateType));
- }, []);
-
- const fetchData = useCallback(async () => {
- setLoading(true);
- try {
- if (devices.length > 0) {
- setGraphData(undefined);
- setGraphMemData(undefined);
- setGraphStorageData(undefined);
- setTopTenData(undefined);
- const [deviceStats, averagedDeviceStats] = await Promise.all([
- getDashboardDevicesStats(devices as string[], type, {
- from: rangePickerValue[0].toDate(),
- to: rangePickerValue[1].toDate(),
- }),
- getDashboardAveragedDevicesStats(devices as string[], type, {
- from: rangePickerValue[0].toDate(),
- to: rangePickerValue[1].toDate(),
- }),
- ]);
- console.log(deviceStats.data);
- switch (type) {
- case StatsType.DeviceStatsType.CPU:
- setGraphData(deviceStats.data);
- setTopTenData(averagedDeviceStats.data);
- break;
- case StatsType.DeviceStatsType.MEM_USED:
- setGraphMemData(deviceStats.data);
- setTopTenData(averagedDeviceStats.data);
- break;
- case StatsType.DeviceStatsType.DISK_USED:
- setGraphStorageData(deviceStats.data);
- setTopTenData(averagedDeviceStats.data);
- break;
- }
- }
- } catch (error) {
- console.error('Failed to fetch data:', error);
- } finally {
- setLoading(false);
- }
- }, [devices, type, rangePickerValue]);
-
- useEffect(() => {
- void fetchData();
- }, [fetchData]);
-
- const cpuConfig: LineConfig = useMemo(
- () => ({
- data: graphData,
- loading,
- animate: { enter: { type: 'waveIn' } },
- theme: {
- view: {
- viewFill: 'rgba(255,255,255,0.00)',
- },
- },
- loadingTemplate: (
-
-
-
- ),
- xField: 'date',
- yField: 'value',
- colorField: 'name',
- seriesField: 'name',
- xAxis: {
- type: 'time',
- },
- legend: {
- color: {
- itemLabelFill: '#fff',
- },
- },
- axis: {
- x: {
- labelFill: '#fff',
- },
- y: {
- labelFill: '#fff',
- labelFormatter: (v: any) => `${parseFloat(v)?.toFixed(2)}%`,
- },
- },
- tooltip: {
- channel: 'y',
- valueFormatter: (d: string) => `${parseFloat(d)?.toFixed(2)}%`,
- },
- yAxis: {
- label: {
- formatter: (v: number) => `${v?.toFixed(2)}%`,
- },
- },
- }),
- [graphData, loading],
- );
-
- const memConfig = useMemo(
- () => ({
- ...cpuConfig,
- data: graphMemData,
- // Customize further if needed
- }),
- [graphMemData, loading],
- );
-
- const storageConfig = useMemo(
- () => ({
- ...cpuConfig,
- data: graphStorageData,
- // Customize further if needed
- }),
- [graphStorageData, loading],
- );
-
- const handleTabChange = (key: string) => {
- setType(key as StatsType.DeviceStatsType);
- };
-
- const items: TabsProps['items'] = useMemo(
- () => [
- {
- key: StatsType.DeviceStatsType.CPU,
- label: 'CPU',
- children: (
-
-
-
-
-
-
-
-
-
- CPU Usage % Average Ranking
-
-
- {topTenData?.slice(0, 10).map((item, i) => (
-
-
- {i + 1}
-
-
- {item.name}
-
-
- {item.value?.toFixed(2)}%
-
-
- ))}
-
-
-
-
- ),
- },
- {
- key: StatsType.DeviceStatsType.MEM_USED,
- label: 'MEM',
- children: (
-
-
-
-
-
-
-
-
-
- Memory Usage % Average Ranking
-
-
- {topTenData?.slice(0, 10).map((item, i) => (
-
-
- {i + 1}
-
-
- {item.name}
-
-
- {item.value?.toFixed(2)}%
-
-
- ))}
-
-
-
-
- ),
- },
- {
- key: StatsType.DeviceStatsType.DISK_USED,
- label: 'DISK',
- children: (
-
-
-
-
-
-
-
-
-
- Disk Usage % Average Ranking
-
-
- {topTenData?.slice(0, 10).map((item, i) => (
-
-
- {i + 1}
-
-
- {item.name}
-
-
- {item.value?.toFixed(2)}%
-
-
- ))}
-
-
-
-
- ),
- },
- ],
- [cpuConfig, memConfig, storageConfig, topTenData],
- );
-
- const tabBarExtra = useMemo(
- () => (
-
-
-
-
e.status !== Devicestatus.UNMANAGED)
- .map((e) => ({
- value: e.uuid,
- label: e.name,
- }))}
- onChange={setDevices}
- />
-
- ),
- [
- devices,
- rangePickerValue,
- selectDate,
- handleRangePickerChange,
- isActive,
- currentUser?.devices?.overview,
- ],
- );
-
- return (
-
-
-
-
-
- );
-};
-
-export default React.memo(MainChartCard);
diff --git a/client/src/pages/Dashboard/Components/MaintenanceCalendar.tsx b/client/src/pages/Dashboard/Components/MaintenanceCalendar.tsx
new file mode 100644
index 000000000..068849349
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/MaintenanceCalendar.tsx
@@ -0,0 +1,559 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Typography, Calendar, Badge, Modal, Space, Button, Form, Input, Select, DatePicker, TimePicker, message, List, Tag } from 'antd';
+import {
+ PlusOutlined,
+ CalendarOutlined,
+ ToolOutlined,
+ DatabaseOutlined,
+ SecurityScanOutlined,
+ CloudUploadOutlined,
+ EditOutlined,
+ DeleteOutlined,
+ BellOutlined
+} from '@ant-design/icons';
+import type { Dayjs } from 'dayjs';
+import dayjs from 'dayjs';
+
+interface MaintenanceTask {
+ id: string;
+ title: string;
+ description: string;
+ type: 'maintenance' | 'backup' | 'update' | 'security' | 'deployment';
+ date: Dayjs;
+ startTime: Dayjs;
+ endTime: Dayjs;
+ status: 'scheduled' | 'in-progress' | 'completed' | 'cancelled';
+ assignee?: string;
+ servers: string[];
+ priority: 'low' | 'medium' | 'high' | 'critical';
+ recurrence?: 'none' | 'daily' | 'weekly' | 'monthly';
+ reminder?: boolean;
+}
+
+interface MaintenanceCalendarProps {
+ title?: string;
+ cardStyle?: React.CSSProperties;
+}
+
+const MaintenanceCalendar: React.FC = ({
+ title = 'Maintenance Calendar',
+ cardStyle,
+}) => {
+ const [tasks, setTasks] = useState([]);
+ const [selectedDate, setSelectedDate] = useState(dayjs());
+ const [showModal, setShowModal] = useState(false);
+ const [editingTask, setEditingTask] = useState(null);
+ const [viewMode, setViewMode] = useState<'calendar' | 'list'>('calendar');
+ const [form] = Form.useForm();
+
+ const taskTypes = [
+ { value: 'maintenance', label: 'System Maintenance', icon: , color: '#1890ff' },
+ { value: 'backup', label: 'Backup', icon: , color: '#52c41a' },
+ { value: 'update', label: 'Updates', icon: , color: '#faad14' },
+ { value: 'security', label: 'Security', icon: , color: '#ff4d4f' },
+ { value: 'deployment', label: 'Deployment', icon: , color: '#722ed1' },
+ ];
+
+ const priorityColors = {
+ low: '#52c41a',
+ medium: '#faad14',
+ high: '#ff4d4f',
+ critical: '#722ed1',
+ };
+
+ const statusColors = {
+ scheduled: '#1890ff',
+ 'in-progress': '#faad14',
+ completed: '#52c41a',
+ cancelled: '#8c8c8c',
+ };
+
+ // Mock maintenance tasks
+ const mockTasks: MaintenanceTask[] = [
+ {
+ id: '1',
+ title: 'Weekly Database Backup',
+ description: 'Automated backup of all production databases',
+ type: 'backup',
+ date: dayjs().add(1, 'day'),
+ startTime: dayjs().hour(2).minute(0),
+ endTime: dayjs().hour(4).minute(0),
+ status: 'scheduled',
+ assignee: 'System',
+ servers: ['db-prod-01', 'db-prod-02'],
+ priority: 'high',
+ recurrence: 'weekly',
+ reminder: true,
+ },
+ {
+ id: '2',
+ title: 'Security Patches',
+ description: 'Apply latest security updates to all web servers',
+ type: 'security',
+ date: dayjs().add(3, 'day'),
+ startTime: dayjs().hour(22).minute(0),
+ endTime: dayjs().hour(23).minute(30),
+ status: 'scheduled',
+ assignee: 'Admin Team',
+ servers: ['web-01', 'web-02', 'web-03'],
+ priority: 'critical',
+ recurrence: 'monthly',
+ reminder: true,
+ },
+ {
+ id: '3',
+ title: 'Container Registry Cleanup',
+ description: 'Remove old and unused container images',
+ type: 'maintenance',
+ date: dayjs().add(5, 'day'),
+ startTime: dayjs().hour(1).minute(0),
+ endTime: dayjs().hour(2).minute(0),
+ status: 'scheduled',
+ servers: ['registry-01'],
+ priority: 'low',
+ recurrence: 'weekly',
+ },
+ {
+ id: '4',
+ title: 'Application Deployment',
+ description: 'Deploy version 2.4.1 to production environment',
+ type: 'deployment',
+ date: dayjs().add(7, 'day'),
+ startTime: dayjs().hour(20).minute(0),
+ endTime: dayjs().hour(21).minute(0),
+ status: 'scheduled',
+ assignee: 'Dev Team',
+ servers: ['app-prod-01', 'app-prod-02'],
+ priority: 'medium',
+ recurrence: 'none',
+ reminder: true,
+ },
+ {
+ id: '5',
+ title: 'System Updates',
+ description: 'Update operating system and core packages',
+ type: 'update',
+ date: dayjs().subtract(2, 'day'),
+ startTime: dayjs().hour(3).minute(0),
+ endTime: dayjs().hour(5).minute(0),
+ status: 'completed',
+ assignee: 'Admin Team',
+ servers: ['web-01', 'web-02', 'api-01'],
+ priority: 'medium',
+ recurrence: 'monthly',
+ },
+ ];
+
+ useEffect(() => {
+ // Load tasks from localStorage or use mock data
+ const savedTasks = localStorage.getItem('ssm-maintenance-tasks');
+ if (savedTasks) {
+ try {
+ const parsed = JSON.parse(savedTasks).map((task: any) => ({
+ ...task,
+ date: dayjs(task.date),
+ startTime: dayjs(task.startTime),
+ endTime: dayjs(task.endTime),
+ }));
+ setTasks(parsed);
+ } catch (error) {
+ console.error('Failed to load maintenance tasks:', error);
+ setTasks(mockTasks);
+ }
+ } else {
+ setTasks(mockTasks);
+ }
+ }, []);
+
+ const saveTasks = (updatedTasks: MaintenanceTask[]) => {
+ localStorage.setItem('ssm-maintenance-tasks', JSON.stringify(updatedTasks));
+ setTasks(updatedTasks);
+ };
+
+ const getTasksByDate = (date: Dayjs) => {
+ return tasks.filter(task => task.date.isSame(date, 'day'));
+ };
+
+ const dateCellRender = (date: Dayjs) => {
+ const dayTasks = getTasksByDate(date);
+ if (dayTasks.length === 0) return null;
+
+ return (
+
+ {dayTasks.slice(0, 2).map(task => {
+ const taskType = taskTypes.find(t => t.value === task.type);
+ return (
+
+ {task.title}
+
+ );
+ })}
+ {dayTasks.length > 2 && (
+
+ +{dayTasks.length - 2} more
+
+ )}
+
+ );
+ };
+
+ const onSelectDate = (date: Dayjs) => {
+ setSelectedDate(date);
+ };
+
+ const openModal = (task?: MaintenanceTask) => {
+ if (task) {
+ setEditingTask(task);
+ form.setFieldsValue({
+ title: task.title,
+ description: task.description,
+ type: task.type,
+ date: task.date,
+ startTime: task.startTime,
+ endTime: task.endTime,
+ assignee: task.assignee,
+ servers: task.servers,
+ priority: task.priority,
+ recurrence: task.recurrence,
+ reminder: task.reminder,
+ });
+ } else {
+ setEditingTask(null);
+ form.setFieldsValue({
+ date: selectedDate,
+ startTime: dayjs().hour(9).minute(0),
+ endTime: dayjs().hour(10).minute(0),
+ priority: 'medium',
+ recurrence: 'none',
+ reminder: false,
+ });
+ }
+ setShowModal(true);
+ };
+
+ const saveTask = () => {
+ form.validateFields().then(values => {
+ const newTask: MaintenanceTask = {
+ id: editingTask?.id || Date.now().toString(),
+ title: values.title,
+ description: values.description,
+ type: values.type,
+ date: values.date,
+ startTime: values.startTime,
+ endTime: values.endTime,
+ status: editingTask?.status || 'scheduled',
+ assignee: values.assignee,
+ servers: values.servers || [],
+ priority: values.priority,
+ recurrence: values.recurrence,
+ reminder: values.reminder,
+ };
+
+ if (editingTask) {
+ saveTasks(tasks.map(task => task.id === editingTask.id ? newTask : task));
+ message.success('Task updated');
+ } else {
+ saveTasks([...tasks, newTask]);
+ message.success('Task created');
+ }
+
+ setShowModal(false);
+ form.resetFields();
+ });
+ };
+
+ const deleteTask = (id: string) => {
+ saveTasks(tasks.filter(task => task.id !== id));
+ message.success('Task deleted');
+ };
+
+ const updateTaskStatus = (id: string, status: MaintenanceTask['status']) => {
+ saveTasks(tasks.map(task =>
+ task.id === id ? { ...task, status } : task
+ ));
+ message.success(`Task marked as ${status}`);
+ };
+
+ const getUpcomingTasks = () => {
+ const today = dayjs();
+ const nextWeek = today.add(7, 'day');
+ return tasks
+ .filter(task =>
+ task.status === 'scheduled' &&
+ task.date.isAfter(today) &&
+ task.date.isBefore(nextWeek)
+ )
+ .sort((a, b) => a.date.diff(b.date));
+ };
+
+ const selectedDateTasks = getTasksByDate(selectedDate);
+ const upcomingTasks = getUpcomingTasks();
+
+ return (
+ <>
+
+
+
+ 📅 {title}
+
+
+ setViewMode('calendar')}
+ style={{ color: viewMode === 'calendar' ? 'white' : '#8c8c8c' }}
+ >
+ Calendar
+
+ setViewMode('list')}
+ style={{ color: viewMode === 'list' ? 'white' : '#8c8c8c' }}
+ >
+ List
+
+ }
+ size="small"
+ onClick={() => openModal()}
+ style={{
+ backgroundColor: '#4ecb71',
+ borderColor: '#4ecb71',
+ }}
+ >
+ Add Task
+
+
+
+
+
+ {viewMode === 'calendar' ? (
+
+
+
+ {selectedDateTasks.length > 0 && (
+
+
+ Tasks for {selectedDate.format('MMMM D, YYYY')}:
+
+ {selectedDateTasks.map(task => {
+ const taskType = taskTypes.find(t => t.value === task.type);
+ return (
+
+
+
+ {taskType?.icon}
+
+ {task.title}
+
+
+ {task.priority}
+
+
+
+ {task.startTime.format('HH:mm')} - {task.endTime.format('HH:mm')}
+
+
+
+ );
+ })}
+
+ )}
+
+ ) : (
+
+
+ Upcoming Tasks ({upcomingTasks.length})
+
+
{
+ const taskType = taskTypes.find(t => t.value === task.type);
+ return (
+
+
+
+
+
+ {taskType?.icon}
+
+ {task.title}
+
+
+ {task.priority}
+
+
+
+
+ {task.description}
+
+
+
+ }
+ size="small"
+ onClick={() => openModal(task)}
+ style={{ color: '#8c8c8c' }}
+ />
+ }
+ size="small"
+ onClick={() => deleteTask(task.id)}
+ style={{ color: '#ff4d4f' }}
+ />
+
+
+
+
+ {task.date.format('MMM D')} • {task.startTime.format('HH:mm')} - {task.endTime.format('HH:mm')}
+
+ {task.reminder && (
+
+ )}
+
+
+
+ );
+ }}
+ />
+
+ )}
+
+
+
+ {tasks.length} total tasks • {upcomingTasks.length} upcoming
+
+
+
+ {/* Add/Edit Task Modal */}
+ setShowModal(false)}
+ width={600}
+ >
+
+
+
+
+
+
+
+
+
+
+ {taskTypes.map(type => (
+
+ {type.icon} {type.label}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Low
+ Medium
+ High
+ Critical
+
+
+
+
+
+
+
+
+
+ web-01
+ web-02
+ db-prod-01
+ api-01
+
+
+
+
+
+
+ None
+ Daily
+ Weekly
+ Monthly
+
+
+
+
+ Enable reminder
+
+
+
+
+ >
+ );
+};
+
+export default MaintenanceCalendar;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/MetricCardWithMiniLineChart.tsx b/client/src/pages/Dashboard/Components/MetricCardWithMiniLineChart.tsx
new file mode 100644
index 000000000..c9b464e69
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/MetricCardWithMiniLineChart.tsx
@@ -0,0 +1,914 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { Card, Typography, Space } from 'antd';
+import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+import { WIDGET_TYPOGRAPHY, WIDGET_CARD, WIDGET_COLORS } from '../constants/widgetStyles';
+import {
+ getDashboardDevicesStats,
+ getDeviceStat,
+ getDashboardAveragedDevicesStats,
+ getDashboardSystemPerformance
+} from '@/services/rest/statistics/stastistics';
+import {
+ getContainerStat,
+ getContainerStats,
+ getAveragedStats
+} from '@/services/rest/containers/container-statistics';
+import { entityCacheService } from '@/services/cache/entityCache.service';
+import { API } from 'ssm-shared-lib';
+import moment from 'moment';
+import { useWidgetContext } from './DashboardLayoutEngine/WidgetContext';
+import { useRegisterDebugData } from './DashboardLayoutEngine/DebugDataProvider';
+import DemoOverlay from './DemoOverlay';
+
+interface SummaryStatCardProps {
+ title: string;
+ dataType?: 'device' | 'container';
+ source?: string | string[];
+ metric?: string;
+ icon?: React.ReactNode;
+ illustrationUrl?: string;
+ cardStyle?: React.CSSProperties;
+ defaultValue?: string;
+ defaultTrend?: string;
+ isPreview?: boolean;
+}
+
+const SummaryStatCard: React.FC = ({
+ title,
+ dataType = 'device',
+ source = 'all',
+ metric = 'cpu_usage',
+ icon,
+ illustrationUrl,
+ cardStyle,
+ defaultValue = '0',
+ defaultTrend = '0',
+ isPreview = false,
+}) => {
+ const [loading, setLoading] = useState(false);
+ const [currentValue, setCurrentValue] = useState(0);
+ const [weeklyData, setWeeklyData] = useState([]);
+ const [trendValue, setTrendValue] = useState(0);
+ const [trendDirection, setTrendDirection] = useState<'up' | 'down'>('up');
+ const [rawApiData, setRawApiData] = useState({});
+ const [isUsingMockData, setIsUsingMockData] = useState(false);
+
+ // Get widget context and debug registration
+ const widgetContext = useWidgetContext();
+ const updateDebugData = useRegisterDebugData(widgetContext?.widgetId);
+
+ // Determine if we're looking at all items or specific ones (memoized)
+ const { isAllSelected, sourceIds } = useMemo(() => {
+ const isAll = Array.isArray(source) ? source.includes('all') : source === 'all';
+ let ids: string[] = [];
+
+ if (Array.isArray(source)) {
+ ids = source.filter(s => s && s !== 'all');
+ } else if (source && source !== 'all') {
+ ids = [source];
+ }
+
+ return { isAllSelected: isAll, sourceIds: ids };
+ }, [source]);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ // Use mock data in preview mode
+ if (isPreview) {
+ setCurrentValue(65.5);
+ setTrendValue(12.4);
+ const mockSparklineData = Array.from({ length: 7 }, (_, i) => ({
+ value: Math.floor(Math.random() * 30) + 50,
+ index: i,
+ }));
+ setWeeklyData(mockSparklineData);
+ setIsUsingMockData(true);
+ setLoading(false);
+
+ // Update debug data for preview mode
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ apiCalls: {
+ mode: 'preview',
+ mockData: true
+ },
+ responses: {
+ mockData: mockSparklineData
+ }
+ } as Record,
+ processedData: {
+ currentValue: 65.5,
+ trendValue: 12.4,
+ weeklyData: mockSparklineData
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ isPreview: true
+ } as Record
+ });
+ }
+ return;
+ }
+ const now = moment();
+ const weekAgo = moment().subtract(7, 'days');
+
+ // Reset mock data flag when fetching real data
+ setIsUsingMockData(false);
+
+ if (dataType === 'device' && metric) {
+ if (isAllSelected) {
+ // Get all device IDs from cache
+ console.log('📊 SummaryStatCard: Getting all devices', {
+ component: 'SummaryStatCard',
+ title,
+ timestamp: new Date().toISOString()
+ });
+ const devices = await entityCacheService.getDevices();
+ const allDeviceIds = devices.map(device => device.uuid);
+ console.log('📊 SummaryStatCard API Response: getAllDevices', {
+ component: 'SummaryStatCard',
+ title,
+ deviceCount: allDeviceIds.length,
+ deviceIds: allDeviceIds,
+ timestamp: new Date().toISOString()
+ });
+
+ if (allDeviceIds.length === 0) {
+ // No devices available
+ setCurrentValue(0);
+ setWeeklyData([]);
+ return;
+ }
+
+ // Fetch averaged stats for all devices
+ console.log('📊 SummaryStatCard API Call: getDashboardAveragedDevicesStats & getDashboardDevicesStats', {
+ component: 'SummaryStatCard',
+ title,
+ deviceIds: allDeviceIds,
+ metric,
+ currentRange: {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate()
+ },
+ historicalRange: {
+ from: weekAgo.toDate(),
+ to: moment().toDate()
+ },
+ timestamp: new Date().toISOString()
+ });
+ const [currentStats, historicalStats] = await Promise.all([
+ getDashboardAveragedDevicesStats(allDeviceIds, metric, {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate(),
+ }),
+ getDashboardDevicesStats(allDeviceIds, metric, {
+ from: weekAgo.toDate(),
+ to: moment().toDate(),
+ }),
+ ]);
+
+ console.log('📊 SummaryStatCard API Response: getDashboardAveragedDevicesStats & getDashboardDevicesStats', {
+ component: 'SummaryStatCard',
+ title,
+ currentStatsLength: currentStats.data?.length || 0,
+ historicalStatsLength: historicalStats.data?.length || 0,
+ currentData: currentStats.data,
+ historicalData: historicalStats.data,
+ usingFallback: !currentStats.data || currentStats.data.length === 0,
+ timestamp: new Date().toISOString()
+ });
+
+ // Store raw API data for debugging
+ const apiDebugData = {
+ currentStats: currentStats.data,
+ historicalStats: historicalStats.data,
+ metric,
+ dataType,
+ source: 'all'
+ };
+ setRawApiData(apiDebugData);
+
+ // Set current value (latest average)
+ let finalCurrentValue = 0;
+ if (currentStats.data && currentStats.data.length > 0) {
+ const avgValue = currentStats.data.reduce((sum, item) => sum + item.value, 0) / currentStats.data.length;
+ setCurrentValue(avgValue); // Already in percentage (0-100)
+ finalCurrentValue = avgValue;
+ } else if (historicalStats.data && historicalStats.data.length > 0) {
+ // Fallback: use latest values from historical data if current stats are empty
+ const deviceLatestValues = new Map();
+
+ // Get the most recent value for each device
+ historicalStats.data.forEach((stat: API.DeviceStat) => {
+ const deviceId = stat.name;
+ const existingValue = deviceLatestValues.get(deviceId);
+ if (!existingValue || stat.date > (existingValue as any)?.date) {
+ deviceLatestValues.set(deviceId, stat.value);
+ }
+ });
+
+ // Calculate average of latest values
+ if (deviceLatestValues.size > 0) {
+ const values = Array.from(deviceLatestValues.values());
+ finalCurrentValue = values.reduce((sum, val) => sum + val, 0) / values.length;
+ setCurrentValue(finalCurrentValue);
+ }
+ }
+
+ console.log('📊 SummaryStatCard FINAL VALUE CALCULATED:', {
+ component: 'SummaryStatCard',
+ title,
+ finalCurrentValue,
+ timestamp: new Date().toISOString()
+ });
+
+ // Process historical data for the sparkline
+ let processedData: any[] = [];
+ if (historicalStats.data) {
+ processedData = processHistoricalData(historicalStats.data);
+ console.log('📊 SummaryStatCard: Processed sparkline data', {
+ component: 'SummaryStatCard',
+ title,
+ rawDataLength: historicalStats.data.length,
+ processedDataLength: processedData.length,
+ processedData: processedData
+ });
+ setWeeklyData(processedData);
+ calculateTrend(processedData);
+ }
+
+ // Update debug data
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ apiCalls: {
+ source: 'all',
+ isAllSelected: true,
+ sourceIds: [],
+ getAllDevices: {
+ method: 'GET',
+ endpoint: '/api/devices',
+ description: 'Get all devices from cache'
+ },
+ getDashboardAveragedDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/averaged/${metric}`,
+ params: {
+ from: moment().subtract(1, 'hour').toDate(),
+ to: moment().toDate()
+ },
+ body: {
+ devices: allDeviceIds
+ }
+ },
+ getDashboardDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/stats/${metric}`,
+ params: {
+ from: weekAgo.toDate(),
+ to: moment().toDate()
+ },
+ body: {
+ devices: allDeviceIds
+ }
+ }
+ },
+ responses: {
+ devices: { data: devices },
+ currentStats: currentStats,
+ historicalStats: historicalStats
+ }
+ } as Record,
+ processedData: {
+ currentValue: finalCurrentValue,
+ weeklyData: processedData,
+ deviceCount: allDeviceIds.length,
+ dateRange: {
+ from: weekAgo.toISOString(),
+ to: moment().toISOString()
+ }
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ isAllSelected: true
+ } as Record
+ });
+ }
+ } else {
+ // Fetch stats for specific devices
+ if (sourceIds.length === 0) {
+ // No specific devices selected
+ setCurrentValue(0);
+ setWeeklyData([]);
+ return;
+ }
+
+ console.log('📊 SummaryStatCard API Call: getDeviceStat (multiple)', {
+ component: 'SummaryStatCard',
+ title,
+ deviceIds: sourceIds,
+ metric,
+ timestamp: new Date().toISOString()
+ });
+ const devicePromises = sourceIds.map(deviceId =>
+ getDeviceStat(deviceId, metric)
+ );
+ const deviceStats = await Promise.all(devicePromises);
+ console.log('📊 SummaryStatCard API Response: getDeviceStat (multiple)', {
+ component: 'SummaryStatCard',
+ title,
+ deviceIds: sourceIds,
+ deviceStats: deviceStats.map(stat => ({ deviceId: stat.data?.name, value: stat.data?.value })),
+ timestamp: new Date().toISOString()
+ });
+
+ // Calculate average of current values
+ const validStats = deviceStats.filter(stat => stat.data);
+ if (validStats.length > 0) {
+ const avgValue = validStats.reduce((sum, stat) => sum + (stat.data?.value || 0), 0) / validStats.length;
+ setCurrentValue(avgValue); // Value is already in percentage (0-100)
+ }
+
+ // Fetch historical data
+ console.log('📊 SummaryStatCard API Call: getDashboardDevicesStats (specific devices)', {
+ component: 'SummaryStatCard',
+ title,
+ deviceIds: sourceIds,
+ metric,
+ dateRange: {
+ from: weekAgo.toDate(),
+ to: now.toDate()
+ },
+ timestamp: new Date().toISOString()
+ });
+ const historicalStats = await getDashboardDevicesStats(sourceIds, metric, {
+ from: weekAgo.toDate(),
+ to: now.toDate(),
+ });
+ console.log('📊 SummaryStatCard API Response: getDashboardDevicesStats (specific devices)', {
+ component: 'SummaryStatCard',
+ title,
+ historicalStatsLength: historicalStats.data?.length || 0,
+ historicalData: historicalStats.data,
+ timestamp: new Date().toISOString()
+ });
+
+ let processedData: any[] = [];
+ let finalCurrentValue = 0;
+ if (historicalStats.data) {
+ processedData = processHistoricalData(historicalStats.data);
+ console.log('📊 SummaryStatCard: Processed sparkline data (specific devices)', {
+ component: 'SummaryStatCard',
+ title,
+ rawDataLength: historicalStats.data.length,
+ processedDataLength: processedData.length,
+ processedData: processedData
+ });
+ setWeeklyData(processedData);
+ calculateTrend(processedData);
+ } else {
+ console.log('📊 SummaryStatCard: No historical data for sparkline', {
+ component: 'SummaryStatCard',
+ title,
+ historicalStats
+ });
+ }
+
+ // Get current value from earlier calculation
+ const validDeviceStats = deviceStats.filter(stat => stat.data);
+ if (validDeviceStats.length > 0) {
+ finalCurrentValue = validDeviceStats.reduce((sum, stat) => sum + (stat.data?.value || 0), 0) / validDeviceStats.length;
+ }
+
+ // Update debug data for specific devices
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ apiCalls: {
+ source: sourceIds,
+ isAllSelected: false,
+ sourceIds: sourceIds,
+ getDeviceStat: {
+ method: 'GET',
+ endpoint: `/api/statistics/device/{deviceId}/${metric}`,
+ description: 'Get current stats for specific devices',
+ deviceIds: sourceIds
+ },
+ getDashboardDevicesStats: {
+ method: 'POST',
+ endpoint: `/api/statistics/dashboard/stats/${metric}`,
+ params: {
+ from: weekAgo.toDate(),
+ to: moment().toDate()
+ },
+ body: {
+ devices: sourceIds
+ }
+ }
+ },
+ responses: {
+ deviceStats: deviceStats,
+ historicalStats: historicalStats
+ }
+ } as Record,
+ processedData: {
+ currentValue: finalCurrentValue,
+ weeklyData: processedData,
+ deviceCount: sourceIds.length,
+ dateRange: {
+ from: weekAgo.toISOString(),
+ to: moment().toISOString()
+ }
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ isAllSelected: false,
+ sourceIds
+ } as Record
+ });
+ }
+ }
+ } else if (dataType === 'container' && metric) {
+ if (isAllSelected) {
+ // Fetch averaged stats for all containers
+ const avgStats = await getAveragedStats();
+
+ if (metric === 'container_cpu_usage' && avgStats.data?.cpuStats) {
+ setCurrentValue(avgStats.data.cpuStats); // Already in percentage
+ } else if (metric === 'container_memory_usage' && avgStats.data?.memStats) {
+ setCurrentValue(avgStats.data.memStats); // Already in percentage
+ }
+
+ // Get all containers from cache for historical data
+ const containers = await entityCacheService.getContainers();
+ const allContainerIds = containers.map(container => container.id);
+
+ if (allContainerIds.length > 0 && metric) {
+ // Fetch historical data for sparkline
+ const historicalPromises = allContainerIds.slice(0, 10).map(containerId =>
+ getContainerStats(containerId, metric, {
+ from: weekAgo.toDate(),
+ to: now.toDate(),
+ })
+ );
+
+ try {
+ const historicalResults = await Promise.all(historicalPromises);
+ // Combine and process historical data
+ const combinedData: any[] = [];
+ historicalResults.forEach(result => {
+ if (result.data) {
+ combinedData.push(...result.data);
+ }
+ });
+
+ let processedData: any[] = [];
+ if (combinedData.length > 0) {
+ processedData = processHistoricalData(combinedData);
+ setWeeklyData(processedData);
+ calculateTrend(processedData);
+ } else {
+ // Use mock data if no historical data
+ processedData = generateMockSparklineData();
+ setWeeklyData(processedData);
+ calculateTrend(processedData);
+ setIsUsingMockData(true);
+ }
+
+ // Update debug data for containers (all)
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ apiCalls: {
+ source: 'all',
+ isAllSelected: true,
+ sourceIds: [],
+ getAveragedStats: {
+ method: 'GET',
+ endpoint: '/api/containers/stats/averaged',
+ description: 'Get averaged container stats'
+ },
+ getContainerStats: {
+ method: 'GET',
+ endpoint: `/api/containers/{containerId}/stats/${metric}`,
+ description: 'Get historical stats for containers',
+ containerIds: allContainerIds.slice(0, 10)
+ }
+ },
+ responses: {
+ avgStats: avgStats,
+ containers: { data: containers },
+ historicalResults: historicalResults
+ }
+ } as Record,
+ processedData: {
+ currentValue: currentValue,
+ weeklyData: processedData,
+ containerCount: allContainerIds.length,
+ metric,
+ dataType
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ isAllSelected: true
+ } as Record
+ });
+ }
+ } catch (error) {
+ console.error('Failed to fetch container historical data:', error);
+ const mockData = generateMockSparklineData();
+ setWeeklyData(mockData);
+ calculateTrend(mockData);
+ setIsUsingMockData(true);
+ }
+ } else {
+ // Use mock data for sparkline
+ const mockData = generateMockSparklineData();
+ setWeeklyData(mockData);
+ calculateTrend(mockData);
+ setIsUsingMockData(true);
+ }
+ } else {
+ // Fetch stats for specific containers
+ if (sourceIds.length === 0) {
+ // No specific containers selected
+ setCurrentValue(0);
+ setWeeklyData([]);
+ return;
+ }
+
+ const containerPromises = sourceIds.map(containerId =>
+ getContainerStat(containerId, metric)
+ );
+ const containerStats = await Promise.all(containerPromises);
+
+ // Calculate average of current values
+ const validContainerStats = containerStats.filter(stat => stat.data);
+ let finalCurrentValue = 0;
+ if (validContainerStats.length > 0) {
+ const avgValue = validContainerStats.reduce((sum, stat) => sum + (stat.data?.value || 0), 0) / validContainerStats.length;
+ setCurrentValue(avgValue); // Value is already in percentage (0-100)
+ finalCurrentValue = avgValue;
+ }
+
+ // For now, use mock data for container sparklines
+ // TODO: Implement historical container stats when available
+ const mockData = generateMockSparklineData();
+ setWeeklyData(mockData);
+ calculateTrend(mockData);
+ setIsUsingMockData(true);
+
+ // Update debug data for specific containers
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ apiCalls: {
+ source: sourceIds,
+ isAllSelected: false,
+ sourceIds: sourceIds,
+ getContainerStat: {
+ method: 'GET',
+ endpoint: `/api/containers/{containerId}/stat/${metric}`,
+ description: 'Get current stats for specific containers',
+ containerIds: sourceIds
+ }
+ },
+ responses: {
+ containerStats: containerStats,
+ mockSparklineData: mockData
+ }
+ } as Record,
+ processedData: {
+ currentValue: finalCurrentValue,
+ weeklyData: mockData,
+ containerCount: sourceIds.length,
+ metric,
+ dataType
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ isAllSelected: false,
+ sourceIds
+ } as Record
+ });
+ }
+ }
+
+ // For containers without debug data, still add basic debug info
+ if (!updateDebugData && dataType === 'container') {
+ const mockData = generateMockSparklineData();
+ setWeeklyData(mockData);
+ calculateTrend(mockData);
+ setIsUsingMockData(true);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to fetch stats:', error);
+ // Use default values on error
+ setCurrentValue(parseFloat(defaultValue));
+ setTrendValue(parseFloat(defaultTrend));
+
+ // Update debug data for error case
+ if (updateDebugData) {
+ updateDebugData({
+ componentName: 'SummaryStatCard',
+ fileName: 'SummaryStatCard.tsx',
+ rawApiData: {
+ error: {
+ message: error instanceof Error ? error.message : 'Unknown error',
+ stack: error instanceof Error ? error.stack : undefined,
+ timestamp: new Date().toISOString()
+ },
+ fallbackData: {
+ defaultValue: parseFloat(defaultValue),
+ defaultTrend: parseFloat(defaultTrend)
+ }
+ } as Record,
+ processedData: {
+ currentValue: parseFloat(defaultValue),
+ trendValue: parseFloat(defaultTrend),
+ weeklyData: [],
+ errorOccurred: true
+ } as Record,
+ config: {
+ dataType,
+ source,
+ metric,
+ defaultValue,
+ defaultTrend
+ } as Record
+ });
+ }
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ // Refresh every 30 seconds
+ const interval = setInterval(fetchData, 30000);
+ return () => clearInterval(interval);
+ }, [dataType, source, metric, isPreview, defaultValue, defaultTrend]);
+
+ const processHistoricalData = (data: API.DeviceStat[]) => {
+ console.log('📊 SummaryStatCard: Processing historical data', {
+ dataLength: data.length,
+ sampleData: data.slice(0, 3)
+ });
+
+ // Ensure we have valid data
+ if (!data || data.length === 0) {
+ return [];
+ }
+
+ // Group data by time intervals (hourly)
+ const grouped = data.reduce((acc: any, item) => {
+ const hour = moment(item.date).format('YYYY-MM-DD-HH');
+ if (!acc[hour]) {
+ acc[hour] = [];
+ }
+ acc[hour].push(item.value);
+ return acc;
+ }, {});
+
+ // Calculate averages for each hour
+ const processed = Object.keys(grouped)
+ .sort()
+ .map((hour, index) => ({
+ index,
+ value: grouped[hour].reduce((sum: number, val: number) => sum + val, 0) / grouped[hour].length
+ }));
+
+ console.log('📊 SummaryStatCard: Grouped data', {
+ groupedKeys: Object.keys(grouped).length,
+ processedLength: processed.length
+ });
+
+ // Ensure we have at least 2 points for the sparkline to render
+ if (processed.length === 1) {
+ // Duplicate the single point with slight variation
+ const singleValue = processed[0].value;
+ return [
+ { index: 0, value: singleValue * 0.98 },
+ { index: 1, value: singleValue }
+ ];
+ }
+
+ return processed;
+ };
+
+ const calculateTrend = (data: any[]) => {
+ console.log('📊 SummaryStatCard calculateTrend input:', { data, dataType: typeof data[0] });
+ if (data.length < 2) return;
+
+ // Compare last week's average with this week's average
+ const midPoint = Math.floor(data.length / 2);
+ const lastWeekData = data.slice(0, midPoint);
+ const thisWeekData = data.slice(midPoint);
+
+ const lastWeekAvg = lastWeekData.reduce((sum, item) => sum + item.value, 0) / lastWeekData.length;
+ const thisWeekAvg = thisWeekData.reduce((sum, item) => sum + item.value, 0) / thisWeekData.length;
+
+ const percentChange = ((thisWeekAvg - lastWeekAvg) / lastWeekAvg) * 100;
+ console.log('📊 SummaryStatCard calculateTrend result:', {
+ lastWeekAvg, thisWeekAvg, percentChange,
+ trendValue: Math.abs(percentChange),
+ direction: percentChange >= 0 ? 'up' : 'down'
+ });
+ setTrendValue(Math.abs(percentChange));
+ setTrendDirection(percentChange >= 0 ? 'up' : 'down');
+ };
+
+ const generateMockSparklineData = () => {
+ return Array.from({ length: 30 }, (_, i) => {
+ const progress = i / 29;
+ const wave1 = Math.sin(progress * Math.PI * 3) * 15;
+ const wave2 = Math.sin(progress * Math.PI * 5) * 5;
+ const trendLine = trendDirection === 'down' ? progress * -10 : progress * 10;
+ return {
+ index: i,
+ value: 50 + wave1 + wave2 + trendLine
+ };
+ });
+ };
+
+ const formatValue = (value: number) => {
+ // Format based on metric type
+ if (metric === 'containers') {
+ return Math.round(value).toLocaleString();
+ }
+ // For percentages, show one decimal place with % symbol
+ return `${value.toFixed(1)}%`;
+ };
+
+ const chartOptions: ApexOptions = {
+ chart: {
+ type: 'line',
+ sparkline: {
+ enabled: true
+ },
+ animations: {
+ enabled: false
+ }
+ },
+ stroke: {
+ curve: 'smooth',
+ width: 2.5,
+ colors: ['#faad14'] // Golden color as shown in the image
+ },
+ fill: {
+ opacity: 1
+ },
+ tooltip: {
+ enabled: true,
+ theme: 'dark',
+ style: {
+ fontSize: '12px'
+ },
+ custom: function({ series, seriesIndex, dataPointIndex, w }) {
+ const value = series[seriesIndex][dataPointIndex];
+ const dataPoint = weeklyData[dataPointIndex];
+ const timestamp = dataPoint ? new Date(Date.now() - (weeklyData.length - dataPointIndex - 1) * 3600000).toLocaleString() : 'Unknown';
+
+ return `
+
+
${value.toFixed(1)}%
+
${timestamp}
+
`;
+ }
+ },
+ yaxis: {
+ show: false
+ },
+ xaxis: {
+ show: false
+ },
+ grid: {
+ show: false,
+ padding: {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ }
+ }
+ };
+
+ const chartSeries = [{
+ name: title,
+ data: weeklyData.map(item => item.value)
+ }];
+
+ // Debug log for sparkline rendering
+ console.log('📊 SummaryStatCard: Sparkline render data', {
+ component: 'SummaryStatCard',
+ title,
+ weeklyDataLength: weeklyData.length,
+ weeklyData: weeklyData,
+ chartSeries: chartSeries
+ });
+
+ const trendIcon = trendDirection === 'up' ? : ;
+ const trendColor = trendDirection === 'up' ? WIDGET_COLORS.trend.up : WIDGET_COLORS.trend.down;
+
+ return (
+
+
+
+
+
+ {title}
+
+
+
+
+
+ {formatValue(currentValue)}
+
+
+ {trendIcon}
+
+ {trendValue.toFixed(1)}%
+
+
+ last week
+
+
+
+
+
+ {weeklyData.length > 0 ? (
+
+ ) : (
+
+ No data
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default SummaryStatCard;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/NotebookWidget.tsx b/client/src/pages/Dashboard/Components/NotebookWidget.tsx
new file mode 100644
index 000000000..fccff6f47
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/NotebookWidget.tsx
@@ -0,0 +1,297 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Typography, Button, Space, Input, message } from 'antd';
+import { EditOutlined, SaveOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
+
+const { TextArea } = Input;
+
+interface Note {
+ id: string;
+ title: string;
+ content: string;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+interface NotebookWidgetProps {
+ title?: string;
+ cardStyle?: React.CSSProperties;
+}
+
+const NotebookWidget: React.FC = ({
+ title = 'Notebook',
+ cardStyle,
+}) => {
+ const [notes, setNotes] = useState([]);
+ const [editingId, setEditingId] = useState(null);
+ const [editTitle, setEditTitle] = useState('');
+ const [editContent, setEditContent] = useState('');
+ const [isCreating, setIsCreating] = useState(false);
+
+ // Load notes from localStorage
+ useEffect(() => {
+ const savedNotes = localStorage.getItem('ssm-notebook-notes');
+ if (savedNotes) {
+ try {
+ const parsedNotes = JSON.parse(savedNotes).map((note: any) => ({
+ ...note,
+ createdAt: new Date(note.createdAt),
+ updatedAt: new Date(note.updatedAt),
+ }));
+ setNotes(parsedNotes);
+ } catch (error) {
+ console.error('Failed to load notes:', error);
+ }
+ }
+ }, []);
+
+ // Save notes to localStorage
+ const saveNotes = (updatedNotes: Note[]) => {
+ localStorage.setItem('ssm-notebook-notes', JSON.stringify(updatedNotes));
+ setNotes(updatedNotes);
+ };
+
+ const createNote = () => {
+ if (!editTitle.trim()) {
+ message.warning('Please enter a title');
+ return;
+ }
+
+ const newNote: Note = {
+ id: Date.now().toString(),
+ title: editTitle.trim(),
+ content: editContent.trim(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+
+ const updatedNotes = [newNote, ...notes];
+ saveNotes(updatedNotes);
+ setEditTitle('');
+ setEditContent('');
+ setIsCreating(false);
+ message.success('Note created');
+ };
+
+ const startEdit = (note: Note) => {
+ setEditingId(note.id);
+ setEditTitle(note.title);
+ setEditContent(note.content);
+ };
+
+ const saveEdit = () => {
+ if (!editTitle.trim()) {
+ message.warning('Please enter a title');
+ return;
+ }
+
+ const updatedNotes = notes.map(note =>
+ note.id === editingId
+ ? {
+ ...note,
+ title: editTitle.trim(),
+ content: editContent.trim(),
+ updatedAt: new Date(),
+ }
+ : note
+ );
+
+ saveNotes(updatedNotes);
+ setEditingId(null);
+ setEditTitle('');
+ setEditContent('');
+ message.success('Note updated');
+ };
+
+ const deleteNote = (id: string) => {
+ const updatedNotes = notes.filter(note => note.id !== id);
+ saveNotes(updatedNotes);
+ message.success('Note deleted');
+ };
+
+ const cancelEdit = () => {
+ setEditingId(null);
+ setEditTitle('');
+ setEditContent('');
+ setIsCreating(false);
+ };
+
+ const formatDate = (date: Date) => {
+ return new Intl.DateTimeFormat('en-US', {
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }).format(date);
+ };
+
+ return (
+
+
+
+ 📝 {title}
+
+ }
+ size="small"
+ onClick={() => setIsCreating(true)}
+ >
+ Add Note
+
+
+
+
+ {isCreating && (
+
+ setEditTitle(e.target.value)}
+ style={{
+ marginBottom: '8px',
+ backgroundColor: '#1a1a1a',
+ borderColor: '#3a3a3a',
+ color: 'white'
+ }}
+ />
+
+ )}
+
+ {notes.map((note) => (
+
+ {editingId === note.id ? (
+
+ setEditTitle(e.target.value)}
+ style={{
+ marginBottom: '8px',
+ backgroundColor: '#1a1a1a',
+ borderColor: '#3a3a3a',
+ color: 'white'
+ }}
+ />
+
+ ) : (
+
+
+
+ {note.title}
+
+
+ }
+ onClick={() => startEdit(note)}
+ style={{ color: '#8c8c8c' }}
+ />
+ }
+ onClick={() => deleteNote(note.id)}
+ style={{ color: '#ff4d4f' }}
+ />
+
+
+
+ {note.content || 'No content'}
+
+
+ {formatDate(note.updatedAt)}
+
+
+ )}
+
+ ))}
+
+ {notes.length === 0 && !isCreating && (
+
+ 📝 No notes yet. Click "Add Note" to get started!
+
+ )}
+
+
+ );
+};
+
+export default NotebookWidget;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/ProgressBarsCard.tsx b/client/src/pages/Dashboard/Components/ProgressBarsCard.tsx
new file mode 100644
index 000000000..15dff5986
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/ProgressBarsCard.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import { Card, Typography, Progress, Space, Row, Col } from 'antd';
+
+interface StatusItem {
+ name: string;
+ count: string;
+ percentage: number; // For progress bar
+ color: string;
+}
+
+interface ProgressBarsCardProps {
+ title: string;
+ statuses?: StatusItem[];
+ cardStyle?: React.CSSProperties;
+}
+
+const ProgressBarsCard: React.FC = ({
+ title,
+ statuses = [],
+ cardStyle,
+}) => {
+ return (
+
+
+ {title}
+
+
+
+ {statuses.map((status) => (
+
+
+
+
+ {status.name}
+
+
+
+
+
+ {status.count}
+
+
+ ({status.percentage}%)
+
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default ProgressBarsCard;
diff --git a/client/src/pages/Dashboard/Components/QuickActionsWidget.tsx b/client/src/pages/Dashboard/Components/QuickActionsWidget.tsx
new file mode 100644
index 000000000..3a2aa46d9
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/QuickActionsWidget.tsx
@@ -0,0 +1,536 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Typography, Button, Space, Row, Col, Modal, Input, Select, message, Badge } from 'antd';
+import {
+ PlusOutlined,
+ PlayCircleOutlined,
+ StopOutlined,
+ ReloadOutlined,
+ SettingOutlined,
+ DeleteOutlined,
+ EditOutlined,
+ RocketOutlined,
+ CodeOutlined,
+ GlobalOutlined,
+ DatabaseOutlined,
+ TerminalIcon
+} from '@ant-design/icons';
+
+interface QuickAction {
+ id: string;
+ name: string;
+ description: string;
+ type: 'container' | 'playbook' | 'ssh' | 'command' | 'url';
+ action: string; // container ID, playbook path, SSH command, etc.
+ color: string;
+ icon: string;
+ target?: string; // for SSH actions
+ enabled: boolean;
+}
+
+interface QuickActionsWidgetProps {
+ title?: string;
+ cardStyle?: React.CSSProperties;
+}
+
+const QuickActionsWidget: React.FC = ({
+ title = 'Quick Actions',
+ cardStyle,
+}) => {
+ const [actions, setActions] = useState([]);
+ const [showModal, setShowModal] = useState(false);
+ const [editingAction, setEditingAction] = useState(null);
+ const [executingActions, setExecutingActions] = useState>(new Set());
+
+ // Form state
+ const [formData, setFormData] = useState({
+ name: '',
+ description: '',
+ type: 'container' as QuickAction['type'],
+ action: '',
+ color: '#4ecb71',
+ icon: 'RocketOutlined',
+ target: '',
+ enabled: true,
+ });
+
+ const actionTypes = [
+ { value: 'container', label: 'Container Action', icon: '🐳' },
+ { value: 'playbook', label: 'Ansible Playbook', icon: '📋' },
+ { value: 'ssh', label: 'SSH Command', icon: '🖥️' },
+ { value: 'command', label: 'System Command', icon: '⚡' },
+ { value: 'url', label: 'Open URL', icon: '🌐' },
+ ];
+
+ const colorOptions = [
+ '#4ecb71', '#1890ff', '#faad14', '#ff4d4f', '#722ed1',
+ '#13c2c2', '#eb2f96', '#52c41a', '#f5222d', '#fa8c16'
+ ];
+
+ const iconOptions = [
+ { value: 'RocketOutlined', icon: , label: 'Rocket' },
+ { value: 'PlayCircleOutlined', icon: , label: 'Play' },
+ { value: 'CodeOutlined', icon: , label: 'Code' },
+ { value: 'GlobalOutlined', icon: , label: 'Global' },
+ { value: 'DatabaseOutlined', icon: , label: 'Database' },
+ { value: 'SettingOutlined', icon: , label: 'Settings' },
+ ];
+
+ // Default actions
+ const defaultActions: QuickAction[] = [
+ {
+ id: 'restart-nginx',
+ name: 'Restart Nginx',
+ description: 'Restart the Nginx container',
+ type: 'container',
+ action: 'nginx_container_restart',
+ color: '#4ecb71',
+ icon: 'RocketOutlined',
+ enabled: true,
+ },
+ {
+ id: 'backup-db',
+ name: 'Backup Database',
+ description: 'Run database backup playbook',
+ type: 'playbook',
+ action: '/playbooks/backup-database.yml',
+ color: '#1890ff',
+ icon: 'DatabaseOutlined',
+ enabled: true,
+ },
+ {
+ id: 'check-disk',
+ name: 'Check Disk Space',
+ description: 'Check disk usage on all servers',
+ type: 'ssh',
+ action: 'df -h',
+ color: '#faad14',
+ icon: 'GlobalOutlined',
+ target: 'all_servers',
+ enabled: true,
+ },
+ {
+ id: 'portainer',
+ name: 'Open Portainer',
+ description: 'Open Portainer in new tab',
+ type: 'url',
+ action: 'http://localhost:9000',
+ color: '#722ed1',
+ icon: 'CodeOutlined',
+ enabled: true,
+ },
+ ];
+
+ // Load actions from localStorage
+ useEffect(() => {
+ const savedActions = localStorage.getItem('ssm-quick-actions');
+ if (savedActions) {
+ try {
+ setActions(JSON.parse(savedActions));
+ } catch (error) {
+ console.error('Failed to load quick actions:', error);
+ setActions(defaultActions);
+ }
+ } else {
+ setActions(defaultActions);
+ }
+ }, []);
+
+ const saveActions = (updatedActions: QuickAction[]) => {
+ localStorage.setItem('ssm-quick-actions', JSON.stringify(updatedActions));
+ setActions(updatedActions);
+ };
+
+ const executeAction = async (action: QuickAction) => {
+ if (executingActions.has(action.id)) return;
+
+ setExecutingActions(prev => new Set(prev).add(action.id));
+
+ try {
+ switch (action.type) {
+ case 'container':
+ // Mock container action
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ message.success(`Container action "${action.name}" executed successfully`);
+ break;
+
+ case 'playbook':
+ // Mock playbook execution
+ await new Promise(resolve => setTimeout(resolve, 3000));
+ message.success(`Playbook "${action.name}" executed successfully`);
+ break;
+
+ case 'ssh':
+ // Mock SSH command
+ await new Promise(resolve => setTimeout(resolve, 1500));
+ message.success(`SSH command "${action.name}" executed successfully`);
+ break;
+
+ case 'command':
+ // Mock system command
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ message.success(`Command "${action.name}" executed successfully`);
+ break;
+
+ case 'url':
+ // Open URL in new tab
+ window.open(action.action, '_blank');
+ message.info(`Opened ${action.name} in new tab`);
+ break;
+
+ default:
+ message.error('Unknown action type');
+ }
+ } catch (error) {
+ console.error('Action execution failed:', error);
+ message.error(`Failed to execute "${action.name}"`);
+ } finally {
+ setExecutingActions(prev => {
+ const newSet = new Set(prev);
+ newSet.delete(action.id);
+ return newSet;
+ });
+ }
+ };
+
+ const openModal = (action?: QuickAction) => {
+ if (action) {
+ setEditingAction(action);
+ setFormData({
+ name: action.name,
+ description: action.description,
+ type: action.type,
+ action: action.action,
+ color: action.color,
+ icon: action.icon,
+ target: action.target || '',
+ enabled: action.enabled,
+ });
+ } else {
+ setEditingAction(null);
+ setFormData({
+ name: '',
+ description: '',
+ type: 'container',
+ action: '',
+ color: '#4ecb71',
+ icon: 'RocketOutlined',
+ target: '',
+ enabled: true,
+ });
+ }
+ setShowModal(true);
+ };
+
+ const saveAction = () => {
+ if (!formData.name.trim() || !formData.action.trim()) {
+ message.warning('Please fill in all required fields');
+ return;
+ }
+
+ const newAction: QuickAction = {
+ id: editingAction?.id || Date.now().toString(),
+ name: formData.name.trim(),
+ description: formData.description.trim(),
+ type: formData.type,
+ action: formData.action.trim(),
+ color: formData.color,
+ icon: formData.icon,
+ target: formData.target.trim(),
+ enabled: formData.enabled,
+ };
+
+ if (editingAction) {
+ saveActions(actions.map(action => action.id === editingAction.id ? newAction : action));
+ message.success('Action updated');
+ } else {
+ saveActions([...actions, newAction]);
+ message.success('Action created');
+ }
+
+ setShowModal(false);
+ };
+
+ const deleteAction = (id: string) => {
+ saveActions(actions.filter(action => action.id !== id));
+ message.success('Action deleted');
+ };
+
+ const toggleAction = (id: string) => {
+ saveActions(actions.map(action =>
+ action.id === id ? { ...action, enabled: !action.enabled } : action
+ ));
+ };
+
+ const getIcon = (iconName: string) => {
+ const iconMap: Record = {
+ RocketOutlined: ,
+ PlayCircleOutlined: ,
+ CodeOutlined: ,
+ GlobalOutlined: ,
+ DatabaseOutlined: ,
+ SettingOutlined: ,
+ };
+ return iconMap[iconName] || ;
+ };
+
+ const enabledActions = actions.filter(action => action.enabled);
+
+ return (
+ <>
+
+
+
+ 🎯 {title}
+
+ }
+ size="small"
+ onClick={() => openModal()}
+ style={{
+ backgroundColor: '#4ecb71',
+ borderColor: '#4ecb71',
+ }}
+ >
+ Add Action
+
+
+
+
+ {enabledActions.length > 0 ? (
+
+ {enabledActions.map((action) => (
+
+
+
executeAction(action)}
+ loading={executingActions.has(action.id)}
+ disabled={executingActions.has(action.id)}
+ >
+
+ {getIcon(action.icon)}
+
+
+ {action.name}
+
+
+
+
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ openModal(action);
+ }}
+ style={{
+ color: 'rgba(255, 255, 255, 0.8)',
+ backgroundColor: 'rgba(0, 0, 0, 0.2)',
+ border: 'none',
+ width: '24px',
+ height: '24px',
+ borderRadius: '4px',
+ }}
+ />
+
+
+
+ ))}
+
+ ) : (
+
+ 🎯 No quick actions configured. Click "Add Action" to get started!
+
+ )}
+
+
+ {actions.length > 0 && (
+
+ {enabledActions.length} of {actions.length} actions enabled
+
+ )}
+
+
+ setShowModal(false)}
+ width={500}
+ >
+
+
+ Name *
+ setFormData({ ...formData, name: e.target.value })}
+ />
+
+
+
+ Description
+ setFormData({ ...formData, description: e.target.value })}
+ rows={2}
+ />
+
+
+
+ Type *
+ setFormData({ ...formData, type: value })}
+ options={actionTypes.map(type => ({
+ value: type.value,
+ label: `${type.icon} ${type.label}`,
+ }))}
+ />
+
+
+
+
+ {formData.type === 'container' ? 'Container Action' :
+ formData.type === 'playbook' ? 'Playbook Path' :
+ formData.type === 'ssh' ? 'SSH Command' :
+ formData.type === 'command' ? 'System Command' :
+ 'URL'} *
+
+ setFormData({ ...formData, action: e.target.value })}
+ />
+
+
+ {formData.type === 'ssh' && (
+
+ Target
+ setFormData({ ...formData, target: e.target.value })}
+ />
+
+ )}
+
+
+
+ Color
+
+ {colorOptions.map(color => (
+
setFormData({ ...formData, color })}
+ />
+ ))}
+
+
+
+
Icon
+
setFormData({ ...formData, icon: value })}
+ >
+ {iconOptions.map(icon => (
+
+ {icon.icon} {icon.label}
+
+ ))}
+
+
+
+
+ {editingAction && (
+
+
+ setFormData({ ...formData, enabled: e.target.checked })}
+ style={{ marginRight: '8px' }}
+ />
+ Enabled
+
+
}
+ onClick={() => {
+ deleteAction(editingAction.id);
+ setShowModal(false);
+ }}
+ >
+ Delete Action
+
+
+ )}
+
+
+ >
+ );
+};
+
+export default QuickActionsWidget;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/RSSFeedSettings.tsx b/client/src/pages/Dashboard/Components/RSSFeedSettings.tsx
new file mode 100644
index 000000000..c491d295c
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/RSSFeedSettings.tsx
@@ -0,0 +1,228 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Button, Space, Typography, InputNumber, Alert, Card, Checkbox, Select } from 'antd';
+import { PlusOutlined, DeleteOutlined, BgColorsOutlined } from '@ant-design/icons';
+import { FeedConfig } from '@/services/rest/rss.service';
+import { COLOR_PALETTES } from './utils/colorPalettes';
+
+interface RSSFeedSettingsProps {
+ value?: {
+ feeds?: FeedConfig[];
+ refreshInterval?: number;
+ maxItems?: number;
+ colorPalette?: string;
+ customColors?: string[];
+ };
+ onChange?: (value: any) => void;
+}
+
+const RSSFeedSettings: React.FC
= ({ value = {}, onChange }) => {
+ const [feeds, setFeeds] = useState(value.feeds || []);
+ const [refreshInterval, setRefreshInterval] = useState(value.refreshInterval || 30);
+ const [maxItems, setMaxItems] = useState(value.maxItems || 20);
+ const [colorPalette, setColorPalette] = useState(value.colorPalette || 'default');
+ const [customColors, setCustomColors] = useState(value.customColors || []);
+ const [newFeedName, setNewFeedName] = useState('');
+ const [newFeedUrl, setNewFeedUrl] = useState('');
+
+ // Default feeds
+ const defaultFeeds: FeedConfig[] = [
+ {
+ id: 'docker-blog',
+ name: 'Docker Blog',
+ url: 'https://www.docker.com/blog/feed/',
+ enabled: true,
+ },
+ {
+ id: 'kubernetes-blog',
+ name: 'Kubernetes Blog',
+ url: 'https://kubernetes.io/feed.xml',
+ enabled: true,
+ },
+ {
+ id: 'ansible-blog',
+ name: 'Ansible Blog',
+ url: 'https://www.ansible.com/blog/rss.xml',
+ enabled: true,
+ },
+ ];
+
+ useEffect(() => {
+ // If no feeds, set defaults
+ if (feeds.length === 0) {
+ setFeeds(defaultFeeds);
+ }
+ }, []);
+
+ useEffect(() => {
+ // Update parent whenever settings change
+ onChange?.({
+ feeds,
+ refreshInterval,
+ maxItems,
+ colorPalette,
+ customColors
+ });
+ }, [feeds, refreshInterval, maxItems, colorPalette, customColors, onChange]);
+
+ const addFeed = () => {
+ if (!newFeedName.trim() || !newFeedUrl.trim()) {
+ return;
+ }
+
+ const newFeed: FeedConfig = {
+ id: Date.now().toString(),
+ name: newFeedName.trim(),
+ url: newFeedUrl.trim(),
+ enabled: true,
+ };
+
+ setFeeds([...feeds, newFeed]);
+ setNewFeedName('');
+ setNewFeedUrl('');
+ };
+
+ const removeFeed = (id: string) => {
+ setFeeds(feeds.filter(feed => feed.id !== id));
+ };
+
+ const toggleFeed = (id: string) => {
+ setFeeds(feeds.map(feed =>
+ feed.id === id ? { ...feed, enabled: !feed.enabled } : feed
+ ));
+ };
+
+ return (
+
+ RSS Feed Configuration
+
+
+
+
+ Refresh Interval (minutes)
+ setRefreshInterval(val || 30)}
+ style={{ width: '100%', marginTop: 4 }}
+ />
+
+
+ Maximum Items to Display
+ setMaxItems(val || 20)}
+ style={{ width: '100%', marginTop: 4 }}
+ />
+
+
+
+
+ Badge Color Theme
+
+
({
+ label: (
+
+
+ {palette.colors.slice(0, 4).map((color, index) => (
+
+ ))}
+
+
{palette.name}
+
+ ),
+ value: palette.id
+ }))}
+ />
+
+
+
+
+
+
+
+
+
+ setNewFeedName(e.target.value)}
+ style={{ marginBottom: 8 }}
+ />
+
+ setNewFeedUrl(e.target.value)}
+ onPressEnter={addFeed}
+ />
+ }
+ onClick={addFeed}
+ disabled={!newFeedName.trim() || !newFeedUrl.trim()}
+ >
+ Add
+
+
+
+
+
+
+ {feeds.map((feed) => (
+
+
+
+ toggleFeed(feed.id)}
+ />
+
+ {feed.name}
+
+ {feed.url}
+
+
+
+
}
+ size="small"
+ onClick={() => removeFeed(feed.id)}
+ />
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export default RSSFeedSettings;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/RSSFeedWidget.tsx b/client/src/pages/Dashboard/Components/RSSFeedWidget.tsx
new file mode 100644
index 000000000..cd93f76e7
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/RSSFeedWidget.tsx
@@ -0,0 +1,269 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { Card, Typography, List, Button, Spin, Badge, message } from 'antd';
+import { ReloadOutlined, LinkOutlined } from '@ant-design/icons';
+import { fetchRSSFeeds, RSSFeedItem, FeedConfig } from '@/services/rest/rss.service';
+import { getColorForItem } from './utils/colorPalettes';
+
+// Use types from the service
+type RSSItem = RSSFeedItem;
+type RSSFeed = FeedConfig;
+
+interface RSSFeedWidgetProps {
+ title?: string;
+ cardStyle?: React.CSSProperties;
+ colorPalette?: string;
+ customColors?: string[];
+ widgetSettings?: {
+ feeds?: FeedConfig[];
+ refreshInterval?: number;
+ maxItems?: number;
+ colorPalette?: string;
+ customColors?: string[];
+ };
+}
+
+const RSSFeedWidget: React.FC = ({
+ title = 'News Feed',
+ cardStyle,
+ colorPalette = 'default',
+ customColors,
+ widgetSettings,
+}) => {
+ const [items, setItems] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [feeds, setFeeds] = useState([]);
+ const [lastUpdate, setLastUpdate] = useState(null);
+ const refreshInterval = widgetSettings?.refreshInterval || 30;
+ const maxItems = widgetSettings?.maxItems || 20;
+
+ // Get effective color palette - priority: widgetSettings > props > default
+ const effectiveColorPalette = widgetSettings?.colorPalette || colorPalette || 'default';
+ const effectiveCustomColors = widgetSettings?.customColors || customColors;
+
+ // Default feeds
+ const defaultFeeds: RSSFeed[] = [
+ {
+ id: 'docker-blog',
+ name: 'Docker Blog',
+ url: 'https://www.docker.com/blog/feed/',
+ enabled: true,
+ },
+ {
+ id: 'kubernetes-blog',
+ name: 'Kubernetes Blog',
+ url: 'https://kubernetes.io/feed.xml',
+ enabled: true,
+ },
+ {
+ id: 'ansible-blog',
+ name: 'Ansible Blog',
+ url: 'https://www.ansible.com/blog/rss.xml',
+ enabled: true,
+ },
+ ];
+
+ // Load feeds from widget settings
+ useEffect(() => {
+ if (widgetSettings?.feeds && widgetSettings.feeds.length > 0) {
+ setFeeds(widgetSettings.feeds);
+ } else {
+ setFeeds(defaultFeeds);
+ }
+ }, [widgetSettings?.feeds]);
+
+ // Fetch RSS feeds from the backend API
+ const fetchFeeds = async () => {
+ setLoading(true);
+ try {
+ // Only fetch if there are enabled feeds
+ const enabledFeeds = feeds.filter(feed => feed.enabled);
+ if (enabledFeeds.length === 0) {
+ setItems([]);
+ setLastUpdate(new Date());
+ return;
+ }
+
+ const response = await fetchRSSFeeds(enabledFeeds);
+
+ if (response.data) {
+ // Sort by date and limit items
+ const sortedItems = response.data
+ .sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime())
+ .slice(0, maxItems);
+
+ setItems(sortedItems);
+ setLastUpdate(new Date());
+ } else {
+ setItems([]);
+ message.warning('No RSS items found');
+ }
+ } catch (error) {
+ console.error('Failed to fetch RSS feeds:', error);
+ message.error('Failed to fetch news feeds. Please check your feed URLs.');
+ setItems([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // Initial load and refresh based on settings
+ useEffect(() => {
+ if (feeds.length > 0) {
+ fetchFeeds();
+ // Refresh based on settings
+ const interval = setInterval(fetchFeeds, refreshInterval * 60 * 1000);
+ return () => clearInterval(interval);
+ }
+ }, [feeds, refreshInterval]);
+
+
+ const formatDate = (dateString: string) => {
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffMs = now.getTime() - date.getTime();
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
+ const diffDays = Math.floor(diffHours / 24);
+
+ if (diffHours < 1) return 'Just now';
+ if (diffHours < 24) return `${diffHours}h ago`;
+ if (diffDays < 7) return `${diffDays}d ago`;
+ return date.toLocaleDateString();
+ };
+
+ // Get badge color based on source name using color palette
+ const getBadgeColor = (sourceName: string) => {
+ if (effectiveCustomColors && effectiveCustomColors.length > 0) {
+ // Use custom colors with hash-based assignment
+ const hash = sourceName.split('').reduce((acc, char) => {
+ return char.charCodeAt(0) + ((acc << 5) - acc);
+ }, 0);
+ const colorIndex = Math.abs(hash) % effectiveCustomColors.length;
+ return effectiveCustomColors[colorIndex];
+ }
+
+ // Use palette colors
+ return getColorForItem(sourceName, effectiveColorPalette);
+ };
+
+ return (
+
+
+
+ {title}
+
+ }
+ size="small"
+ onClick={fetchFeeds}
+ loading={loading}
+ style={{ color: '#8c8c8c' }}
+ />
+
+
+
+ {loading && items.length === 0 ? (
+
+
+
+ ) : (
+
(
+
+
+
+ window.open(item.link, '_blank')}
+ >
+ {item.title}
+
+ window.open(item.link, '_blank')}
+ />
+
+
+ {item.description}
+
+
+
+
+ {formatDate(item.pubDate)}
+
+
+
+
+ )}
+ />
+ )}
+
+ {items.length === 0 && !loading && (
+
+ No news items available. Try refreshing or check your feed settings.
+
+ )}
+
+
+ {lastUpdate && (
+
+ Last updated: {lastUpdate.toLocaleTimeString()}
+
+ )}
+
+ );
+};
+
+export default RSSFeedWidget;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/SystemPerformanceCard.tsx b/client/src/pages/Dashboard/Components/SystemPerformanceCard.tsx
index 21639a4ad..994bdd916 100644
--- a/client/src/pages/Dashboard/Components/SystemPerformanceCard.tsx
+++ b/client/src/pages/Dashboard/Components/SystemPerformanceCard.tsx
@@ -1,14 +1,19 @@
-import styles from '@/pages/Dashboard/Analysis.less';
-import ChartCard from '@/pages/Dashboard/ChartComponents/ChartCard';
-import Field from '@/pages/Dashboard/ChartComponents/Field';
-import Trend from '@/pages/Dashboard/ChartComponents/Trend';
import { getDashboardSystemPerformance } from '@/services/rest/statistics/stastistics';
-import { InfoCircleFilled } from '@ant-design/icons';
-import { Tooltip, Typography } from 'antd';
+import { InfoCircleFilled, ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
+import { Tooltip, Typography, Card, Skeleton } from 'antd';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { API } from 'ssm-shared-lib';
+import { getSemanticColors } from './utils/colorPalettes';
-const SystemPerformanceCard: React.FC = () => {
+interface SystemPerformanceCardProps {
+ colorPalette?: string;
+ customColors?: string[];
+}
+
+const SystemPerformanceCard: React.FC = ({
+ colorPalette = 'default',
+ customColors = [],
+}) => {
const [loading, setLoading] = useState(false);
const [performancesStat, setPerformancesStat] = useState(
{
@@ -21,57 +26,28 @@ const SystemPerformanceCard: React.FC = () => {
},
);
- const asyncFetch = useCallback(async () => {
- setLoading(true);
- try {
- const response = await getDashboardSystemPerformance();
- setPerformancesStat(response.data);
- } finally {
- setLoading(false);
- }
- }, []);
-
useEffect(() => {
- void asyncFetch();
- }, [asyncFetch]);
-
- const title = useMemo(
- () => System Performance ,
- [],
- );
-
- const action = useMemo(
- () => (
-
-
-
- ),
- [],
- );
-
- const total = useMemo(
- () => (
-
- {performancesStat?.message}
-
- ),
- [performancesStat],
- );
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ console.log('📊 SystemPerformanceCard API Call: getDashboardSystemPerformance', {
+ component: 'SystemPerformanceCard',
+ timestamp: new Date().toISOString()
+ });
+ const response = await getDashboardSystemPerformance();
+ console.log('📊 SystemPerformanceCard API Response: getDashboardSystemPerformance', {
+ component: 'SystemPerformanceCard',
+ performanceData: response.data,
+ timestamp: new Date().toISOString()
+ });
+ setPerformancesStat(response.data);
+ } finally {
+ setLoading(false);
+ }
+ };
- const footer = useMemo(
- () => (
- Current Avg. CPU/Mem:}
- value={
-
- {(performancesStat?.currentCpu || NaN).toFixed(2)}%/
- {(performancesStat?.currentMem || NaN).toFixed(2)}%
-
- }
- />
- ),
- [performancesStat],
- );
+ fetchData();
+ }, []); // Runs once on mount
const cpuTrendFlag = useMemo(
() =>
@@ -89,43 +65,97 @@ const SystemPerformanceCard: React.FC = () => {
[performancesStat],
);
+ // Get semantic colors from palette
+ const semanticColors = useMemo(() => {
+ return getSemanticColors(colorPalette);
+ }, [colorPalette]);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
return (
-
-
- Weekly CPU Variation
-
-
- {(
- performancesStat?.previousCpu - performancesStat?.currentCpu
- ).toFixed(2)}
- %
+ {/* Header */}
+
+
+ System Performance
+
+
+
+
+
+
+ {/* Performance Status */}
+
+
+ {performancesStat?.message}
+
+
+
+ {/* Current Stats */}
+
+
+ Current:
+
+
+ {(performancesStat?.currentCpu || 0).toFixed(1)}% / {(performancesStat?.currentMem || 0).toFixed(1)}%
+
+
+
+ {/* Trend Information - Compact */}
+
+
+
+ CPU
+
+ {cpuTrendFlag === 'up' ? (
+
+ ) : (
+
+ )}
+
+ {Math.abs(performancesStat?.previousCpu - performancesStat?.currentCpu).toFixed(1)}%
-
-
-
- Weekly MEM Variation
-
-
- {(
- performancesStat?.previousMem - performancesStat?.currentMem
- ).toFixed(2)}
- %
+
+
+
+
+ MEM
+
+ {memTrendFlag === 'up' ? (
+
+ ) : (
+
+ )}
+
+ {Math.abs(performancesStat?.previousMem - performancesStat?.currentMem).toFixed(1)}%
-
-
-
+
+
+
);
};
-export default React.memo(SystemPerformanceCard);
+export default React.memo(SystemPerformanceCard);
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/TimeSeriesLineChart.tsx b/client/src/pages/Dashboard/Components/TimeSeriesLineChart.tsx
new file mode 100644
index 000000000..405776518
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/TimeSeriesLineChart.tsx
@@ -0,0 +1,577 @@
+import {
+ getDashboardAveragedDevicesStats,
+ getDashboardDevicesStats,
+} from '@/services/rest/statistics/stastistics';
+import Devicestatus from '@/utils/devicestatus';
+import { getTimeDistance } from '@/utils/time';
+import { LoadingOutlined } from '@ant-design/icons';
+import ReactApexChart from 'react-apexcharts';
+import type { ApexOptions } from 'apexcharts';
+import { useModel } from '@umijs/max';
+import {
+ Card,
+ Col,
+ DatePicker,
+ Flex,
+ Row,
+ Select,
+ Tabs,
+ TabsProps,
+ Typography,
+} from 'antd';
+import React, { useEffect, useState, useCallback, useMemo } from 'react';
+import { API, StatsType } from 'ssm-shared-lib';
+import styles from '../Analysis.less';
+import { getPaletteColors } from './utils/colorPalettes';
+
+const { RangePicker } = DatePicker;
+
+interface TimeSeriesLineChartProps {
+ colorPalette?: string;
+ customColors?: string[];
+}
+
+const TimeSeriesLineChart: React.FC = ({
+ colorPalette = 'default',
+ customColors = [],
+}) => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser }: { currentUser?: API.CurrentUser } = initialState || {};
+
+ const [loading, setLoading] = useState(false);
+ const [graphData, setGraphData] = useState([]);
+ const [graphMemData, setGraphMemData] = useState<
+ API.DeviceStat[] | undefined
+ >([]);
+ const [graphStorageData, setGraphStorageData] = useState<
+ API.DeviceStat[] | undefined
+ >([]);
+
+ const [topTenData, setTopTenData] = useState<
+ { name: string; value: number }[] | undefined
+ >([]);
+ const [devices, setDevices] = useState(
+ currentUser?.devices?.overview
+ ?.filter((e) => e.status !== Devicestatus.UNMANAGED)
+ .map((e) => e.uuid) || [],
+ );
+
+ // Create a mapping of UUID to device name for display purposes
+ const deviceNameMap = useMemo(() => {
+ const map = new Map();
+ currentUser?.devices?.overview?.forEach((device) => {
+ map.set(device.uuid, device.name || device.uuid);
+ });
+ return map;
+ }, [currentUser?.devices?.overview]);
+ const [type, setType] = useState(
+ StatsType.DeviceStatsType.CPU,
+ );
+ const [rangePickerValue, setRangePickerValue] = useState(
+ getTimeDistance('year'),
+ );
+
+ const isActive = useCallback(
+ (dateType: string) => {
+ const value = getTimeDistance(dateType);
+ if (!rangePickerValue[0] || !rangePickerValue[1]) {
+ return '';
+ }
+ if (
+ rangePickerValue[0].isSame(value[0], 'day') &&
+ rangePickerValue[1].isSame(value[1], 'day')
+ ) {
+ return styles.currentDate;
+ }
+ return '';
+ },
+ [rangePickerValue],
+ );
+
+ const handleRangePickerChange = useCallback((dates: any) => {
+ setRangePickerValue(dates);
+ }, []);
+
+ const selectDate = useCallback((dateType: string) => {
+ setRangePickerValue(getTimeDistance(dateType));
+ }, []);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ setLoading(true);
+ try {
+ if (devices.length > 0) {
+ setGraphData(undefined);
+ setGraphMemData(undefined);
+ setGraphStorageData(undefined);
+ setTopTenData(undefined);
+ const [deviceStats, averagedDeviceStats] = await Promise.all([
+ getDashboardDevicesStats(devices as string[], type, {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate(),
+ }),
+ getDashboardAveragedDevicesStats(devices as string[], type, {
+ from: rangePickerValue[0].toDate(),
+ to: rangePickerValue[1].toDate(),
+ }),
+ ]);
+ console.log(deviceStats.data);
+ // Transform averaged data to use device names instead of UUIDs
+ const transformedAveragedData = averagedDeviceStats.data?.map(item => {
+ let deviceName = item.name || 'Unknown';
+ // If the name looks like a UUID, try to get the actual name from our mapping
+ if (deviceName.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
+ deviceName = deviceNameMap.get(deviceName) || deviceName;
+ }
+ return {
+ ...item,
+ name: deviceName
+ };
+ });
+
+ switch (type) {
+ case StatsType.DeviceStatsType.CPU:
+ setGraphData(deviceStats.data);
+ setTopTenData(transformedAveragedData);
+ break;
+ case StatsType.DeviceStatsType.MEM_USED:
+ setGraphMemData(deviceStats.data);
+ setTopTenData(transformedAveragedData);
+ break;
+ case StatsType.DeviceStatsType.DISK_USED:
+ setGraphStorageData(deviceStats.data);
+ setTopTenData(transformedAveragedData);
+ break;
+ }
+ }
+ } catch (error) {
+ console.error('Failed to fetch data:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchData();
+ }, [devices, type, rangePickerValue, deviceNameMap]); // Direct dependencies in useEffect
+
+ // Prepare data for ApexCharts
+ const prepareChartData = useCallback((data: API.DeviceStat[] | undefined) => {
+ if (!data || data.length === 0) return { series: [], categories: [] };
+
+ // Group data by device name and collect all unique timestamps
+ const deviceMap = new Map>();
+ const allTimestamps = new Set();
+
+ data.forEach((item) => {
+ // Use the device name from the item, or try to map from UUID if needed
+ let deviceName = item.name || 'Unknown';
+
+ // If the name looks like a UUID, try to get the actual name from our mapping
+ if (deviceName.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
+ deviceName = deviceNameMap.get(deviceName) || deviceName;
+ }
+
+ if (!deviceMap.has(deviceName)) {
+ deviceMap.set(deviceName, new Map());
+ }
+
+ // Parse the date string
+ let dateStr = item.date;
+ if (typeof dateStr === 'string' && dateStr.includes('-') && dateStr.split('-').length > 3) {
+ const parts = dateStr.split('-');
+ dateStr = `${parts[0]}-${parts[1]}-${parts[2]}T${parts[3]}`;
+ }
+
+ const timestamp = new Date(dateStr).getTime();
+ allTimestamps.add(timestamp);
+
+ deviceMap.get(deviceName)!.set(timestamp, parseFloat(item.value.toFixed(2)));
+ });
+
+ // Sort timestamps
+ const sortedTimestamps = Array.from(allTimestamps).sort((a, b) => a - b);
+
+ // Convert to ApexCharts series format with aligned data points
+ const series = Array.from(deviceMap.entries()).map(([name, dataMap]) => ({
+ name,
+ data: sortedTimestamps.map(timestamp => ({
+ x: timestamp,
+ y: dataMap.get(timestamp) ?? null
+ }))
+ }));
+
+ return { series };
+ }, [deviceNameMap]);
+
+ // Get colors from palette
+ const colors = useMemo(() => {
+ return customColors && customColors.length > 0 ? customColors : getPaletteColors(colorPalette);
+ }, [colorPalette, customColors]);
+
+ const getChartOptions = useCallback((): ApexOptions => ({
+ chart: {
+ type: 'line',
+ height: 400,
+ toolbar: {
+ show: false
+ },
+ background: 'transparent',
+ animations: {
+ enabled: false
+ }
+ },
+ stroke: {
+ curve: 'smooth',
+ width: 2,
+ lineCap: 'round'
+ },
+ fill: {
+ opacity: 1
+ },
+ markers: {
+ size: 0,
+ hover: {
+ size: 5
+ }
+ },
+ xaxis: {
+ type: 'datetime',
+ labels: {
+ style: {
+ colors: '#fff',
+ fontSize: '12px'
+ },
+ datetimeFormatter: {
+ year: 'yyyy',
+ month: "MMM 'yy",
+ day: 'dd MMM',
+ hour: 'HH:mm'
+ }
+ },
+ axisBorder: {
+ color: '#3a3a3e'
+ },
+ axisTicks: {
+ color: '#3a3a3e'
+ }
+ },
+ yaxis: {
+ labels: {
+ style: {
+ colors: '#fff',
+ fontSize: '12px'
+ },
+ formatter: (value: number) => `${value.toFixed(2)}%`
+ }
+ },
+ grid: {
+ borderColor: '#3a3a3e',
+ strokeDashArray: 4,
+ yaxis: {
+ lines: {
+ show: true,
+ opacity: 0.3
+ }
+ },
+ xaxis: {
+ lines: {
+ show: false
+ }
+ }
+ },
+ legend: {
+ show: true,
+ position: 'top',
+ horizontalAlign: 'right',
+ labels: {
+ colors: '#fff'
+ }
+ },
+ tooltip: {
+ theme: 'dark',
+ enabled: true,
+ shared: true,
+ intersect: false,
+ followCursor: true,
+ x: {
+ show: true,
+ format: 'dd MMM yyyy HH:mm'
+ },
+ y: {
+ formatter: (value: number) => value ? `${value.toFixed(2)}%` : '0%'
+ },
+ marker: {
+ show: true
+ },
+ fixed: {
+ enabled: false
+ }
+ },
+ colors: colors
+ }), [colors]);
+
+ const cpuChartData = useMemo(() => prepareChartData(graphData), [graphData, prepareChartData]);
+ const memChartData = useMemo(() => prepareChartData(graphMemData), [graphMemData, prepareChartData]);
+ const storageChartData = useMemo(() => prepareChartData(graphStorageData), [graphStorageData, prepareChartData]);
+
+
+ const handleTabChange = (key: string) => {
+ setType(key as StatsType.DeviceStatsType);
+ };
+
+ const items: TabsProps['items'] = useMemo(
+ () => [
+ {
+ key: StatsType.DeviceStatsType.CPU,
+ label: 'CPU',
+ children: (
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ CPU Usage % Average Ranking
+
+
+ {topTenData?.slice(0, 10).map((item, i) => (
+
+
+ {i + 1}
+
+
+ {item.name}
+
+
+ {item.value?.toFixed(2)}%
+
+
+ ))}
+
+
+
+
+ ),
+ },
+ {
+ key: StatsType.DeviceStatsType.MEM_USED,
+ label: 'MEM',
+ children: (
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ Memory Usage % Average Ranking
+
+
+ {topTenData?.slice(0, 10).map((item, i) => (
+
+
+ {i + 1}
+
+
+ {item.name}
+
+
+ {item.value?.toFixed(2)}%
+
+
+ ))}
+
+
+
+
+ ),
+ },
+ {
+ key: StatsType.DeviceStatsType.DISK_USED,
+ label: 'DISK',
+ children: (
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+ Disk Usage % Average Ranking
+
+
+ {topTenData?.slice(0, 10).map((item, i) => (
+
+
+ {i + 1}
+
+
+ {item.name}
+
+
+ {item.value?.toFixed(2)}%
+
+
+ ))}
+
+
+
+
+ ),
+ },
+ ],
+ [cpuChartData, memChartData, storageChartData, topTenData, loading, getChartOptions],
+ );
+
+ const tabBarExtra = useMemo(
+ () => (
+
+
+
+
e.status !== Devicestatus.UNMANAGED)
+ .map((e) => ({
+ value: e.uuid,
+ label: e.name,
+ }))}
+ onChange={setDevices}
+ />
+
+ ),
+ [
+ devices,
+ rangePickerValue,
+ selectDate,
+ handleRangePickerChange,
+ isActive,
+ currentUser?.devices?.overview,
+ ],
+ );
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default React.memo(TimeSeriesLineChart);
diff --git a/client/src/pages/Dashboard/Components/WelcomeHeaderSection.tsx b/client/src/pages/Dashboard/Components/WelcomeHeaderSection.tsx
new file mode 100644
index 000000000..bc2a2ce7e
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/WelcomeHeaderSection.tsx
@@ -0,0 +1,165 @@
+import React, { useMemo } from 'react';
+import { Typography } from 'antd';
+import { useModel } from '@umijs/max';
+
+interface WelcomeHeaderSectionProps {
+ userName?: string;
+ greeting?: string;
+ subtitle?: string;
+ buttonText: string;
+ onButtonClick: () => void;
+ illustrationUrl: string;
+ style?: React.CSSProperties;
+}
+
+const WelcomeHeaderSection: React.FC = ({
+ userName: propUserName,
+ greeting = 'Welcome',
+ subtitle: propSubtitle,
+ buttonText,
+ onButtonClick,
+ illustrationUrl,
+ style,
+}) => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+
+ // Get user name from the model, fallback to prop
+ const userName = useMemo(() => currentUser?.name || propUserName || 'User', [currentUser?.name, propUserName]);
+
+ // Squirrel-themed quotes about SSM
+ const squirrelQuotes = useMemo(() => [
+ "Going nuts over server management? We've got you covered! 🥜",
+ "No more squirreling away from complex server tasks - SSM makes it simple! 🐿️",
+ "Storing your server configurations safely in our digital tree! 🌳",
+ "Busy as a squirrel? Let SSM handle your servers while you focus on what matters! ⚡",
+ "From acorn-sized servers to mighty oak infrastructures - we scale with you! 🌰➡️🌳",
+ "Don't let server management drive you nuts - SSM is here to help! 🔧",
+ "Hoarding server resources efficiently, one container at a time! 📦",
+ "Winter-proof your infrastructure with SSM's reliable management! ❄️",
+ "Cracking the code on server management - no more nutty configurations! 💻",
+ "Scurrying through your to-do list? Let SSM automate your servers! 🏃♂️",
+ "Building a nest egg of perfectly managed servers! 🏠",
+ "Going out on a limb to make server management fun and easy! 🌿",
+ "SSM: Because life's too short for manual server management! ⏰"
+ ], []);
+
+ // Get random quote
+ const randomQuote = useMemo(() => {
+ const randomIndex = Math.floor(Math.random() * squirrelQuotes.length);
+ return squirrelQuotes[randomIndex];
+ }, [squirrelQuotes]);
+
+ const subtitle = propSubtitle || randomQuote;
+ return (
+
+ {/* Background decoration bars */}
+
+
+ {/* Content */}
+
+
+ {greeting} {userName}
+ 🎉
+
+
+
+ {subtitle}
+
+
+
+ {buttonText}
+
+
+
+ {/* Character illustration */}
+
+
+ );
+};
+
+export default WelcomeHeaderSection;
diff --git a/client/src/pages/Dashboard/Components/useWidgetDebugData.tsx b/client/src/pages/Dashboard/Components/useWidgetDebugData.tsx
new file mode 100644
index 000000000..21f357a1c
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/useWidgetDebugData.tsx
@@ -0,0 +1,42 @@
+import { useEffect } from 'react';
+import { useDebugData } from './DashboardLayoutEngine/DebugDataProvider';
+
+interface WidgetDebugInfo {
+ widgetId?: string;
+ componentName: string;
+ fileName: string;
+ rawApiData?: Record;
+ processedData?: Record;
+ config?: Record;
+}
+
+export const useWidgetDebugData = (debugInfo: WidgetDebugInfo) => {
+ // This hook would be used inside widgets, but since we're wrapping from outside,
+ // we'll use a different approach
+
+ // For now, this is a placeholder for future widget integration
+ return null;
+};
+
+// Helper to create debug data for widgets
+export const createWidgetDebugData = (
+ componentName: string,
+ fileName: string,
+ props: Record
+): Record => {
+ const { children, ...safeProps } = props;
+
+ return {
+ componentName,
+ fileName,
+ props: safeProps,
+ config: {
+ dataType: safeProps.dataType,
+ source: safeProps.source,
+ metric: safeProps.metric || safeProps.metrics,
+ dateRangePreset: safeProps.dateRangePreset,
+ colorPalette: safeProps.colorPalette,
+ },
+ settings: safeProps.widgetSettings || safeProps.settings || {},
+ };
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Components/utils/colorPalettes.ts b/client/src/pages/Dashboard/Components/utils/colorPalettes.ts
new file mode 100644
index 000000000..a38f0f0d3
--- /dev/null
+++ b/client/src/pages/Dashboard/Components/utils/colorPalettes.ts
@@ -0,0 +1,174 @@
+// Predefined color palettes for charts
+export interface ColorPalette {
+ id: string;
+ name: string;
+ colors: string[];
+ description?: string;
+ semantic?: {
+ positive: string; // Success, healthy, good performance
+ negative: string; // Error, danger, poor performance
+ warning: string; // Warning, caution, medium performance
+ neutral: string; // Normal, info, neutral data
+ primary: string; // Primary accent color
+ secondary: string; // Secondary accent color
+ };
+}
+
+export const COLOR_PALETTES: Record = {
+ default: {
+ id: 'default',
+ name: 'Default',
+ colors: ['#52c41a', '#faad14', '#1890ff', '#f5222d', '#722ed1', '#13c2c2', '#fa8c16'],
+ description: 'Original SSM colors',
+ semantic: {
+ positive: '#52c41a', // Green for success
+ negative: '#f5222d', // Red for danger
+ warning: '#faad14', // Orange for warning
+ neutral: '#1890ff', // Blue for neutral/info
+ primary: '#1890ff', // Blue as primary
+ secondary: '#722ed1' // Purple as secondary
+ }
+ },
+ vibrant: {
+ id: 'vibrant',
+ name: 'Vibrant',
+ colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', '#FFD93D', '#6BCF7F'],
+ description: 'Bright and energetic',
+ semantic: {
+ positive: '#6BCF7F', // Bright green
+ negative: '#FF6B6B', // Bright red
+ warning: '#FFD93D', // Bright yellow
+ neutral: '#45B7D1', // Sky blue
+ primary: '#4ECDC4', // Teal
+ secondary: '#FFA07A' // Light salmon
+ }
+ },
+ pastel: {
+ id: 'pastel',
+ name: 'Pastel',
+ colors: ['#FFE5E5', '#E5F3FF', '#E5FFE5', '#FFF5E5', '#F5E5FF', '#E5FFF5', '#FFE5F5'],
+ description: 'Soft and subtle',
+ semantic: {
+ positive: '#E5FFE5', // Pastel green
+ negative: '#FFE5E5', // Pastel red
+ warning: '#FFF5E5', // Pastel yellow
+ neutral: '#E5F3FF', // Pastel blue
+ primary: '#E5F3FF', // Pastel blue
+ secondary: '#F5E5FF' // Pastel purple
+ }
+ },
+ monochrome: {
+ id: 'monochrome',
+ name: 'Monochrome',
+ colors: ['#4A90E2', '#5A9FEE', '#6AAEF5', '#7ABDFF', '#8AC5FF', '#9AD0FF', '#AADAFF'],
+ description: 'Single color variations',
+ semantic: {
+ positive: '#4A90E2', // Dark blue (most saturated)
+ negative: '#AADAFF', // Light blue (for contrast)
+ warning: '#7ABDFF', // Medium blue
+ neutral: '#6AAEF5', // Medium-dark blue
+ primary: '#4A90E2', // Dark blue
+ secondary: '#8AC5FF' // Light-medium blue
+ }
+ },
+ earth: {
+ id: 'earth',
+ name: 'Earth Tones',
+ colors: ['#D2691E', '#8B4513', '#A0522D', '#CD853F', '#DEB887', '#BC8F8F', '#F4A460'],
+ description: 'Natural and warm',
+ semantic: {
+ positive: '#8B4513', // Dark brown (stable)
+ negative: '#BC8F8F', // Rosy brown
+ warning: '#F4A460', // Sandy brown
+ neutral: '#D2691E', // Chocolate
+ primary: '#A0522D', // Sienna
+ secondary: '#DEB887' // Burlywood
+ }
+ },
+ neon: {
+ id: 'neon',
+ name: 'Neon',
+ colors: ['#FF073A', '#0FFFFF', '#39FF14', '#FF10F0', '#FFFF33', '#FF6600', '#9D00FF'],
+ description: 'Electric and bold',
+ semantic: {
+ positive: '#39FF14', // Neon green
+ negative: '#FF073A', // Neon red
+ warning: '#FFFF33', // Neon yellow
+ neutral: '#0FFFFF', // Cyan
+ primary: '#0FFFFF', // Cyan
+ secondary: '#FF10F0' // Neon pink
+ }
+ },
+ professional: {
+ id: 'professional',
+ name: 'Professional',
+ colors: ['#2E86AB', '#A23B72', '#F18F01', '#C73E1D', '#6A994E', '#BC4B51', '#5B8C85'],
+ description: 'Business appropriate',
+ semantic: {
+ positive: '#6A994E', // Sage green
+ negative: '#C73E1D', // Rust red
+ warning: '#F18F01', // Amber
+ neutral: '#2E86AB', // Steel blue
+ primary: '#2E86AB', // Steel blue
+ secondary: '#5B8C85' // Teal grey
+ }
+ },
+ system: {
+ id: 'system',
+ name: 'System Status',
+ colors: ['#38A169', '#3182CE', '#ECC94B', '#E53E3E', '#5e9a35', '#DD6B20', '#A0AEC0'],
+ description: 'System monitoring colors',
+ semantic: {
+ positive: '#38A169', // Green
+ negative: '#E53E3E', // Red
+ warning: '#ECC94B', // Yellow
+ neutral: '#3182CE', // Blue
+ primary: '#3182CE', // Blue
+ secondary: '#A0AEC0' // Gray
+ }
+ }
+};
+
+// Utility function to get colors for a palette
+export const getPaletteColors = (paletteId: string): string[] => {
+ return COLOR_PALETTES[paletteId]?.colors || COLOR_PALETTES.default.colors;
+};
+
+// Assign consistent colors to items based on their ID/name
+export const getColorForItem = (
+ itemId: string,
+ paletteId: string,
+ index: number = 0
+): string => {
+ const colors = getPaletteColors(paletteId);
+ // Use a hash of the itemId for consistent color assignment
+ const hash = itemId.split('').reduce((acc, char) => {
+ return char.charCodeAt(0) + ((acc << 5) - acc);
+ }, 0);
+ const colorIndex = Math.abs(hash) % colors.length;
+ return colors[colorIndex] || colors[index % colors.length];
+};
+
+// Get color by index with fallback
+export const getColorByIndex = (
+ paletteId: string,
+ index: number
+): string => {
+ const colors = getPaletteColors(paletteId);
+ return colors[index % colors.length];
+};
+
+// Get semantic color for a specific meaning
+export const getSemanticColor = (
+ paletteId: string,
+ semantic: keyof ColorPalette['semantic']
+): string => {
+ const palette = COLOR_PALETTES[paletteId] || COLOR_PALETTES.default;
+ return palette.semantic?.[semantic] || COLOR_PALETTES.default.semantic![semantic];
+};
+
+// Get all semantic colors for a palette
+export const getSemanticColors = (paletteId: string) => {
+ const palette = COLOR_PALETTES[paletteId] || COLOR_PALETTES.default;
+ return palette.semantic || COLOR_PALETTES.default.semantic!;
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/DashboardWidget.types.ts b/client/src/pages/Dashboard/Core/DashboardWidget.types.ts
new file mode 100644
index 000000000..532b36f65
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/DashboardWidget.types.ts
@@ -0,0 +1,44 @@
+/**
+ * Dashboard Widget Type Definitions
+ * Strongly typed interfaces for dashboard widgets
+ */
+
+import { WidgetConfiguration } from './WidgetSettings.types';
+
+export type DashboardItemSize = 'small' | 'medium' | 'large' | 'wide' | 'full';
+
+export interface DashboardWidget {
+ id: string;
+ widgetType: string;
+ title: string;
+ size: DashboardItemSize;
+ position: number;
+ settings?: WidgetConfiguration;
+}
+
+export interface DashboardItem {
+ id: string;
+ component: React.ReactNode;
+ size: DashboardItemSize;
+ title: string;
+ category?: 'monitoring' | 'charts' | 'tools';
+ componentFactory?: (settings: WidgetConfiguration) => React.ReactNode;
+ widgetSettings?: WidgetConfiguration;
+ hasSettings?: boolean;
+}
+
+export interface Dashboard {
+ _id?: string;
+ name: string;
+ description?: string;
+ pages: DashboardPage[];
+ createdAt?: Date;
+ updatedAt?: Date;
+}
+
+export interface DashboardPage {
+ id: string;
+ name: string;
+ isDefault: boolean;
+ widgets: DashboardWidget[];
+}
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/README.md b/client/src/pages/Dashboard/Core/README.md
new file mode 100644
index 000000000..200613c64
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/README.md
@@ -0,0 +1,220 @@
+# Dashboard Widget Settings Framework
+
+A comprehensive, type-safe framework for managing dashboard widget configurations.
+
+## Overview
+
+This framework provides:
+- **Strong TypeScript typing** - No "any" types, full type safety
+- **Schema-based validation** - Define widget settings with validation rules
+- **Automatic UI generation** - Settings UI is generated from schemas
+- **Extensible field types** - Built-in types + custom field support
+- **React hooks integration** - Easy to use in React components
+
+## Architecture
+
+```
+Core/
+├── WidgetSettings.types.ts # Core type definitions
+├── WidgetSettingsManager.ts # Settings validation & management
+├── WidgetSettingsProvider.tsx # React context & hooks
+├── WidgetSettingsRenderer.tsx # Auto-generates settings UI
+├── WidgetSettingsDrawer.tsx # Settings drawer component
+├── DashboardWidget.types.ts # Dashboard widget types
+├── schemas/ # Widget settings schemas
+└── components/ # Reusable field components
+ ├── StatisticsSelector.tsx
+ └── PlaybookSelector.tsx
+```
+
+## Usage
+
+### 1. Define a Widget Settings Schema
+
+```typescript
+import { WidgetSettingsSchema } from '../Core/WidgetSettings.types';
+
+const myWidgetSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Widget Title',
+ required: true,
+ defaultValue: 'My Widget',
+ },
+ refreshInterval: {
+ type: 'number',
+ label: 'Refresh Interval (seconds)',
+ min: 10,
+ max: 300,
+ defaultValue: 60,
+ },
+ theme: {
+ type: 'select',
+ label: 'Theme',
+ options: [
+ { label: 'Light', value: 'light' },
+ { label: 'Dark', value: 'dark' },
+ ],
+ defaultValue: 'light',
+ },
+ },
+ layout: {
+ type: 'sections',
+ groups: [
+ {
+ id: 'basic',
+ label: 'Basic Settings',
+ fields: ['title', 'theme'],
+ },
+ {
+ id: 'advanced',
+ label: 'Advanced',
+ fields: ['refreshInterval'],
+ },
+ ],
+ },
+};
+```
+
+### 2. Register the Schema
+
+```typescript
+import { widgetSettingsManager } from '../Core/WidgetSettingsManager';
+
+widgetSettingsManager.registerSchema('my-widget', myWidgetSchema);
+```
+
+### 3. Create Your Widget Component
+
+```typescript
+import React from 'react';
+import { WidgetConfiguration } from '../Core/WidgetSettings.types';
+
+interface MyWidgetProps {
+ configuration?: WidgetConfiguration;
+}
+
+const MyWidget: React.FC = ({ configuration }) => {
+ // Extract typed settings
+ const title = configuration?.title as string || 'Default Title';
+ const refreshInterval = configuration?.refreshInterval as number || 60;
+ const theme = configuration?.theme as 'light' | 'dark' || 'light';
+
+ return (
+
+
{title}
+ {/* Widget content */}
+
+ );
+};
+```
+
+### 4. Add to Dashboard Factory
+
+```typescript
+const dashboardItems: DashboardItem[] = [
+ {
+ id: 'my-widget',
+ title: 'My Widget',
+ size: 'medium',
+ component: ,
+ componentFactory: (configuration: WidgetConfiguration) => (
+
+ ),
+ hasSettings: true,
+ },
+];
+```
+
+## Built-in Field Types
+
+- **text** - Text input with validation
+- **number** - Number input with min/max
+- **boolean** - Switch/toggle
+- **select** - Single or multi-select dropdown
+- **dateRange** - Date range picker with presets
+- **colorPalette** - Color palette selector
+- **statistics** - Device/container statistics selector
+- **playbook** - Ansible playbook selector
+- **custom** - Custom React component
+
+## Custom Field Example
+
+```typescript
+const customField: CustomSettingField = {
+ type: 'custom',
+ label: 'Tag Selector',
+ component: ({ value, onChange }) => (
+
+ ),
+};
+```
+
+## Validation
+
+```typescript
+const urlField: TextSettingField = {
+ type: 'text',
+ label: 'API URL',
+ validation: {
+ validate: (value: string) => {
+ try {
+ new URL(value);
+ return true;
+ } catch {
+ return 'Please enter a valid URL';
+ }
+ },
+ },
+};
+```
+
+## React Hooks
+
+```typescript
+// Use in settings components
+const { configuration, updateConfiguration, isValid } =
+ useWidgetConfiguration('my-widget');
+
+// Access settings context
+const { getSchema, validateConfiguration } = useWidgetSettings();
+```
+
+## Type Safety
+
+All settings are strongly typed with no "any" types:
+
+```typescript
+// Configuration is typed
+interface StatisticsConfig {
+ dataType: 'device' | 'container';
+ source: string[];
+ metric: string;
+ aggregation?: 'average' | 'sum' | 'min' | 'max';
+}
+
+// Field values are typed
+const statsField: StatisticsSettingField = {
+ type: 'statistics',
+ label: 'Data Configuration',
+ defaultValue: {
+ dataType: 'device',
+ source: ['all'],
+ metric: 'cpu_usage',
+ },
+};
+```
+
+## Benefits
+
+1. **No more "any" types** - Full TypeScript type safety
+2. **Consistent UI** - All settings follow the same patterns
+3. **Validation built-in** - Catch errors before saving
+4. **Extensible** - Easy to add new field types
+5. **Reusable** - Components can be shared across widgets
+6. **Maintainable** - Schema changes automatically update UI
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/WidgetSettings.types.ts b/client/src/pages/Dashboard/Core/WidgetSettings.types.ts
new file mode 100644
index 000000000..efcb333be
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/WidgetSettings.types.ts
@@ -0,0 +1,206 @@
+/**
+ * Core type definitions for the widget settings framework
+ * This provides a type-safe, extensible system for widget configuration
+ */
+
+import { Moment } from 'moment';
+import { API } from 'ssm-shared-lib';
+
+// Base setting types
+export type SettingValueType =
+ | string
+ | number
+ | boolean
+ | string[]
+ | number[]
+ | DateRange
+ | ColorPalette
+ | StatisticsConfig
+ | PlaybookConfig
+ | Record;
+
+// Date range configuration
+export interface DateRange {
+ preset: 'last24hours' | 'last7days' | 'last30days' | 'last3months' | 'last6months' | 'lastyear' | 'custom';
+ customRange?: [Moment, Moment];
+}
+
+// Color palette configuration
+export interface ColorPalette {
+ id: 'default' | 'modern' | 'vibrant' | 'pastel' | 'dark' | 'custom';
+ colors?: string[];
+}
+
+// Statistics widget configuration
+export interface StatisticsConfig {
+ dataType: 'device' | 'container';
+ source: string[];
+ metric: string;
+ aggregation?: 'average' | 'sum' | 'min' | 'max';
+}
+
+// Playbook widget configuration
+export interface PlaybookConfig {
+ selectedPlaybooks: Array<{
+ uuid: string;
+ deviceUuids: string[];
+ }>;
+}
+
+// Setting field types
+export interface BaseSettingField {
+ type: string;
+ label: string;
+ description?: string;
+ defaultValue?: T;
+ required?: boolean;
+ validation?: SettingValidation;
+}
+
+export interface TextSettingField extends BaseSettingField {
+ type: 'text';
+ placeholder?: string;
+ minLength?: number;
+ maxLength?: number;
+}
+
+export interface NumberSettingField extends BaseSettingField {
+ type: 'number';
+ min?: number;
+ max?: number;
+ step?: number;
+}
+
+export interface BooleanSettingField extends BaseSettingField {
+ type: 'boolean';
+}
+
+export interface SelectSettingField extends BaseSettingField {
+ type: 'select';
+ options: Array<{
+ label: string;
+ value: T;
+ icon?: React.ReactNode;
+ }>;
+ mode?: 'single' | 'multiple';
+}
+
+export interface DateRangeSettingField extends BaseSettingField {
+ type: 'dateRange';
+ presets?: Array<{
+ label: string;
+ value: DateRange['preset'];
+ }>;
+}
+
+export interface ColorPaletteSettingField extends BaseSettingField {
+ type: 'colorPalette';
+ allowCustom?: boolean;
+ maxColors?: number;
+}
+
+export interface StatisticsSettingField extends BaseSettingField {
+ type: 'statistics';
+ supportedDataTypes?: Array<'device' | 'container'>;
+ supportedMetrics?: Record;
+ selectionMode?: 'single' | 'multiple';
+}
+
+export interface PlaybookSettingField extends BaseSettingField {
+ type: 'playbook';
+ maxPlaybooks?: number;
+ requireDeviceSelection?: boolean;
+}
+
+export interface CustomSettingField extends BaseSettingField {
+ type: 'custom';
+ component: React.ComponentType>;
+}
+
+// Union of all setting field types
+export type SettingField =
+ | TextSettingField
+ | NumberSettingField
+ | BooleanSettingField
+ | SelectSettingField
+ | DateRangeSettingField
+ | ColorPaletteSettingField
+ | StatisticsSettingField
+ | PlaybookSettingField
+ | CustomSettingField;
+
+// Validation types
+export interface SettingValidation {
+ validate: (value: T) => boolean | string;
+ message?: string;
+}
+
+// Component props for custom settings
+export interface CustomSettingComponentProps {
+ value: T | undefined;
+ onChange: (value: T) => void;
+ field: CustomSettingField;
+ disabled?: boolean;
+}
+
+// Widget settings schema
+export interface WidgetSettingsSchema {
+ version: '1.0';
+ fields: Record;
+ layout?: SettingLayout;
+ dependencies?: SettingDependencies;
+}
+
+// Layout configuration
+export interface SettingLayout {
+ type: 'default' | 'tabs' | 'sections';
+ groups?: Array<{
+ id: string;
+ label: string;
+ fields: string[];
+ collapsible?: boolean;
+ defaultExpanded?: boolean;
+ }>;
+}
+
+// Field dependencies
+export interface SettingDependencies {
+ [fieldName: string]: {
+ dependsOn: string;
+ condition: (value: SettingValueType) => boolean;
+ };
+}
+
+// Widget configuration result
+export interface WidgetConfiguration {
+ [key: string]: SettingValueType;
+}
+
+// Widget settings manager interface
+export interface IWidgetSettingsManager {
+ getSchema(widgetType: string): WidgetSettingsSchema | undefined;
+ validateConfiguration(widgetType: string, config: WidgetConfiguration): ValidationResult;
+ getDefaultConfiguration(widgetType: string): WidgetConfiguration;
+ serializeConfiguration(config: WidgetConfiguration): string;
+ deserializeConfiguration(data: string): WidgetConfiguration;
+}
+
+// Validation result
+export interface ValidationResult {
+ valid: boolean;
+ errors: Array<{
+ field: string;
+ message: string;
+ }>;
+}
+
+// Widget with settings
+export interface WidgetWithSettings {
+ id: string;
+ type: string;
+ configuration: WidgetConfiguration;
+ metadata?: {
+ version: string;
+ lastModified: Date;
+ };
+}
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/WidgetSettingsDrawer.tsx b/client/src/pages/Dashboard/Core/WidgetSettingsDrawer.tsx
new file mode 100644
index 000000000..fe465fae3
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/WidgetSettingsDrawer.tsx
@@ -0,0 +1,123 @@
+/**
+ * Widget Settings Drawer
+ * Renders widget settings using the settings framework
+ */
+
+import React, { useCallback, useEffect } from 'react';
+import { Drawer, Form, Button, Space, message, Typography, Divider } from 'antd';
+import { ProForm } from '@ant-design/pro-components';
+import { WidgetSettingsRenderer } from './WidgetSettingsRenderer';
+import { useWidgetConfiguration, useWidgetSettings } from './WidgetSettingsProvider';
+import { DashboardItem } from './DashboardWidget.types';
+import { WidgetConfiguration } from './WidgetSettings.types';
+
+interface WidgetSettingsDrawerProps {
+ visible: boolean;
+ widget: DashboardItem | null;
+ onClose: () => void;
+ onSave: (widgetId: string, configuration: WidgetConfiguration) => void;
+}
+
+export const WidgetSettingsDrawer: React.FC = ({
+ visible,
+ widget,
+ onClose,
+ onSave,
+}) => {
+ const { getSchema } = useWidgetSettings();
+ const [form] = Form.useForm();
+
+ // Extract widget type from widget ID (remove timestamp suffix)
+ const widgetType = widget ? widget.id.split('-').slice(0, -1).join('-') : '';
+ const schema = widget ? getSchema(widgetType) : undefined;
+
+ const {
+ configuration,
+ validation,
+ updateConfiguration,
+ resetConfiguration,
+ isValid,
+ } = useWidgetConfiguration(widgetType, widget?.widgetSettings);
+
+ useEffect(() => {
+ if (widget) {
+ // Reset form with current configuration when widget changes
+ form.setFieldsValue({ settings: configuration });
+ }
+ }, [widget, configuration, form]);
+
+ const handleSave = useCallback(async () => {
+ if (!widget || !isValid) {
+ if (validation.errors.length > 0) {
+ message.error(validation.errors[0].message);
+ }
+ return;
+ }
+
+ try {
+ await form.validateFields();
+ onSave(widget.id, configuration);
+ onClose();
+ message.success('Widget settings saved successfully');
+ } catch (error) {
+ console.error('Failed to save settings:', error);
+ message.error('Failed to save widget settings');
+ }
+ }, [widget, isValid, validation, configuration, form, onSave, onClose]);
+
+ const handleReset = useCallback(() => {
+ resetConfiguration();
+ form.setFieldsValue({ settings: configuration });
+ }, [resetConfiguration, configuration, form]);
+
+ if (!widget || !schema) {
+ return null;
+ }
+
+ const errors = validation.errors.reduce((acc, error) => {
+ acc[error.field] = error.message;
+ return acc;
+ }, {} as Record);
+
+ return (
+
+ Cancel
+ Reset
+
+ Apply
+
+
+ }
+ >
+ {/* Component Name Header */}
+
+
+ {widget.title}
+
+
+ Component Type: {widgetType}
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/WidgetSettingsManager.ts b/client/src/pages/Dashboard/Core/WidgetSettingsManager.ts
new file mode 100644
index 000000000..fcab7906a
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/WidgetSettingsManager.ts
@@ -0,0 +1,306 @@
+/**
+ * Widget Settings Manager
+ * Handles validation, serialization, and management of widget configurations
+ */
+
+import {
+ WidgetSettingsSchema,
+ WidgetConfiguration,
+ ValidationResult,
+ IWidgetSettingsManager,
+ SettingField,
+ SettingValueType,
+} from './WidgetSettings.types';
+
+export class WidgetSettingsManager implements IWidgetSettingsManager {
+ private schemas: Map = new Map();
+
+ /**
+ * Register a widget settings schema
+ */
+ registerSchema(widgetType: string, schema: WidgetSettingsSchema): void {
+ this.schemas.set(widgetType, schema);
+ }
+
+ /**
+ * Get schema for a widget type
+ */
+ getSchema(widgetType: string): WidgetSettingsSchema | undefined {
+ return this.schemas.get(widgetType);
+ }
+
+ /**
+ * Validate a widget configuration against its schema
+ */
+ validateConfiguration(widgetType: string, config: WidgetConfiguration): ValidationResult {
+ const schema = this.getSchema(widgetType);
+ if (!schema) {
+ return {
+ valid: false,
+ errors: [{ field: '', message: `No schema found for widget type: ${widgetType}` }],
+ };
+ }
+
+ const errors: ValidationResult['errors'] = [];
+
+ // Validate each field
+ Object.entries(schema.fields).forEach(([fieldName, field]) => {
+ const value = config[fieldName];
+
+ // Check required fields
+ if (field.required && (value === undefined || value === null || value === '')) {
+ errors.push({
+ field: fieldName,
+ message: `${field.label} is required`,
+ });
+ return;
+ }
+
+ // Skip validation if value is not provided and field is optional
+ if (value === undefined || value === null) {
+ return;
+ }
+
+ // Type-specific validation
+ const fieldError = this.validateField(field, value);
+ if (fieldError) {
+ errors.push({
+ field: fieldName,
+ message: fieldError,
+ });
+ }
+
+ // Custom validation
+ if (field.validation) {
+ const validationResult = field.validation.validate(value);
+ if (validationResult !== true) {
+ errors.push({
+ field: fieldName,
+ message: typeof validationResult === 'string' ? validationResult : field.validation.message || 'Validation failed',
+ });
+ }
+ }
+ });
+
+ // Check for unknown fields
+ Object.keys(config).forEach(fieldName => {
+ if (!schema.fields[fieldName]) {
+ errors.push({
+ field: fieldName,
+ message: `Unknown field: ${fieldName}`,
+ });
+ }
+ });
+
+ // Validate dependencies
+ if (schema.dependencies) {
+ Object.entries(schema.dependencies).forEach(([fieldName, dependency]) => {
+ const dependentValue = config[dependency.dependsOn];
+ const shouldShow = dependency.condition(dependentValue);
+
+ if (!shouldShow && config[fieldName] !== undefined) {
+ // Field should be hidden but has a value
+ delete config[fieldName];
+ }
+ });
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors,
+ };
+ }
+
+ /**
+ * Validate a single field value
+ */
+ private validateField(field: SettingField, value: SettingValueType): string | null {
+ switch (field.type) {
+ case 'text': {
+ if (typeof value !== 'string') {
+ return `Expected string, got ${typeof value}`;
+ }
+ const textField = field as import('./WidgetSettings.types').TextSettingField;
+ if (textField.minLength && value.length < textField.minLength) {
+ return `Minimum length is ${textField.minLength}`;
+ }
+ if (textField.maxLength && value.length > textField.maxLength) {
+ return `Maximum length is ${textField.maxLength}`;
+ }
+ break;
+ }
+
+ case 'number': {
+ if (typeof value !== 'number') {
+ return `Expected number, got ${typeof value}`;
+ }
+ const numberField = field as import('./WidgetSettings.types').NumberSettingField;
+ if (numberField.min !== undefined && value < numberField.min) {
+ return `Minimum value is ${numberField.min}`;
+ }
+ if (numberField.max !== undefined && value > numberField.max) {
+ return `Maximum value is ${numberField.max}`;
+ }
+ break;
+ }
+
+ case 'boolean': {
+ if (typeof value !== 'boolean') {
+ return `Expected boolean, got ${typeof value}`;
+ }
+ break;
+ }
+
+ case 'select': {
+ const selectField = field as import('./WidgetSettings.types').SelectSettingField;
+ if (selectField.mode === 'multiple') {
+ if (!Array.isArray(value)) {
+ return `Expected array for multi-select`;
+ }
+ const invalidValues = (value as Array).filter(
+ v => !selectField.options.some(opt => opt.value === v)
+ );
+ if (invalidValues.length > 0) {
+ return `Invalid values: ${invalidValues.join(', ')}`;
+ }
+ } else {
+ const validValues = selectField.options.map(opt => opt.value);
+ if (!validValues.includes(value as string | number)) {
+ return `Invalid value. Expected one of: ${validValues.join(', ')}`;
+ }
+ }
+ break;
+ }
+
+ case 'dateRange': {
+ const dateValue = value as import('./WidgetSettings.types').DateRange;
+ if (!dateValue || typeof dateValue !== 'object') {
+ return `Expected date range object`;
+ }
+ if (!dateValue.preset) {
+ return `Date range preset is required`;
+ }
+ if (dateValue.preset === 'custom' && !dateValue.customRange) {
+ return `Custom date range is required when preset is 'custom'`;
+ }
+ break;
+ }
+
+ case 'colorPalette': {
+ const colorValue = value as import('./WidgetSettings.types').ColorPalette;
+ if (!colorValue || typeof colorValue !== 'object') {
+ return `Expected color palette object`;
+ }
+ if (!colorValue.id) {
+ return `Color palette ID is required`;
+ }
+ if (colorValue.id === 'custom' && (!colorValue.colors || colorValue.colors.length === 0)) {
+ return `Custom colors are required when palette is 'custom'`;
+ }
+ break;
+ }
+
+ case 'statistics': {
+ const statsValue = value as import('./WidgetSettings.types').StatisticsConfig;
+ if (!statsValue || typeof statsValue !== 'object') {
+ return `Expected statistics configuration object`;
+ }
+ if (!statsValue.dataType || !statsValue.source || !statsValue.metric) {
+ return `Data type, source, and metric are required`;
+ }
+ break;
+ }
+
+ case 'playbook': {
+ const playbookValue = value as import('./WidgetSettings.types').PlaybookConfig;
+ if (!playbookValue || typeof playbookValue !== 'object') {
+ return `Expected playbook configuration object`;
+ }
+ if (!Array.isArray(playbookValue.selectedPlaybooks)) {
+ return `Selected playbooks must be an array`;
+ }
+ break;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get default configuration for a widget type
+ */
+ getDefaultConfiguration(widgetType: string): WidgetConfiguration {
+ const schema = this.getSchema(widgetType);
+ if (!schema) {
+ return {};
+ }
+
+ const config: WidgetConfiguration = {};
+
+ Object.entries(schema.fields).forEach(([fieldName, field]) => {
+ if (field.defaultValue !== undefined) {
+ config[fieldName] = field.defaultValue;
+ }
+ });
+
+ return config;
+ }
+
+ /**
+ * Serialize configuration to JSON string
+ */
+ serializeConfiguration(config: WidgetConfiguration): string {
+ return JSON.stringify(config, null, 2);
+ }
+
+ /**
+ * Deserialize configuration from JSON string
+ */
+ deserializeConfiguration(data: string): WidgetConfiguration {
+ try {
+ return JSON.parse(data);
+ } catch (error) {
+ console.error('Failed to deserialize configuration:', error);
+ return {};
+ }
+ }
+
+ /**
+ * Merge configurations with defaults
+ */
+ mergeWithDefaults(widgetType: string, config: Partial): WidgetConfiguration {
+ const defaults = this.getDefaultConfiguration(widgetType);
+ return {
+ ...defaults,
+ ...config,
+ };
+ }
+
+ /**
+ * Get visible fields based on dependencies
+ */
+ getVisibleFields(widgetType: string, config: WidgetConfiguration): string[] {
+ const schema = this.getSchema(widgetType);
+ if (!schema) {
+ return [];
+ }
+
+ const visibleFields = Object.keys(schema.fields);
+
+ if (schema.dependencies) {
+ return visibleFields.filter(fieldName => {
+ const dependency = schema.dependencies![fieldName];
+ if (!dependency) {
+ return true;
+ }
+ const dependentValue = config[dependency.dependsOn];
+ return dependency.condition(dependentValue);
+ });
+ }
+
+ return visibleFields;
+ }
+}
+
+// Singleton instance
+export const widgetSettingsManager = new WidgetSettingsManager();
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/WidgetSettingsProvider.tsx b/client/src/pages/Dashboard/Core/WidgetSettingsProvider.tsx
new file mode 100644
index 000000000..0c45bdbf2
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/WidgetSettingsProvider.tsx
@@ -0,0 +1,114 @@
+/**
+ * Widget Settings Provider
+ * React context and hooks for widget settings management
+ */
+
+import React, { createContext, useContext, useCallback, useMemo } from 'react';
+import { widgetSettingsManager } from './WidgetSettingsManager';
+import {
+ WidgetSettingsSchema,
+ WidgetConfiguration,
+ ValidationResult,
+} from './WidgetSettings.types';
+
+interface WidgetSettingsContextValue {
+ getSchema: (widgetType: string) => WidgetSettingsSchema | undefined;
+ validateConfiguration: (widgetType: string, config: WidgetConfiguration) => ValidationResult;
+ getDefaultConfiguration: (widgetType: string) => WidgetConfiguration;
+ mergeWithDefaults: (widgetType: string, config: Partial) => WidgetConfiguration;
+ getVisibleFields: (widgetType: string, config: WidgetConfiguration) => string[];
+}
+
+const WidgetSettingsContext = createContext(undefined);
+
+interface WidgetSettingsProviderProps {
+ children: React.ReactNode;
+}
+
+export const WidgetSettingsProvider: React.FC = ({ children }) => {
+ const getSchema = useCallback((widgetType: string) => {
+ return widgetSettingsManager.getSchema(widgetType);
+ }, []);
+
+ const validateConfiguration = useCallback((widgetType: string, config: WidgetConfiguration) => {
+ return widgetSettingsManager.validateConfiguration(widgetType, config);
+ }, []);
+
+ const getDefaultConfiguration = useCallback((widgetType: string) => {
+ return widgetSettingsManager.getDefaultConfiguration(widgetType);
+ }, []);
+
+ const mergeWithDefaults = useCallback((widgetType: string, config: Partial) => {
+ return widgetSettingsManager.mergeWithDefaults(widgetType, config);
+ }, []);
+
+ const getVisibleFields = useCallback((widgetType: string, config: WidgetConfiguration) => {
+ return widgetSettingsManager.getVisibleFields(widgetType, config);
+ }, []);
+
+ const value = useMemo(() => ({
+ getSchema,
+ validateConfiguration,
+ getDefaultConfiguration,
+ mergeWithDefaults,
+ getVisibleFields,
+ }), [getSchema, validateConfiguration, getDefaultConfiguration, mergeWithDefaults, getVisibleFields]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+/**
+ * Hook to access widget settings functionality
+ */
+export const useWidgetSettings = () => {
+ const context = useContext(WidgetSettingsContext);
+ if (!context) {
+ throw new Error('useWidgetSettings must be used within a WidgetSettingsProvider');
+ }
+ return context;
+};
+
+/**
+ * Hook to manage a specific widget's configuration
+ */
+export const useWidgetConfiguration = (
+ widgetType: string,
+ initialConfig?: Partial
+) => {
+ const { getDefaultConfiguration, validateConfiguration, mergeWithDefaults } = useWidgetSettings();
+
+ const [configuration, setConfiguration] = React.useState(() => {
+ return mergeWithDefaults(widgetType, initialConfig || {});
+ });
+
+ const [validation, setValidation] = React.useState(() => {
+ return validateConfiguration(widgetType, configuration);
+ });
+
+ const updateConfiguration = useCallback((updates: Partial) => {
+ setConfiguration(prev => {
+ const newConfig = { ...prev, ...updates };
+ const validationResult = validateConfiguration(widgetType, newConfig);
+ setValidation(validationResult);
+ return newConfig;
+ });
+ }, [widgetType, validateConfiguration]);
+
+ const resetConfiguration = useCallback(() => {
+ const defaultConfig = getDefaultConfiguration(widgetType);
+ setConfiguration(defaultConfig);
+ setValidation(validateConfiguration(widgetType, defaultConfig));
+ }, [widgetType, getDefaultConfiguration, validateConfiguration]);
+
+ return {
+ configuration,
+ validation,
+ updateConfiguration,
+ resetConfiguration,
+ isValid: validation.valid,
+ };
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/WidgetSettingsRenderer.tsx b/client/src/pages/Dashboard/Core/WidgetSettingsRenderer.tsx
new file mode 100644
index 000000000..4f03de6a1
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/WidgetSettingsRenderer.tsx
@@ -0,0 +1,322 @@
+/**
+ * Widget Settings Renderer
+ * Automatically renders widget settings based on schema
+ */
+
+import React from 'react';
+import {
+ Form,
+ Input,
+ InputNumber,
+ Switch,
+ Select,
+ DatePicker,
+ Space,
+ Typography,
+ Collapse,
+ Tabs,
+} from 'antd';
+import { ProFormDependency } from '@ant-design/pro-components';
+import moment from 'moment';
+import {
+ WidgetSettingsSchema,
+ WidgetConfiguration,
+ SettingField,
+ TextSettingField,
+ NumberSettingField,
+ BooleanSettingField,
+ SelectSettingField,
+ DateRangeSettingField,
+ ColorPaletteSettingField,
+ StatisticsSettingField,
+ PlaybookSettingField,
+ CustomSettingField,
+} from './WidgetSettings.types';
+import { useWidgetSettings } from './WidgetSettingsProvider';
+import ColorPaletteSelector from '../Components/ColorPaletteSelector';
+import StatisticsSelector from './components/StatisticsSelector';
+import PlaybookSelector from './components/PlaybookSelector';
+
+const { Text } = Typography;
+const { RangePicker } = DatePicker;
+
+interface WidgetSettingsRendererProps {
+ schema: WidgetSettingsSchema;
+ configuration: WidgetConfiguration;
+ onChange: (updates: Partial) => void;
+ errors?: Record;
+}
+
+export const WidgetSettingsRenderer: React.FC = ({
+ schema,
+ configuration,
+ onChange,
+ errors = {},
+}) => {
+ const { getVisibleFields } = useWidgetSettings();
+ const visibleFields = getVisibleFields(schema.fields, configuration);
+
+ const renderField = (fieldName: string, field: SettingField) => {
+ if (!visibleFields.includes(fieldName)) {
+ return null;
+ }
+
+ const value = configuration[fieldName];
+ const error = errors[fieldName];
+
+ const commonProps = {
+ name: fieldName,
+ label: field.label,
+ tooltip: field.description,
+ rules: field.required ? [{ required: true, message: `${field.label} is required` }] : undefined,
+ validateStatus: error ? 'error' : undefined,
+ help: error,
+ };
+
+ switch (field.type) {
+ case 'text':
+ return (
+
+ onChange({ [fieldName]: e.target.value })}
+ placeholder={(field as TextSettingField).placeholder}
+ minLength={(field as TextSettingField).minLength}
+ maxLength={(field as TextSettingField).maxLength}
+ />
+
+ );
+
+ case 'number':
+ return (
+
+ onChange({ [fieldName]: val })}
+ min={(field as NumberSettingField).min}
+ max={(field as NumberSettingField).max}
+ step={(field as NumberSettingField).step}
+ style={{ width: '100%' }}
+ />
+
+ );
+
+ case 'boolean':
+ return (
+
+ onChange({ [fieldName]: checked })}
+ />
+
+ );
+
+ case 'select': {
+ const selectField = field as SelectSettingField;
+ return (
+
+ onChange({ [fieldName]: val })}
+ mode={selectField.mode === 'multiple' ? 'multiple' : undefined}
+ options={selectField.options}
+ placeholder={`Select ${field.label.toLowerCase()}`}
+ />
+
+ );
+ }
+
+ case 'dateRange': {
+ const dateField = field as DateRangeSettingField;
+ const dateValue = value as import('./WidgetSettings.types').DateRange | undefined;
+
+ return (
+
+
+ onChange({
+ [fieldName]: {
+ ...dateValue,
+ preset: preset as import('./WidgetSettings.types').DateRange['preset']
+ }
+ })}
+ options={dateField.presets || [
+ { label: 'Last 24 Hours', value: 'last24hours' },
+ { label: 'Last 7 Days', value: 'last7days' },
+ { label: 'Last 30 Days', value: 'last30days' },
+ { label: 'Last 3 Months', value: 'last3months' },
+ { label: 'Last 6 Months', value: 'last6months' },
+ { label: 'Last Year', value: 'lastyear' },
+ { label: 'Custom Range', value: 'custom' },
+ ]}
+ />
+
+
+ {dateValue?.preset === 'custom' && (
+
+ {
+ if (dates && dates[0] && dates[1]) {
+ onChange({
+ [fieldName]: {
+ ...dateValue,
+ customRange: [dates[0], dates[1]]
+ }
+ });
+ }
+ }}
+ style={{ width: '100%' }}
+ />
+
+ )}
+
+ );
+ }
+
+ case 'colorPalette': {
+ const colorField = field as ColorPaletteSettingField;
+ const colorValue = value as import('./WidgetSettings.types').ColorPalette | undefined;
+
+ return (
+
+ {
+ onChange({
+ [fieldName]: {
+ id: paletteId as import('./WidgetSettings.types').ColorPalette['id'],
+ colors: customColors,
+ }
+ });
+ }}
+ />
+
+ );
+ }
+
+ case 'statistics': {
+ const statsField = field as StatisticsSettingField;
+ const statsValue = value as import('./WidgetSettings.types').StatisticsConfig | undefined;
+
+ return (
+
+ onChange({ [fieldName]: val })}
+ supportedDataTypes={statsField.supportedDataTypes}
+ supportedMetrics={statsField.supportedMetrics}
+ selectionMode={statsField.selectionMode}
+ />
+
+ );
+ }
+
+ case 'playbook': {
+ const playbookField = field as PlaybookSettingField;
+ const playbookValue = value as import('./WidgetSettings.types').PlaybookConfig | undefined;
+
+ return (
+
+ onChange({ [fieldName]: val })}
+ maxPlaybooks={playbookField.maxPlaybooks}
+ requireDeviceSelection={playbookField.requireDeviceSelection}
+ />
+
+ );
+ }
+
+ case 'custom': {
+ const customField = field as CustomSettingField;
+ const Component = customField.component;
+
+ return (
+
+ onChange({ [fieldName]: val })}
+ field={customField}
+ />
+
+ );
+ }
+
+ default:
+ return null;
+ }
+ };
+
+ const renderContent = () => {
+ if (!schema.layout || schema.layout.type === 'default') {
+ // Default layout: render all fields in order
+ return (
+
+ {Object.entries(schema.fields).map(([fieldName, field]) => (
+
+ {renderField(fieldName, field)}
+
+ ))}
+
+ );
+ }
+
+ if (schema.layout.type === 'sections' && schema.layout.groups) {
+ // Sections layout: render fields in collapsible groups
+ return (
+ g.defaultExpanded !== false)
+ .map(g => g.id)}
+ >
+ {schema.layout.groups.map(group => (
+
+
+ {group.fields.map(fieldName => {
+ const field = schema.fields[fieldName];
+ return field ? (
+
+ {renderField(fieldName, field)}
+
+ ) : null;
+ })}
+
+
+ ))}
+
+ );
+ }
+
+ if (schema.layout.type === 'tabs' && schema.layout.groups) {
+ // Tabs layout: render fields in tabs
+ return (
+ ({
+ key: group.id,
+ label: group.label,
+ children: (
+
+ {group.fields.map(fieldName => {
+ const field = schema.fields[fieldName];
+ return field ? (
+
+ {renderField(fieldName, field)}
+
+ ) : null;
+ })}
+
+ ),
+ }))}
+ />
+ );
+ }
+
+ return null;
+ };
+
+ return {renderContent()}
;
+};
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/components/PlaybookSelector.tsx b/client/src/pages/Dashboard/Core/components/PlaybookSelector.tsx
new file mode 100644
index 000000000..f39bcd436
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/components/PlaybookSelector.tsx
@@ -0,0 +1,231 @@
+/**
+ * Playbook Selector Component
+ * Provides UI for selecting playbooks and their target devices
+ */
+
+import React, { useState, useEffect } from 'react';
+import { Select, Space, Typography, Checkbox, Alert, Spin, Button, Tag } from 'antd';
+import { getPlaybooks } from '@/services/rest/playbooks/playbooks';
+import { getAllDevices } from '@/services/rest/devices/devices';
+import { PlaybookConfig } from '../WidgetSettings.types';
+import { API } from 'ssm-shared-lib';
+
+const { Text } = Typography;
+
+interface PlaybookSelectorProps {
+ value?: PlaybookConfig;
+ onChange: (value: PlaybookConfig) => void;
+ maxPlaybooks?: number;
+ requireDeviceSelection?: boolean;
+ disabled?: boolean;
+}
+
+const PlaybookSelector: React.FC = ({
+ value,
+ onChange,
+ maxPlaybooks,
+ requireDeviceSelection = false,
+ disabled = false,
+}) => {
+ const [playbooks, setPlaybooks] = useState([]);
+ const [devices, setDevices] = useState([]);
+ const [loadingPlaybooks, setLoadingPlaybooks] = useState(false);
+ const [loadingDevices, setLoadingDevices] = useState(false);
+
+ const currentValue: PlaybookConfig = value || {
+ selectedPlaybooks: [],
+ };
+
+ useEffect(() => {
+ // Load playbooks
+ setLoadingPlaybooks(true);
+ getPlaybooks()
+ .then(response => {
+ if (response.data) {
+ setPlaybooks(response.data);
+ }
+ })
+ .catch(error => {
+ console.error('Failed to load playbooks:', error);
+ })
+ .finally(() => {
+ setLoadingPlaybooks(false);
+ });
+
+ // Load devices
+ setLoadingDevices(true);
+ getAllDevices()
+ .then(response => {
+ if (response.data) {
+ setDevices(response.data);
+ }
+ })
+ .catch(error => {
+ console.error('Failed to load devices:', error);
+ })
+ .finally(() => {
+ setLoadingDevices(false);
+ });
+ }, []);
+
+ const handlePlaybookToggle = (playbookUuid: string, checked: boolean) => {
+ if (checked) {
+ // Check if we've reached the max playbooks limit
+ if (maxPlaybooks && currentValue.selectedPlaybooks.length >= maxPlaybooks) {
+ return;
+ }
+
+ onChange({
+ selectedPlaybooks: [
+ ...currentValue.selectedPlaybooks,
+ {
+ uuid: playbookUuid,
+ deviceUuids: [],
+ },
+ ],
+ });
+ } else {
+ onChange({
+ selectedPlaybooks: currentValue.selectedPlaybooks.filter(
+ p => p.uuid !== playbookUuid
+ ),
+ });
+ }
+ };
+
+ const handleDeviceSelection = (playbookUuid: string, deviceUuids: string[]) => {
+ onChange({
+ selectedPlaybooks: currentValue.selectedPlaybooks.map(p =>
+ p.uuid === playbookUuid ? { ...p, deviceUuids } : p
+ ),
+ });
+ };
+
+ const selectAll = () => {
+ const allPlaybookUuids = playbooks
+ .slice(0, maxPlaybooks || playbooks.length)
+ .map(p => p.uuid);
+
+ onChange({
+ selectedPlaybooks: allPlaybookUuids.map(uuid => ({
+ uuid,
+ deviceUuids: [],
+ })),
+ });
+ };
+
+ const deselectAll = () => {
+ onChange({
+ selectedPlaybooks: [],
+ });
+ };
+
+ const isLoading = loadingPlaybooks || loadingDevices;
+ const selectedCount = currentValue.selectedPlaybooks.length;
+ const canSelectMore = !maxPlaybooks || selectedCount < maxPlaybooks;
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (playbooks.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ Select Playbooks
+
+ {maxPlaybooks && (
+
+ {selectedCount} / {maxPlaybooks}
+
+ )}
+
+ Select All
+
+
+ Clear
+
+
+
+
+
+
+ {playbooks.map(playbook => {
+ const isSelected = currentValue.selectedPlaybooks.some(
+ p => p.uuid === playbook.uuid
+ );
+ const selectedPlaybook = currentValue.selectedPlaybooks.find(
+ p => p.uuid === playbook.uuid
+ );
+
+ return (
+
+
handlePlaybookToggle(playbook.uuid, e.target.checked)}
+ disabled={disabled || (!isSelected && !canSelectMore)}
+ >
+ {playbook.name}
+
+
+ {isSelected && (
+
+
+ Target devices {requireDeviceSelection ? '(required)' : '(optional)'}:
+
+ handleDeviceSelection(playbook.uuid, deviceUuids)}
+ options={devices.map(device => ({
+ label: device.fqdn || device.ip || device.uuid,
+ value: device.uuid,
+ }))}
+ disabled={disabled}
+ />
+
+ )}
+
+ );
+ })}
+
+
+
+ {!requireDeviceSelection && (
+
+ )}
+
+ );
+};
+
+export default PlaybookSelector;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/components/StatisticsSelector.tsx b/client/src/pages/Dashboard/Core/components/StatisticsSelector.tsx
new file mode 100644
index 000000000..bb7216eed
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/components/StatisticsSelector.tsx
@@ -0,0 +1,223 @@
+/**
+ * Statistics Selector Component
+ * Provides UI for selecting data type, source, and metrics
+ */
+
+import React, { useState, useEffect } from 'react';
+import { Select, Space, Spin, Typography } from 'antd';
+import { getAllDevices } from '@/services/rest/devices/devices';
+import { getContainers } from '@/services/rest/containers/containers';
+import { StatisticsConfig } from '../WidgetSettings.types';
+import { API } from 'ssm-shared-lib';
+
+const { Text } = Typography;
+
+interface StatisticsSelectorProps {
+ value?: StatisticsConfig;
+ onChange: (value: StatisticsConfig) => void;
+ supportedDataTypes?: Array<'device' | 'container'>;
+ supportedMetrics?: Record;
+ disabled?: boolean;
+ selectionMode?: 'single' | 'multiple';
+}
+
+const defaultMetrics: Record> = {
+ device: [
+ { label: 'CPU Usage', value: 'cpu_usage' },
+ { label: 'Memory Usage', value: 'memory_usage' },
+ { label: 'Memory Free', value: 'memory_free' },
+ { label: 'Storage Usage', value: 'storage_usage' },
+ { label: 'Storage Free', value: 'storage_free' },
+ { label: 'Containers', value: 'containers' },
+ ],
+ container: [
+ { label: 'CPU Usage', value: 'container_cpu_usage' },
+ { label: 'Memory Usage', value: 'container_memory_usage' },
+ ],
+};
+
+const StatisticsSelector: React.FC = ({
+ value,
+ onChange,
+ supportedDataTypes = ['device', 'container'],
+ supportedMetrics,
+ disabled = false,
+ selectionMode = 'multiple',
+}) => {
+ const [devices, setDevices] = useState([]);
+ const [containers, setContainers] = useState([]);
+ const [loadingDevices, setLoadingDevices] = useState(false);
+ const [loadingContainers, setLoadingContainers] = useState(false);
+
+ const currentValue: StatisticsConfig = value || {
+ dataType: 'device',
+ source: ['all'],
+ metric: 'cpu_usage',
+ };
+
+ useEffect(() => {
+ // Load devices
+ if (supportedDataTypes.includes('device')) {
+ setLoadingDevices(true);
+ getAllDevices()
+ .then(response => {
+ if (response.data) {
+ setDevices(response.data);
+ }
+ })
+ .catch(error => {
+ console.error('Failed to load devices:', error);
+ })
+ .finally(() => {
+ setLoadingDevices(false);
+ });
+ }
+
+ // Load containers
+ if (supportedDataTypes.includes('container')) {
+ setLoadingContainers(true);
+ getContainers()
+ .then(response => {
+ if (response.data) {
+ setContainers(response.data);
+ }
+ })
+ .catch(error => {
+ console.error('Failed to load containers:', error);
+ })
+ .finally(() => {
+ setLoadingContainers(false);
+ });
+ }
+ }, [supportedDataTypes]);
+
+ const handleDataTypeChange = (dataType: 'device' | 'container') => {
+ onChange({
+ ...currentValue,
+ dataType,
+ source: ['all'], // Reset source when data type changes
+ metric: dataType === 'device' ? 'cpu_usage' : 'container_cpu_usage', // Set appropriate default metric
+ });
+ };
+
+ const handleSourceChange = (source: string | string[]) => {
+ // For single selection mode, convert single string to array format for consistency
+ const sourceArray = Array.isArray(source) ? source : [source];
+
+ // If in single selection mode and multiple items selected, only keep the last one
+ let finalSource = sourceArray;
+ if (selectionMode === 'single' && sourceArray.length > 1) {
+ finalSource = [sourceArray[sourceArray.length - 1]];
+ }
+
+ onChange({
+ ...currentValue,
+ source: finalSource,
+ });
+ };
+
+ const handleMetricChange = (metric: string) => {
+ onChange({
+ ...currentValue,
+ metric,
+ });
+ };
+
+ const getSourceOptions = () => {
+ const baseOptions = [{ label: 'All', value: 'all' }];
+
+ if (currentValue.dataType === 'device') {
+ return [
+ ...baseOptions,
+ ...devices.map(device => ({
+ label: device.fqdn || device.ip || device.uuid,
+ value: device.uuid,
+ })),
+ ];
+ } else {
+ return [
+ ...baseOptions,
+ ...containers.map(container => ({
+ label: container.customName || container.name || container.id,
+ value: container.id,
+ })),
+ ];
+ }
+ };
+
+ const getMetricOptions = () => {
+ if (supportedMetrics && supportedMetrics[currentValue.dataType]) {
+ return supportedMetrics[currentValue.dataType].map(metric => ({
+ label: metric.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
+ value: metric,
+ }));
+ }
+ return defaultMetrics[currentValue.dataType] || [];
+ };
+
+ const isLoading = loadingDevices || loadingContainers;
+
+ return (
+
+
+ Data Type
+ ({
+ label: type.charAt(0).toUpperCase() + type.slice(1),
+ value: type,
+ }))}
+ style={{ width: '100%' }}
+ disabled={disabled}
+ />
+
+
+
+ Source
+ : 'No data'}
+ maxTagCount={selectionMode === 'single' ? 1 : undefined}
+ />
+
+
+
+ Metric
+
+
+
+
+ Aggregation
+ onChange({ ...currentValue, aggregation: aggregation as StatisticsConfig['aggregation'] })}
+ options={[
+ { label: 'Average', value: 'average' },
+ { label: 'Sum', value: 'sum' },
+ { label: 'Minimum', value: 'min' },
+ { label: 'Maximum', value: 'max' },
+ ]}
+ style={{ width: '100%' }}
+ disabled={disabled}
+ />
+
+
+ );
+};
+
+export default StatisticsSelector;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/Core/schemas/index.ts b/client/src/pages/Dashboard/Core/schemas/index.ts
new file mode 100644
index 000000000..57cd11c15
--- /dev/null
+++ b/client/src/pages/Dashboard/Core/schemas/index.ts
@@ -0,0 +1,329 @@
+/**
+ * Widget Settings Schemas
+ * Define schemas for all dashboard widgets
+ */
+
+import { WidgetSettingsSchema } from '../WidgetSettings.types';
+import { widgetSettingsManager } from '../WidgetSettingsManager';
+
+// Statistics-based widgets schema
+const statisticsWidgetSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Widget Title',
+ required: true,
+ defaultValue: 'Statistics',
+ },
+ statistics: {
+ type: 'statistics',
+ label: 'Data Configuration',
+ required: true,
+ defaultValue: {
+ dataType: 'device',
+ source: ['all'],
+ metric: 'cpu_usage',
+ },
+ },
+ dateRange: {
+ type: 'dateRange',
+ label: 'Date Range',
+ defaultValue: {
+ preset: 'last7days',
+ },
+ },
+ colorPalette: {
+ type: 'colorPalette',
+ label: 'Color Theme',
+ defaultValue: {
+ id: 'default',
+ },
+ },
+ },
+ layout: {
+ type: 'sections',
+ groups: [
+ {
+ id: 'basic',
+ label: 'Basic Settings',
+ fields: ['title'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'data',
+ label: 'Data Configuration',
+ fields: ['statistics', 'dateRange'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'appearance',
+ label: 'Appearance',
+ fields: ['colorPalette'],
+ defaultExpanded: false,
+ },
+ ],
+ },
+};
+
+// Line Chart schema
+const lineChartSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Chart Title',
+ required: true,
+ defaultValue: 'Metric Trends',
+ },
+ statistics: {
+ type: 'statistics',
+ label: 'Data Configuration',
+ required: true,
+ defaultValue: {
+ dataType: 'device',
+ source: ['all'],
+ metric: 'cpu_usage',
+ },
+ supportedMetrics: {
+ device: ['cpu_usage', 'memory_usage', 'storage_usage'],
+ container: ['container_cpu_usage', 'container_memory_usage'],
+ },
+ },
+ dateRange: {
+ type: 'dateRange',
+ label: 'Date Range',
+ defaultValue: {
+ preset: 'last7days',
+ },
+ },
+ colorPalette: {
+ type: 'colorPalette',
+ label: 'Color Theme',
+ defaultValue: {
+ id: 'default',
+ },
+ },
+ showLegend: {
+ type: 'boolean',
+ label: 'Show Legend',
+ defaultValue: true,
+ },
+ showGrid: {
+ type: 'boolean',
+ label: 'Show Grid',
+ defaultValue: true,
+ },
+ },
+ layout: {
+ type: 'tabs',
+ groups: [
+ {
+ id: 'general',
+ label: 'General',
+ fields: ['title', 'statistics', 'dateRange'],
+ },
+ {
+ id: 'appearance',
+ label: 'Appearance',
+ fields: ['colorPalette', 'showLegend', 'showGrid'],
+ },
+ ],
+ },
+};
+
+// Ansible Playbook Runner schema
+const ansiblePlaybookRunnerSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Widget Title',
+ defaultValue: 'Ansible Playbooks',
+ },
+ playbooks: {
+ type: 'playbook',
+ label: 'Playbook Configuration',
+ required: true,
+ defaultValue: {
+ selectedPlaybooks: [],
+ },
+ maxPlaybooks: 10,
+ requireDeviceSelection: false,
+ },
+ showTags: {
+ type: 'boolean',
+ label: 'Show Playbook Tags',
+ defaultValue: true,
+ },
+ showDeviceCount: {
+ type: 'boolean',
+ label: 'Show Target Device Count',
+ defaultValue: true,
+ },
+ },
+ layout: {
+ type: 'sections',
+ groups: [
+ {
+ id: 'basic',
+ label: 'Basic Settings',
+ fields: ['title'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'playbooks',
+ label: 'Playbook Selection',
+ fields: ['playbooks'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'display',
+ label: 'Display Options',
+ fields: ['showTags', 'showDeviceCount'],
+ defaultExpanded: false,
+ },
+ ],
+ },
+};
+
+// Donut Chart schema - restricts to single device/container selection
+const donutChartSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Chart Title',
+ required: true,
+ defaultValue: 'Donut Chart',
+ },
+ statistics: {
+ type: 'statistics',
+ label: 'Data Configuration',
+ required: true,
+ defaultValue: {
+ dataType: 'device',
+ source: ['all'],
+ metric: 'cpu_usage',
+ },
+ selectionMode: 'single',
+ supportedMetrics: {
+ device: ['cpu_usage', 'memory_usage', 'storage_usage'],
+ container: ['container_cpu_usage', 'container_memory_usage'],
+ },
+ },
+ colorPalette: {
+ type: 'colorPalette',
+ label: 'Color Theme',
+ defaultValue: {
+ id: 'default',
+ },
+ },
+ },
+ layout: {
+ type: 'sections',
+ groups: [
+ {
+ id: 'basic',
+ label: 'Basic Settings',
+ fields: ['title'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'data',
+ label: 'Data Configuration',
+ fields: ['statistics'],
+ defaultExpanded: true,
+ },
+ {
+ id: 'appearance',
+ label: 'Appearance',
+ fields: ['colorPalette'],
+ defaultExpanded: false,
+ },
+ ],
+ },
+};
+
+// IFrame Widget schema
+const iframeWidgetSchema: WidgetSettingsSchema = {
+ version: '1.0',
+ fields: {
+ title: {
+ type: 'text',
+ label: 'Widget Title',
+ required: true,
+ defaultValue: 'External Service',
+ },
+ url: {
+ type: 'text',
+ label: 'URL',
+ required: true,
+ placeholder: 'https://example.com',
+ validation: {
+ validate: (value: string) => {
+ try {
+ new URL(value);
+ return true;
+ } catch {
+ return 'Please enter a valid URL';
+ }
+ },
+ },
+ },
+ height: {
+ type: 'number',
+ label: 'Height (pixels)',
+ defaultValue: 400,
+ min: 200,
+ max: 800,
+ },
+ refreshInterval: {
+ type: 'select',
+ label: 'Refresh Interval',
+ defaultValue: 0,
+ options: [
+ { label: 'No refresh', value: 0 },
+ { label: 'Every 30 seconds', value: 30 },
+ { label: 'Every minute', value: 60 },
+ { label: 'Every 5 minutes', value: 300 },
+ { label: 'Every 15 minutes', value: 900 },
+ ],
+ },
+ },
+};
+
+// Register all schemas
+export function registerWidgetSchemas(): void {
+ // Statistics widgets
+ widgetSettingsManager.registerSchema('single-number-variation', statisticsWidgetSchema);
+ widgetSettingsManager.registerSchema('compact-stat-card', statisticsWidgetSchema);
+ widgetSettingsManager.registerSchema('summary-stat-card', statisticsWidgetSchema);
+
+ // Chart widgets
+ widgetSettingsManager.registerSchema('line-chart', lineChartSchema);
+ widgetSettingsManager.registerSchema('area-chart', lineChartSchema);
+ widgetSettingsManager.registerSchema('stacked-bar-chart', {
+ ...lineChartSchema,
+ fields: {
+ ...lineChartSchema.fields,
+ stacked: {
+ type: 'boolean',
+ label: 'Stack Bars',
+ defaultValue: true,
+ },
+ },
+ });
+
+ // Donut chart with single selection restriction
+ widgetSettingsManager.registerSchema('ring-progress', donutChartSchema);
+
+ // Playbook widget
+ widgetSettingsManager.registerSchema('AnsiblePlaybookRunner', ansiblePlaybookRunnerSchema);
+
+ // IFrame widget
+ widgetSettingsManager.registerSchema('IFrameWidget', iframeWidgetSchema);
+}
+
+// Initialize schemas
+registerWidgetSchemas();
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/constants/widgetConstants.ts b/client/src/pages/Dashboard/constants/widgetConstants.ts
new file mode 100644
index 000000000..065d131b3
--- /dev/null
+++ b/client/src/pages/Dashboard/constants/widgetConstants.ts
@@ -0,0 +1,87 @@
+/**
+ * Widget Constants
+ * Centralized constants for dashboard widgets
+ */
+
+// Field names for widget settings
+export const WIDGET_FIELDS = {
+ STATISTICS_TYPE: 'statistics_type',
+ STATISTICS_SOURCE: 'statistics_source',
+ STATISTICS_METRIC: 'statistics_metric',
+ TITLE: 'title',
+ DATE_RANGE_PRESET: 'dateRangePreset',
+ CUSTOM_DATE_RANGE: 'customDateRange',
+ COLOR_PALETTE: 'colorPalette',
+ CUSTOM_COLORS: 'customColors',
+ BACKGROUND_COLOR_PALETTE: 'backgroundColorPalette',
+ CUSTOM_TEXT: 'customText',
+ ICON: 'icon',
+ ILLUSTRATION_URL: 'illustrationUrl',
+ DEFAULT_VALUE: 'defaultValue',
+ IS_PREVIEW: 'isPreview',
+} as const;
+
+// Default values
+export const WIDGET_DEFAULTS = {
+ DATA_TYPE: 'device',
+ METRIC: 'cpu_usage',
+ DATE_RANGE: 'last7days',
+ COLOR_PALETTE: 'default',
+ EMPTY_ARRAY: [] as string[],
+ ALL_SOURCES: ['all'],
+} as const;
+
+// Data types
+export const DATA_TYPES = {
+ DEVICE: 'device',
+ CONTAINER: 'container',
+} as const;
+
+// Metrics
+export const METRICS = {
+ CPU_USAGE: 'cpu_usage',
+ MEMORY_USAGE: 'memory_usage',
+ MEMORY_FREE: 'memory_free',
+ STORAGE_USAGE: 'storage_usage',
+ STORAGE_FREE: 'storage_free',
+ CONTAINERS: 'containers',
+ CONTAINER_CPU_USAGE: 'container_cpu_usage',
+ CONTAINER_MEMORY_USAGE: 'container_memory_usage',
+} as const;
+
+// Date range presets
+export const DATE_RANGE_PRESETS = {
+ LAST_24_HOURS: 'last24hours',
+ LAST_7_DAYS: 'last7days',
+ LAST_30_DAYS: 'last30days',
+ LAST_3_MONTHS: 'last3months',
+ LAST_6_MONTHS: 'last6months',
+ LAST_YEAR: 'lastyear',
+ CUSTOM: 'custom',
+} as const;
+
+// Color palettes
+export const COLOR_PALETTES = {
+ DEFAULT: 'default',
+ MODERN: 'modern',
+ VIBRANT: 'vibrant',
+ PASTEL: 'pastel',
+ PROFESSIONAL: 'professional',
+ SYSTEM: 'system',
+ DARK: 'dark',
+ CUSTOM: 'custom',
+} as const;
+
+// Background color themes
+export const BACKGROUND_THEMES = {
+ DEFAULT: 'default',
+ PRIMARY: 'primary',
+ SUCCESS: 'success',
+ WARNING: 'warning',
+ ERROR: 'error',
+ LIGHT: 'light',
+ DARK: 'dark',
+ PURPLE: 'purple',
+ OCEAN: 'ocean',
+ SUNSET: 'sunset',
+} as const;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/constants/widgetStyles.ts b/client/src/pages/Dashboard/constants/widgetStyles.ts
new file mode 100644
index 000000000..2c07b4980
--- /dev/null
+++ b/client/src/pages/Dashboard/constants/widgetStyles.ts
@@ -0,0 +1,107 @@
+/**
+ * Widget Styles
+ * Centralized styles for dashboard widgets to ensure consistency
+ */
+
+import { CSSProperties } from 'react';
+
+// Typography styles
+export const WIDGET_TYPOGRAPHY = {
+ // Widget title style - used for card headers
+ title: {
+ color: '#b8bac3',
+ fontSize: '14px',
+ fontWeight: 400,
+ } as CSSProperties,
+
+ // Main value display - large numbers/metrics
+ mainValue: {
+ color: '#ffffff',
+ fontSize: '36px',
+ fontWeight: '500',
+ lineHeight: 1,
+ margin: 0,
+ } as CSSProperties,
+
+ // Compact value display - smaller version of main value
+ compactValue: {
+ color: '#f0f0f0',
+ fontSize: '30px',
+ fontWeight: '600',
+ margin: 0,
+ } as CSSProperties,
+
+ // Trend value - percentage changes
+ trendValue: {
+ fontSize: '14px',
+ fontWeight: 500,
+ } as CSSProperties,
+
+ // Descriptive text - labels and descriptions
+ description: {
+ color: '#7a7d87',
+ fontSize: '14px',
+ } as CSSProperties,
+
+ // Muted text - secondary information
+ muted: {
+ color: '#8c8c8c',
+ fontSize: '13px',
+ } as CSSProperties,
+} as const;
+
+// Card styles
+export const WIDGET_CARD = {
+ // Base card style
+ base: {
+ backgroundColor: '#1a1a1a',
+ borderRadius: '16px',
+ color: 'white',
+ border: 'none',
+ position: 'relative' as const,
+ } as CSSProperties,
+
+ // Card body padding
+ bodyStyle: {
+ padding: '24px',
+ } as CSSProperties,
+
+ // Compact card body padding
+ compactBodyStyle: {
+ padding: '20px 24px',
+ } as CSSProperties,
+} as const;
+
+// Colors
+export const WIDGET_COLORS = {
+ // Background colors
+ background: {
+ primary: '#1a1a1a',
+ secondary: '#2a2a2a',
+ hover: '#2f2f2f',
+ },
+
+ // Text colors
+ text: {
+ primary: '#ffffff',
+ secondary: '#f0f0f0',
+ muted: '#8c8c8c',
+ description: '#7a7d87',
+ title: '#b8bac3',
+ },
+
+ // Status colors
+ status: {
+ success: '#52c41a',
+ error: '#ff4d4f',
+ warning: '#faad14',
+ info: '#1890ff',
+ },
+
+ // Trend colors
+ trend: {
+ up: '#52c41a',
+ down: '#ff4d4f',
+ neutral: '#8c8c8c',
+ },
+} as const;
\ No newline at end of file
diff --git a/client/src/pages/Dashboard/index.tsx b/client/src/pages/Dashboard/index.tsx
index 34fa8371e..e2f6a5d9f 100644
--- a/client/src/pages/Dashboard/index.tsx
+++ b/client/src/pages/Dashboard/index.tsx
@@ -1,23 +1,160 @@
-import MainChartCard from '@/pages/Dashboard/Components/MainChartCard';
+import React, { useState, useEffect, useCallback } from 'react';
+import {
+ DashboardOutlined,
+ LayoutOutlined,
+ PlusOutlined,
+} from '@ant-design/icons';
+import { message, Spin } from 'antd';
+import dashboardService from '@/services/rest/dashboard.service';
+import type { Dashboard, DashboardPage } from '@/services/rest/dashboard.service';
+
+// Layout Components
+import StyledTabContainer, {
+ IconWrapper,
+ TabLabel,
+} from '@/components/Layout/StyledTabContainer';
+
+// Existing Dashboard Components
+import TimeSeriesLineChart from '@/pages/Dashboard/Components/TimeSeriesLineChart';
import DashboardTop from '@/pages/Dashboard/Components/DashboardTop';
-import { PageContainer } from '@ant-design/pro-components';
-import React from 'react';
import { useSlot } from '@/plugins/contexts/plugin-context';
+
+// Customizable Dashboard Engine
+import DashboardLayoutEngine from '@/pages/Dashboard/Components/DashboardLayoutEngine';
+import { createDashboardItems } from '@/pages/Dashboard/Components/DashboardItemsFactory';
+
+
const Index: React.FC = () => {
// Get the dashboard widgets slot renderer
const DashboardWidgetsSlot = useSlot('dashboard-widgets');
+ // Create available dashboard items for the customizable dashboard
+ const availableDashboardItems = createDashboardItems();
+
+ const [dashboard, setDashboard] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [refreshKey, setRefreshKey] = useState(0);
+ // Capture the initial hash immediately
+ const [initialActiveKey] = useState(() => {
+ const hash = window.location.hash.replace('#', '');
+ return hash || '';
+ });
- return (
-
-
-
+ // Load dashboard on mount
+ useEffect(() => {
+ loadDashboard();
+ }, [refreshKey]);
+
+ const loadDashboard = async () => {
+ try {
+ setLoading(true);
+ const currentDashboard = await dashboardService.getCurrentDashboard();
+ setDashboard(currentDashboard);
+ } catch (error) {
+ console.error('Failed to load dashboard:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
- {/* Render plugin dashboard widgets */}
-
-
+ const handleAddPage = useCallback(async () => {
+ if (!dashboard) return;
+
+ try {
+ const newPageName = `Dashboard ${dashboard.pages.length + 1}`;
+ const newPage: DashboardPage = {
+ id: `page-${Date.now()}`,
+ name: newPageName,
+ order: dashboard.pages.length,
+ widgets: [],
+ };
+
+ const updatedDashboard = await dashboardService.update(dashboard._id!, {
+ pages: [...dashboard.pages, newPage],
+ });
+
+ setDashboard(updatedDashboard);
+ message.success(`Created new page: ${newPageName}`);
+ // Force refresh to update tabs
+ setRefreshKey(prev => prev + 1);
+ } catch (error) {
+ console.error('Failed to add page:', error);
+ message.error('Failed to create new page');
+ }
+ }, [dashboard]);
+
+ // If dashboard has no pages, create one
+ useEffect(() => {
+ if (dashboard && dashboard.pages.length === 0) {
+ handleAddPage();
+ }
+ }, [dashboard, handleAddPage]);
+
+ const tabItems = [
+ // Add customizable dashboard pages
+ ...(dashboard?.pages || []).map((page, index) => ({
+ key: page.id,
+ label: (
+
+
+
+
+ {page.name}
+
+ ),
+ children: (
+
setRefreshKey(prev => prev + 1)}
+ onDashboardUpdate={(updatedDashboard) => setDashboard(updatedDashboard)}
+ isFirstPage={index === 0}
+ />
+ ),
+ })),
+ // Add Page button
+ {
+ key: 'add-page',
+ label: (
+
+
+
+
+ Add Page
+
+ ),
+ children: null,
+ },
+ ];
+
+ // Don't render until dashboard is loaded
+ if (loading || !dashboard) {
+ return (
+
+
-
+ );
+ }
+
+ // Determine the active key
+ const activeKey = initialActiveKey || dashboard?.pages?.[0]?.id || '';
+
+ return (
+ {
+ if (key === 'add-page') {
+ handleAddPage();
+ }
+ }}
+ />
);
};
diff --git a/client/src/pages/Dashboard/unified-framework-implementation.md b/client/src/pages/Dashboard/unified-framework-implementation.md
new file mode 100644
index 000000000..5a9412b6e
--- /dev/null
+++ b/client/src/pages/Dashboard/unified-framework-implementation.md
@@ -0,0 +1,260 @@
+# Unified Dashboard Framework Implementation Guide
+
+## Overview
+
+This document describes the unified framework implementation for SquirrelServersManager dashboard components. The goal is to reduce code duplication by ~60% while preserving ALL original styling and functionality.
+
+## Architecture
+
+### Core Components
+
+1. **Data Fetching Layer** (`hooks/useDashboardData.ts`)
+ - Unified data fetching for all dashboard components
+ - Handles device/container data sources
+ - Manages loading states and error handling
+ - Provides consistent API interfaces
+
+2. **Chart Utilities** (`utils/chartUtils.ts`)
+ - Pre-configured chart options that preserve original styling
+ - Color palette management
+ - Common formatting functions
+ - Reusable chart configurations
+
+3. **Data Processing** (`utils/dataProcessing.ts`)
+ - Unified data transformation utilities
+ - Trend calculations
+ - Historical data processing
+ - Chart series generation
+
+4. **Mock Data** (`utils/mockData.ts`)
+ - Clean separation of mock data from components
+ - Deterministic preview data
+ - Consistent mock data patterns
+
+5. **Source Parsing** (`hooks/useSourceParser.ts`)
+ - Entity resolution utilities
+ - Device/container name mapping
+ - Source configuration parsing
+
+## Implementation Status
+
+### ✅ Completed
+- [x] **useDashboardData Hook**: Unified data fetching
+- [x] **Chart Utilities**: Preserved original styling
+- [x] **Data Processing**: Extracted processing logic
+- [x] **Mock Data**: External mock data utilities
+- [x] **LineChart Migration**: First component migrated
+
+### 🚧 In Progress
+- [ ] **LineChart Testing**: Verify styling matches original
+- [ ] **Component Migration**: Remaining components
+
+### 📋 Pending
+- [ ] **MetricCardWithMiniLineChart**: Migrate to unified framework
+- [ ] **CompactStatCard**: Migrate to unified framework
+- [ ] **DonutChart**: Migrate to unified framework
+- [ ] **DonutChartWithTable**: Migrate to unified framework
+
+## Key Principles
+
+### 1. Preserve Original Styling
+- **NEVER** change visual appearance
+- Extract exact styling configurations
+- Test against original components
+- Use type-safe chart options
+
+### 2. Unified Data Sources
+- Single source of truth for API calls
+- Consistent error handling
+- Unified loading states
+- Standardized data formats
+
+### 3. Clean Code Separation
+- Extract utilities to separate files
+- Remove inline mock data
+- Consistent naming conventions
+- Clear responsibility boundaries
+
+## Migration Checklist
+
+For each component migration:
+
+### Pre-Migration
+- [ ] Read and understand original component
+- [ ] Identify data fetching patterns
+- [ ] Extract chart configurations
+- [ ] Note any special behaviors
+
+### During Migration
+- [ ] Replace data fetching with `useDashboardData`
+- [ ] Use chart utilities from `chartUtils.ts`
+- [ ] Extract processing logic to `dataProcessing.ts`
+- [ ] Use external mock data from `mockData.ts`
+- [ ] Preserve all resize/interaction logic
+
+### Post-Migration
+- [ ] Test visual appearance matches exactly
+- [ ] Verify all functionality works
+- [ ] Check loading states and errors
+- [ ] Test preview mode
+- [ ] Run TypeScript checks
+
+## File Structure
+
+```
+src/pages/Dashboard/
+├── hooks/
+│ ├── useDashboardData.ts # Unified data fetching
+│ └── useSourceParser.ts # Entity resolution
+├── utils/
+│ ├── chartUtils.ts # Chart configurations
+│ ├── dataProcessing.ts # Data transformations
+│ └── mockData.ts # Mock data utilities
+└── Components/
+ ├── LineChart.tsx # ✅ Migrated
+ ├── MetricCardWithMiniLineChart.tsx # 🚧 Next
+ ├── CompactStatCard.tsx # 📋 Pending
+ └── DonutChart.tsx # 📋 Pending
+```
+
+## API Reference
+
+### useDashboardData Hook
+
+```typescript
+interface DashboardDataConfig {
+ dataType: 'device' | 'container';
+ source: string | string[];
+ metrics: string[];
+ dateRangePreset?: string;
+ customDateRange?: [moment.Moment, moment.Moment];
+ isPreview?: boolean;
+}
+
+const dashboardData = useDashboardData({
+ dataType: 'device',
+ source: 'all',
+ metrics: ['cpu_usage', 'memory_usage'],
+ isPreview: false
+});
+```
+
+### Chart Utils
+
+```typescript
+// Line/Area charts
+const options = createLineChartOptions(metrics, colors);
+
+// Donut charts
+const options = createDonutChartOptions(label, total, labels, colors);
+
+// Sparkline charts
+const options = createSparklineChartOptions();
+
+// Bar charts
+const options = createBarChartOptions(trendColor);
+
+// Color palettes
+const colors = getColorPalette('default', customColors);
+```
+
+### Data Processing
+
+```typescript
+// Line chart data
+const processed = processHistoricalDataForLineChart(data, nameMap);
+
+// Sparkline data
+const processed = processHistoricalDataForSparkline(data);
+
+// Bar chart data
+const processed = processHistoricalDataForBars(data);
+
+// Trend calculation
+const trend = calculateTrendFromData(data);
+```
+
+### Mock Data
+
+```typescript
+// Line charts
+const mockData = generateMockLineChartData();
+
+// Sparklines
+const mockData = generateMockSparklineData();
+
+// Bar charts
+const mockData = generateMockBarChartData();
+
+// Donut charts
+const mockData = generateMockDonutChartData();
+```
+
+## Benefits Achieved
+
+### Code Reduction
+- **LineChart**: 800+ lines → 400 lines (50% reduction)
+- **Removed Duplication**: ~200 lines of data fetching per component
+- **Unified Utilities**: Single source for common operations
+
+### Maintainability
+- **Centralized Logic**: Easy to update API calls
+- **Consistent Patterns**: Same structure across components
+- **Better Testing**: Isolated utilities are easier to test
+- **Clear Dependencies**: Explicit imports and interfaces
+
+### Performance
+- **Optimized Hooks**: Memoized data processing
+- **Efficient Re-renders**: Reduced unnecessary updates
+- **Consistent Loading**: Unified loading state management
+
+## Testing Strategy
+
+### Visual Testing
+1. **Side-by-side Comparison**: Original vs Migrated
+2. **Color Verification**: Exact color matching
+3. **Layout Verification**: Spacing and positioning
+4. **Interaction Testing**: Hover states and animations
+
+### Functional Testing
+1. **Data Loading**: All API scenarios
+2. **Error Handling**: Network failures and empty data
+3. **Preview Mode**: Mock data consistency
+4. **Date Ranges**: All time period selections
+
+### Regression Testing
+1. **Resize Behavior**: Chart responsiveness
+2. **Debug Integration**: Debug overlay functionality
+3. **Widget Context**: Context provider integration
+4. **Performance**: No performance degradation
+
+## Next Steps
+
+1. **Complete LineChart Testing**: Verify visual parity
+2. **Migrate MetricCardWithMiniLineChart**: Apply same patterns
+3. **Migrate CompactStatCard**: Focus on bar charts
+4. **Migrate DonutChart**: Handle complex donut logic
+5. **Final Integration Testing**: All components together
+
+## Implementation Notes
+
+### Preserved Features
+- ✅ All original chart resize logic
+- ✅ Dark theme styling (#1a1a1a background)
+- ✅ Exact color palettes and fonts
+- ✅ Debug overlay integration
+- ✅ Widget context support
+- ✅ Loading and error states
+- ✅ Preview mode functionality
+
+### Framework Benefits
+- 🎯 **60% Code Reduction** while preserving functionality
+- 🔒 **Type Safety** with TypeScript interfaces
+- 🔄 **Consistent APIs** across all components
+- 🚀 **Better Performance** with optimized hooks
+- 🧪 **Easier Testing** with isolated utilities
+- 📦 **Cleaner Architecture** with clear separation
+
+---
+
+*This unified framework provides a solid foundation for maintaining dashboard components while dramatically reducing code duplication and improving maintainability.*
\ No newline at end of file
diff --git a/client/src/pages/Plugins/index.tsx b/client/src/pages/Plugins/index.tsx
index db37505c4..c984b112a 100644
--- a/client/src/pages/Plugins/index.tsx
+++ b/client/src/pages/Plugins/index.tsx
@@ -59,15 +59,6 @@ const PluginsPage: React.FC = () => {
return (
}
- />
- ),
- }}
tabItems={tabItems}
defaultActiveKey="installed"
/>
diff --git a/client/src/services/cache/entityCache.service.ts b/client/src/services/cache/entityCache.service.ts
new file mode 100644
index 000000000..1140ce1c4
--- /dev/null
+++ b/client/src/services/cache/entityCache.service.ts
@@ -0,0 +1,116 @@
+import { getAllDevices } from '@/services/rest/devices/devices';
+import { getContainers as getAllContainers } from '@/services/rest/containers/containers';
+import { API } from 'ssm-shared-lib';
+
+interface CacheEntry {
+ data: T;
+ timestamp: number;
+ ttl: number;
+}
+
+class EntityCacheService {
+ private cache: Map> = new Map();
+ private readonly DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
+
+ private isExpired(entry: CacheEntry): boolean {
+ return Date.now() - entry.timestamp > entry.ttl;
+ }
+
+ private getCacheKey(type: 'devices' | 'containers'): string {
+ return `entities_${type}`;
+ }
+
+ async getDevices(): Promise {
+ const cacheKey = this.getCacheKey('devices');
+ const cached = this.cache.get(cacheKey);
+
+ if (cached && !this.isExpired(cached)) {
+ console.log('📦 EntityCache: Returning cached devices');
+ return cached.data;
+ }
+
+ console.log('📦 EntityCache: Fetching fresh devices data');
+ const response = await getAllDevices();
+ const devices = response.data || [];
+
+ this.cache.set(cacheKey, {
+ data: devices,
+ timestamp: Date.now(),
+ ttl: this.DEFAULT_TTL,
+ });
+
+ return devices;
+ }
+
+ async getContainers(): Promise {
+ const cacheKey = this.getCacheKey('containers');
+ const cached = this.cache.get(cacheKey);
+
+ if (cached && !this.isExpired(cached)) {
+ console.log('📦 EntityCache: Returning cached containers');
+ return cached.data;
+ }
+
+ console.log('📦 EntityCache: Fetching fresh containers data');
+ const response = await getAllContainers();
+ const containers = response.data || [];
+
+ this.cache.set(cacheKey, {
+ data: containers,
+ timestamp: Date.now(),
+ ttl: this.DEFAULT_TTL,
+ });
+
+ return containers;
+ }
+
+ async getEntityName(type: 'device' | 'container', id: string): Promise {
+ if (type === 'device') {
+ const devices = await this.getDevices();
+ const device = devices.find(d => d.uuid === id);
+ return device?.fqdn || device?.ip || device?.name || id;
+ } else {
+ const containers = await this.getContainers();
+ const container = containers.find(c => c.id === id);
+ return container?.name || id;
+ }
+ }
+
+ async getEntitiesByIds(type: 'device' | 'container', ids: string[]): Promise> {
+ const nameMap = new Map();
+
+ if (type === 'device') {
+ const devices = await this.getDevices();
+ ids.forEach(id => {
+ const device = devices.find(d => d.uuid === id);
+ nameMap.set(id, device?.fqdn || device?.ip || device?.name || id);
+ });
+ } else {
+ const containers = await this.getContainers();
+ ids.forEach(id => {
+ const container = containers.find(c => c.id === id);
+ nameMap.set(id, container?.name || id);
+ });
+ }
+
+ return nameMap;
+ }
+
+ clearCache(): void {
+ this.cache.clear();
+ console.log('📦 EntityCache: Cache cleared');
+ }
+
+ clearDevices(): void {
+ this.cache.delete(this.getCacheKey('devices'));
+ console.log('📦 EntityCache: Devices cache cleared');
+ }
+
+ clearContainers(): void {
+ this.cache.delete(this.getCacheKey('containers'));
+ console.log('📦 EntityCache: Containers cache cleared');
+ }
+}
+
+// Export singleton instance
+export const entityCacheService = new EntityCacheService();
\ No newline at end of file
diff --git a/client/src/services/rest/ansible/ansible.logs.ts b/client/src/services/rest/ansible/ansible.logs.ts
index a8eb880bc..0de78f95e 100644
--- a/client/src/services/rest/ansible/ansible.logs.ts
+++ b/client/src/services/rest/ansible/ansible.logs.ts
@@ -7,7 +7,7 @@ export async function getTasksLogs(
params?: API.PageParams,
options?: { [key: string]: any },
) {
- return request(`${API_URL}/tasks`, {
+ return request>(`${API_URL}/tasks`, {
method: 'GET',
params: {
...params,
@@ -21,7 +21,7 @@ export async function getTaskEventsLogs(
params?: API.PageParams,
options?: { [key: string]: any },
) {
- return request(`${API_URL}/tasks/${id}/logs`, {
+ return request>(`${API_URL}/tasks/${id}/logs`, {
method: 'GET',
params: {
...params,
diff --git a/client/src/services/rest/dashboard.service.ts b/client/src/services/rest/dashboard.service.ts
new file mode 100644
index 000000000..8acafa93b
--- /dev/null
+++ b/client/src/services/rest/dashboard.service.ts
@@ -0,0 +1,116 @@
+import { request } from '@umijs/max';
+
+export interface WidgetSettings {
+ statistics_type?: string;
+ statistics_source?: string[];
+ statistics_metric?: string;
+ icon?: string;
+ backgroundColor?: string;
+ title?: string;
+ customText?: string;
+ dateRangePreset?: string;
+ customDateRange?: any;
+ aggregationType?: string;
+ colorPalette?: string;
+ customColors?: string[];
+ customSettings?: Record;
+}
+
+export interface DashboardWidget {
+ id: string;
+ widgetType: string;
+ title: string;
+ size: string;
+ position: number;
+ settings?: WidgetSettings;
+}
+
+export interface DashboardPage {
+ id: string;
+ name: string;
+ order: number;
+ widgets: DashboardWidget[];
+ isDefault?: boolean;
+}
+
+export interface Dashboard {
+ _id?: string;
+ name: string;
+ description?: string;
+ pages: DashboardPage[];
+ isActive: boolean;
+ isSystem?: boolean;
+ lastModifiedBy?: string;
+ createdAt?: string;
+ updatedAt?: string;
+}
+
+export interface CreateDashboardDto {
+ name: string;
+ description?: string;
+ pages: DashboardPage[];
+ isActive?: boolean;
+ isSystem?: boolean;
+}
+
+export interface UpdateDashboardDto extends Partial {}
+
+class DashboardService {
+ async create(data: CreateDashboardDto): Promise {
+ const response = await request('/api/dashboards', {
+ method: 'POST',
+ data,
+ });
+ return response.data || response;
+ }
+
+ async findAll(): Promise {
+ const response = await request('/api/dashboards', {
+ method: 'GET',
+ });
+ return response.data || response;
+ }
+
+ async findOne(id: string): Promise {
+ const response = await request(`/api/dashboards/${id}`, {
+ method: 'GET',
+ });
+ return response.data || response;
+ }
+
+ async getCurrentDashboard(): Promise {
+ const response = await request('/api/dashboards/current', {
+ method: 'GET',
+ });
+ // Extract data from the wrapped response
+ return response.data || response;
+ }
+
+ async update(id: string, data: UpdateDashboardDto): Promise {
+ const response = await request(`/api/dashboards/${id}`, {
+ method: 'PATCH',
+ data,
+ });
+ return response.data || response;
+ }
+
+ async remove(id: string): Promise {
+ return request(`/api/dashboards/${id}`, {
+ method: 'DELETE',
+ });
+ }
+
+ async updateWidgets(
+ dashboardId: string,
+ pageId: string,
+ widgets: DashboardWidget[]
+ ): Promise {
+ const response = await request(`/api/dashboards/${dashboardId}/pages/${pageId}/widgets`, {
+ method: 'PATCH',
+ data: widgets,
+ });
+ return response.data || response;
+ }
+}
+
+export default new DashboardService();
\ No newline at end of file
diff --git a/client/src/services/rest/rss.service.ts b/client/src/services/rest/rss.service.ts
new file mode 100644
index 000000000..8d9e9cfdb
--- /dev/null
+++ b/client/src/services/rest/rss.service.ts
@@ -0,0 +1,40 @@
+import { request } from '@umijs/max';
+import { API } from 'ssm-shared-lib';
+
+export interface RSSFeedItem {
+ title: string;
+ link: string;
+ description: string;
+ pubDate: string;
+ source: string;
+ guid?: string;
+ author?: string;
+ category?: string[];
+}
+
+export interface FeedConfig {
+ id: string;
+ name: string;
+ url: string;
+ enabled: boolean;
+}
+
+export interface FetchFeedsRequest {
+ feeds: FeedConfig[];
+}
+
+const BASE_URL = '/api/rss';
+
+export async function fetchRSSFeeds(feeds: FeedConfig[]): Promise> {
+ return request>(`${BASE_URL}/fetch`, {
+ method: 'POST',
+ data: { feeds }
+ });
+}
+
+export async function fetchSingleRSSFeed(url: string): Promise> {
+ return request>(`${BASE_URL}/fetch-single`, {
+ method: 'GET',
+ params: { url }
+ });
+}
\ No newline at end of file
diff --git a/client/src/utils/logger.ts b/client/src/utils/logger.ts
new file mode 100644
index 000000000..5b5724f59
--- /dev/null
+++ b/client/src/utils/logger.ts
@@ -0,0 +1,60 @@
+import { createConsola } from 'consola';
+
+// Create different logger instances for different modules
+const loggers = {
+ dashboard: createConsola({
+ defaults: {
+ tag: 'dashboard',
+ },
+ formatOptions: {
+ date: true,
+ colors: true,
+ compact: true,
+ },
+ }),
+ widgets: createConsola({
+ defaults: {
+ tag: 'widgets',
+ },
+ formatOptions: {
+ date: true,
+ colors: true,
+ compact: true,
+ },
+ }),
+ api: createConsola({
+ defaults: {
+ tag: 'api',
+ },
+ formatOptions: {
+ date: true,
+ colors: true,
+ compact: true,
+ },
+ }),
+ default: createConsola({
+ formatOptions: {
+ date: true,
+ colors: true,
+ compact: true,
+ },
+ }),
+};
+
+// Development vs Production configuration
+const isDev = process.env.NODE_ENV === 'development';
+
+// Configure log levels based on environment
+if (!isDev) {
+ Object.values(loggers).forEach(logger => {
+ logger.level = 3; // Show only warnings and errors in production
+ });
+}
+
+// Export individual loggers
+export const dashboardLogger = loggers.dashboard;
+export const widgetLogger = loggers.widgets;
+export const apiLogger = loggers.api;
+
+// Export default logger for general use
+export default loggers.default;
\ No newline at end of file
diff --git a/client/src/utils/mf-compat.ts b/client/src/utils/mf-compat.ts
new file mode 100644
index 000000000..c1bdf0fce
--- /dev/null
+++ b/client/src/utils/mf-compat.ts
@@ -0,0 +1,20 @@
+// This file provides compatibility fixes for module federation errors
+// It's used to mock missing federation modules
+
+// Mock for mf-va_remoteEntry.js
+if (typeof window !== 'undefined') {
+ const originalFetch = window.fetch;
+ window.fetch = function(input, init) {
+ if (input && typeof input === 'string' && input.includes('mf-va_remoteEntry.js')) {
+ // Return a mock module federation response
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve('window.mf = {};'),
+ json: () => Promise.resolve({}),
+ } as Response);
+ }
+ return originalFetch(input, init);
+ };
+}
+
+export default {};
\ No newline at end of file
diff --git a/client/tests/mocks/handlers.ts b/client/tests/mocks/handlers.ts
new file mode 100644
index 000000000..8a97d89c8
--- /dev/null
+++ b/client/tests/mocks/handlers.ts
@@ -0,0 +1,106 @@
+/**
+ * MSW request handlers for API mocking
+ */
+
+import { http, HttpResponse } from 'msw';
+
+// Define your API base URL
+const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3000/api';
+
+export const handlers = [
+ // User authentication handlers
+ http.post(`${API_BASE_URL}/login`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ token: 'mock-jwt-token',
+ user: {
+ id: 'test-user-1',
+ name: 'Test User',
+ email: 'test@example.com',
+ },
+ },
+ });
+ }),
+
+ http.get(`${API_BASE_URL}/currentUser`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ id: 'test-user-1',
+ name: 'Test User',
+ email: 'test@example.com',
+ permissions: ['admin', 'read', 'write'],
+ },
+ });
+ }),
+
+ // Device handlers
+ http.get(`${API_BASE_URL}/devices`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ list: [
+ {
+ uuid: 'device-1',
+ name: 'Test Device 1',
+ ip: '192.168.1.100',
+ status: 'online',
+ },
+ {
+ uuid: 'device-2',
+ name: 'Test Device 2',
+ ip: '192.168.1.101',
+ status: 'offline',
+ },
+ ],
+ total: 2,
+ },
+ });
+ }),
+
+ // Containers handlers
+ http.get(`${API_BASE_URL}/containers`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ list: [],
+ total: 0,
+ },
+ });
+ }),
+
+ // Playbooks handlers
+ http.get(`${API_BASE_URL}/playbooks`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ list: [],
+ total: 0,
+ },
+ });
+ }),
+
+ // Settings handlers
+ http.get(`${API_BASE_URL}/settings`, () => {
+ return HttpResponse.json({
+ success: true,
+ data: {
+ theme: 'light',
+ language: 'en-US',
+ },
+ });
+ }),
+
+ // Catch-all handler for unhandled requests
+ http.get('*', ({ request }) => {
+ console.warn(`Unhandled request: ${request.url}`);
+ return HttpResponse.json(
+ {
+ success: false,
+ message: 'Not found',
+ },
+ { status: 404 }
+ );
+ }),
+];
\ No newline at end of file
diff --git a/client/tests/mocks/server.ts b/client/tests/mocks/server.ts
new file mode 100644
index 000000000..84badefac
--- /dev/null
+++ b/client/tests/mocks/server.ts
@@ -0,0 +1,9 @@
+/**
+ * MSW (Mock Service Worker) server setup for tests
+ */
+
+import { setupServer } from 'msw/node';
+import { handlers } from './handlers';
+
+// This configures a request mocking server with the given request handlers.
+export const server = setupServer(...handlers);
\ No newline at end of file
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 8b4c66908..7394a3122 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -20,6 +20,7 @@
"mock/**/*",
"src/**/*",
"config/**/*",
+ "types/**/*",
".umirc.ts",
"typings.d.ts",
"tests/**/*.ts*"
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 1132bf888..6a7c1b5ea 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -77,11 +77,10 @@ services:
volumes:
- ./server/src:/opt/squirrelserversmanager/server/src
- ./.data.dev:/data
+ # - server_node_modules:/opt/squirrelserversmanager/server/node_modules
environment:
NODE_ENV: development
DEBUG: nodejs-docker-express:*
- ports:
- - "3000:3000" # Main API port
labels:
wud.display.name: "SSM - Server"
wud.watch.digest: false
@@ -100,6 +99,11 @@ services:
- ./client/src:/opt/squirrelserversmanager/client/src
- ./client/config:/opt/squirrelserversmanager/client/config
- ./client/public:/opt/squirrelserversmanager/client/public
+ # - client_node_modules:/opt/squirrelserversmanager/client/node_modules
labels:
wud.display.name: "SSM - Client"
- wud.watch.digest: false
\ No newline at end of file
+ wud.watch.digest: false
+
+volumes:
+ server_node_modules:
+ client_node_modules:
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 56a4cd4cf..19cf80dd3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,8 +7,6 @@
"name": "squirrel-servers-manager",
"dependencies": {
"@anthropic-ai/sdk": "^0.39.0",
- "@modelcontextprotocol/sdk": "^1.10.2",
- "@nestjs/microservices": "^11.0.21",
"boxen": "^8.0.1",
"chalk": "^4.1.2",
"cli-table3": "^0.6.5",
@@ -30,12 +28,9 @@
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.25.9",
- "@types/jest": "^29.5.14",
"@typescript-eslint/eslint-plugin": "^8.30.1",
"@typescript-eslint/parser": "^8.30.1",
- "husky": "^9.1.7",
- "jest": "^29.7.0",
- "ts-jest": "^29.3.2"
+ "husky": "^9.1.7"
}
},
"node_modules/@ampproject/remapping": {
@@ -44,6 +39,7 @@
"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"
@@ -88,6 +84,7 @@
"integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -98,6 +95,7 @@
"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",
@@ -129,6 +127,7 @@
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -146,7 +145,8 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
@@ -154,6 +154,7 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
+ "peer": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -194,6 +195,7 @@
"integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/compat-data": "^7.26.8",
"@babel/helper-validator-option": "^7.25.9",
@@ -211,6 +213,7 @@
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
+ "peer": true,
"dependencies": {
"yallist": "^3.0.2"
}
@@ -221,6 +224,7 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
+ "peer": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -277,6 +281,7 @@
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/traverse": "^7.25.9",
"@babel/types": "^7.25.9"
@@ -291,6 +296,7 @@
"integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/helper-module-imports": "^7.25.9",
"@babel/helper-validator-identifier": "^7.25.9",
@@ -384,6 +390,7 @@
"integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -394,6 +401,7 @@
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
@@ -454,61 +462,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-async-generators": {
- "version": "7.8.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
- "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-bigint": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
- "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-properties": {
- "version": "7.12.13",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
- "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.12.13"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-class-static-block": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
- "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-decorators": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz",
@@ -525,190 +478,6 @@
"@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-import-meta": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
- "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-json-strings": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
- "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
- "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
- "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-logical-assignment-operators": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
- "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
- "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-numeric-separator": {
- "version": "7.10.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
- "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.10.4"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-object-rest-spread": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
- "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-catch-binding": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
- "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-optional-chaining": {
- "version": "7.8.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
- "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.8.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-private-property-in-object": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
- "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-top-level-await": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
- "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.14.5"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
- "node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
- "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
- "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/template": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
@@ -792,13 +561,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -1491,613 +1253,579 @@
}
}
},
- "node_modules/@istanbuljs/load-nyc-config": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "camelcase": "^5.3.1",
- "find-up": "^4.1.0",
- "get-package-type": "^0.1.0",
- "js-yaml": "^3.13.1",
- "resolve-from": "^5.0.0"
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
- "node": ">=8"
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "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",
- "dependencies": {
- "sprintf-js": "~1.0.2"
+ "engines": {
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=6.0.0"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "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",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
+ "license": "MIT"
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "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": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
+ "integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
"license": "MIT",
"dependencies": {
- "p-locate": "^4.1.0"
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.3",
+ "eventsource": "^3.0.2",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
},
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@istanbuljs/load-nyc-config/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,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
- "p-try": "^2.0.0"
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
},
"engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.6"
}
},
- "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
- "p-limit": "^2.2.0"
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/@jest/console": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
- "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6.6.0"
}
},
- "node_modules/@jest/core": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
- "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/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==",
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/reporters": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-changed-files": "^29.7.0",
- "jest-config": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-resolve-dependencies": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-ansi": "^6.0.0"
+ "ms": "^2.1.3"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">=6.0"
},
"peerDependenciesMeta": {
- "node-notifier": {
+ "supports-color": {
"optional": true
}
}
},
- "node_modules/@jest/core/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,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/@jest/core/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,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
}
},
- "node_modules/@jest/environment": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
- "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
- "dependencies": {
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/@jest/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
- "expect": "^29.7.0",
- "jest-snapshot": "^29.7.0"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/@jest/expect-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
- "dependencies": {
- "jest-get-type": "^29.6.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/@jest/fake-timers": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
- "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@sinonjs/fake-timers": "^10.0.2",
- "@types/node": "*",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@jest/globals": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
- "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/types": "^29.6.3",
- "jest-mock": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.6"
}
},
- "node_modules/@jest/reporters": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
- "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@jest/console": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "collect-v8-coverage": "^1.0.0",
- "exit": "^0.1.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "istanbul-lib-coverage": "^3.0.0",
- "istanbul-lib-instrument": "^6.0.0",
- "istanbul-lib-report": "^3.0.0",
- "istanbul-lib-source-maps": "^4.0.0",
- "istanbul-reports": "^3.1.3",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "slash": "^3.0.0",
- "string-length": "^4.0.1",
- "strip-ansi": "^6.0.0",
- "v8-to-istanbul": "^9.0.1"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "mime-db": "^1.54.0"
},
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "engines": {
+ "node": ">= 0.6"
}
},
- "node_modules/@jest/reporters/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,
+ "node_modules/@modelcontextprotocol/sdk/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/@modelcontextprotocol/sdk/node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/@jest/reporters/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",
+ "node_modules/@modelcontextprotocol/sdk/node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "side-channel": "^1.1.0"
},
"engines": {
- "node": ">=8"
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
- "@sinclair/typebox": "^0.27.8"
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/@jest/source-map": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
- "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.18",
- "callsites": "^3.0.0",
- "graceful-fs": "^4.2.9"
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 18"
}
},
- "node_modules/@jest/test-result": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
- "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "collect-v8-coverage": "^1.0.0"
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 18"
}
},
- "node_modules/@jest/test-sequencer": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
- "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
- "@jest/test-result": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "slash": "^3.0.0"
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.6"
}
},
- "node_modules/@jest/transform": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
- "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
- "dev": true,
+ "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",
"dependencies": {
- "@babel/core": "^7.11.6",
- "@jest/types": "^29.6.3",
- "@jridgewell/trace-mapping": "^0.3.18",
- "babel-plugin-istanbul": "^6.1.1",
- "chalk": "^4.0.0",
- "convert-source-map": "^2.0.0",
- "fast-json-stable-stringify": "^2.1.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "micromatch": "^4.0.4",
- "pirates": "^4.0.4",
- "slash": "^3.0.0",
- "write-file-atomic": "^4.0.2"
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 8"
}
},
- "node_modules/@jest/types": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
- "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "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",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^3.0.0",
- "@types/node": "*",
- "@types/yargs": "^17.0.8",
- "chalk": "^4.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 8"
}
},
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "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": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
},
"engines": {
- "node": ">=6.0.0"
+ "node": ">= 8"
}
},
- "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"
- }
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "license": "MIT"
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
"license": "MIT",
"engines": {
- "node": ">=6.0.0"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "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,
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"license": "MIT"
},
- "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,
+ "node_modules/@tokenizer/inflate": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
+ "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
"license": "MIT",
"dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@lukeed/csprng": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
- "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
- "license": "MIT",
- "peer": true,
+ "debug": "^4.4.0",
+ "fflate": "^0.8.2",
+ "token-types": "^6.0.0"
+ },
"engines": {
- "node": ">=8"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
}
},
- "node_modules/@modelcontextprotocol/sdk": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
- "integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
+ "node_modules/@tokenizer/inflate/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==",
"license": "MIT",
"dependencies": {
- "content-type": "^1.0.5",
- "cors": "^2.8.5",
- "cross-spawn": "^7.0.3",
- "eventsource": "^3.0.2",
- "express": "^5.0.1",
- "express-rate-limit": "^7.5.0",
- "pkce-challenge": "^5.0.0",
- "raw-body": "^3.0.0",
- "zod": "^3.23.8",
- "zod-to-json-schema": "^3.24.1"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">=18"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "node_modules/@tokenizer/inflate/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/@tokenizer/token": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/node": {
+ "version": "18.19.86",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz",
+ "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==",
"license": "MIT",
"dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
+ "undici-types": "~5.26.4"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
- "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"license": "MIT",
"dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.0",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.6.3",
- "on-finished": "^2.4.1",
- "qs": "^6.14.0",
- "raw-body": "^3.0.0",
- "type-is": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
+ "@types/node": "*",
+ "form-data": "^4.0.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
- "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "node_modules/@types/tinycolor2": {
+ "version": "1.4.6",
+ "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz",
+ "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz",
+ "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "safe-buffer": "5.2.1"
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.30.1",
+ "@typescript-eslint/type-utils": "8.30.1",
+ "@typescript-eslint/utils": "8.30.1",
+ "@typescript-eslint/visitor-keys": "8.30.1",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.0.1"
},
"engines": {
- "node": ">= 0.6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz",
+ "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.30.1",
+ "@typescript-eslint/types": "8.30.1",
+ "@typescript-eslint/typescript-estree": "8.30.1",
+ "@typescript-eslint/visitor-keys": "8.30.1",
+ "debug": "^4.3.4"
+ },
"engines": {
- "node": ">=6.6.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/debug": {
+ "node_modules/@typescript-eslint/parser/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"
@@ -2111,1035 +1839,757 @@
}
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/express": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
- "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
- "license": "MIT",
+ "node_modules/@typescript-eslint/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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz",
+ "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.0",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
+ "@typescript-eslint/types": "8.30.1",
+ "@typescript-eslint/visitor-keys": "8.30.1"
},
"engines": {
- "node": ">= 18"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
- "url": "https://opencollective.com/express"
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
- "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz",
+ "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
+ "@typescript-eslint/typescript-estree": "8.30.1",
+ "@typescript-eslint/utils": "8.30.1",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.0.1"
},
"engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "node_modules/@typescript-eslint/type-utils/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": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">=0.10.0"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
+ "node_modules/@typescript-eslint/type-utils/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/@modelcontextprotocol/sdk/node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz",
+ "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz",
+ "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.30.1",
+ "@typescript-eslint/visitor-keys": "8.30.1",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.0.1"
+ },
"engines": {
- "node": ">= 0.6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
- "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "node_modules/@typescript-eslint/typescript-estree/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": {
- "mime-db": "^1.54.0"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/ms": {
+ "node_modules/@typescript-eslint/typescript-estree/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/@modelcontextprotocol/sdk/node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz",
+ "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "8.30.1",
+ "@typescript-eslint/types": "8.30.1",
+ "@typescript-eslint/typescript-estree": "8.30.1"
+ },
"engines": {
- "node": ">= 0.6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.9.0"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
- "license": "BSD-3-Clause",
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.30.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz",
+ "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "side-channel": "^1.1.0"
+ "@typescript-eslint/types": "8.30.1",
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
- "node": ">=0.6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": {
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/abort-controller": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
- "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
- "bytes": "3.1.2",
- "http-errors": "2.0.0",
- "iconv-lite": "0.6.3",
- "unpipe": "1.0.0"
+ "event-target-shim": "^5.0.0"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">=6.5"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/send": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
- "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.3.5",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "mime-types": "^3.0.1",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
- "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "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": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
},
"engines": {
- "node": ">= 18"
+ "node": ">= 0.6"
}
},
- "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">=0.4.0"
}
},
- "node_modules/@nestjs/common": {
- "version": "11.0.21",
- "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.0.21.tgz",
- "integrity": "sha512-5YgGVNIUJaVjSufbF+/8w5V4V3S/dQoyNsTfBOnlr/oP+7PYRn4Sl126Y0KF9rG81mZzMcZU6pkXi6ZJ+NxcMw==",
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
"license": "MIT",
"peer": true,
- "dependencies": {
- "file-type": "20.4.1",
- "iterare": "1.2.1",
- "load-esm": "1.0.2",
- "tslib": "2.8.1",
- "uid": "2.0.2"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nest"
- },
"peerDependencies": {
- "class-transformer": "*",
- "class-validator": "*",
- "reflect-metadata": "^0.1.12 || ^0.2.0",
- "rxjs": "^7.1.0"
- },
- "peerDependenciesMeta": {
- "class-transformer": {
- "optional": true
- },
- "class-validator": {
- "optional": true
- }
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
- "node_modules/@nestjs/core": {
- "version": "11.0.21",
- "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.0.21.tgz",
- "integrity": "sha512-E9ti75YGtzjXAQIp9b1hnHV8Ue6+7j5dyArav7yv4TvzabuGNKWGrFUBhG/nkS0nADxcICtukEPDqV6rI3jE6w==",
- "hasInstallScript": true,
+ "node_modules/agentkeepalive": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+ "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
- "@nuxt/opencollective": "0.4.1",
- "fast-safe-stringify": "2.1.1",
- "iterare": "1.2.1",
- "path-to-regexp": "8.2.0",
- "tslib": "2.8.1",
- "uid": "2.0.2"
+ "humanize-ms": "^1.2.1"
},
"engines": {
- "node": ">= 20"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nest"
- },
- "peerDependencies": {
- "@nestjs/common": "^11.0.0",
- "@nestjs/microservices": "^11.0.0",
- "@nestjs/platform-express": "^11.0.0",
- "@nestjs/websockets": "^11.0.0",
- "reflect-metadata": "^0.1.12 || ^0.2.0",
- "rxjs": "^7.1.0"
- },
- "peerDependenciesMeta": {
- "@nestjs/microservices": {
- "optional": true
- },
- "@nestjs/platform-express": {
- "optional": true
- },
- "@nestjs/websockets": {
- "optional": true
- }
+ "node": ">= 8.0.0"
}
},
- "node_modules/@nestjs/core/node_modules/path-to-regexp": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
- "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
"license": "MIT",
"peer": true,
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/@nestjs/microservices": {
- "version": "11.0.21",
- "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-11.0.21.tgz",
- "integrity": "sha512-sggF+n/kboPg7BI9roaId6QCt3c5oROkoXr7STLiHNSmYHkvdvfGeiQ/taRlByPNljogaG82h0uUdgaY0ceFxA==",
- "license": "MIT",
"dependencies": {
- "iterare": "1.2.1",
- "tslib": "2.8.1"
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nest"
- },
- "peerDependencies": {
- "@grpc/grpc-js": "*",
- "@nestjs/common": "^11.0.0",
- "@nestjs/core": "^11.0.0",
- "@nestjs/websockets": "^11.0.0",
- "amqp-connection-manager": "*",
- "amqplib": "*",
- "cache-manager": "*",
- "ioredis": "*",
- "kafkajs": "*",
- "mqtt": "*",
- "nats": "*",
- "reflect-metadata": "^0.1.12 || ^0.2.0",
- "rxjs": "^7.1.0"
- },
- "peerDependenciesMeta": {
- "@grpc/grpc-js": {
- "optional": true
- },
- "@nestjs/websockets": {
- "optional": true
- },
- "amqp-connection-manager": {
- "optional": true
- },
- "amqplib": {
- "optional": true
- },
- "cache-manager": {
- "optional": true
- },
- "ioredis": {
- "optional": true
- },
- "kafkajs": {
- "optional": true
- },
- "mqtt": {
- "optional": true
- },
- "nats": {
- "optional": true
- }
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
- "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/ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "license": "ISC",
"dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
+ "string-width": "^4.1.0"
}
},
- "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,
+ "node_modules/ansi-align/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==",
"license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">=8"
}
},
- "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,
+ "node_modules/ansi-align/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==",
+ "license": "MIT"
+ },
+ "node_modules/ansi-align/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==",
"license": "MIT",
"dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">= 8"
+ "node": ">=8"
}
},
- "node_modules/@nuxt/opencollective": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz",
- "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==",
+ "node_modules/ansi-align/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==",
"license": "MIT",
- "peer": true,
"dependencies": {
- "consola": "^3.2.3"
- },
- "bin": {
- "opencollective": "bin/opencollective.js"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": "^14.18.0 || >=16.10.0",
- "npm": ">=5.10.0"
+ "node": ">=8"
}
},
- "node_modules/@sec-ant/readable-stream": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
- "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
- "license": "MIT"
- },
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@sindresorhus/merge-streams": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
- "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "type-detect": "4.0.8"
- }
- },
- "node_modules/@sinonjs/fake-timers": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
- "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.0"
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@standard-schema/spec": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
- "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
- "license": "MIT"
- },
- "node_modules/@tokenizer/inflate": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
- "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
+ "node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "fflate": "^0.8.2",
- "token-types": "^6.0.0"
- },
"engines": {
- "node": ">=18"
+ "node": ">=12"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/@tokenizer/inflate/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==",
+ "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==",
"license": "MIT",
"dependencies": {
- "ms": "^2.1.3"
+ "color-convert": "^2.0.1"
},
"engines": {
- "node": ">=6.0"
+ "node": ">=8"
},
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/@tokenizer/inflate/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==",
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0",
+ "peer": true
+ },
+ "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/@tokenizer/token": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
- "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
+ "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/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
- }
+ "license": "MIT"
},
- "node_modules/@types/babel__generator": {
- "version": "7.27.0",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
- "dev": true,
+ "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": {
- "@babel/types": "^7.0.0"
+ "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/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
- "dev": true,
+ "node_modules/boxen": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
+ "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
+ "ansi-align": "^3.0.1",
+ "camelcase": "^8.0.0",
+ "chalk": "^5.3.0",
+ "cli-boxes": "^3.0.0",
+ "string-width": "^7.2.0",
+ "type-fest": "^4.21.0",
+ "widest-line": "^5.0.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@types/babel__traverse": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
- "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
- "dev": true,
+ "node_modules/boxen/node_modules/chalk": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
- "dependencies": {
- "@babel/types": "^7.20.7"
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/@types/estree": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
- "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/@types/graceful-fs": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
- "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "node_modules/brace-expansion": {
+ "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": {
- "@types/node": "*"
+ "balanced-match": "^1.0.0"
}
},
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/istanbul-lib-report": {
+ "node_modules/braces": {
"version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
- "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/istanbul-lib-coverage": "*"
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/@types/istanbul-reports": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
- "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "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",
+ "peer": true,
"dependencies": {
- "@types/istanbul-lib-report": "*"
+ "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/@types/jest": {
- "version": "29.5.14",
- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz",
- "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "expect": "^29.0.0",
- "pretty-format": "^29.0.0"
- }
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
},
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
- "peer": true
+ "engines": {
+ "node": ">= 0.8"
+ }
},
- "node_modules/@types/node": {
- "version": "18.19.86",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz",
- "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==",
+ "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": {
- "undici-types": "~5.26.4"
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/@types/node-fetch": {
- "version": "2.6.12",
- "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
- "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
+ "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": {
- "@types/node": "*",
- "form-data": "^4.0.0"
+ "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/@types/stack-utils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
- "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/tinycolor2": {
- "version": "1.4.6",
- "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz",
- "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==",
- "license": "MIT"
- },
- "node_modules/@types/yargs": {
- "version": "17.0.33",
- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
- "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/yargs-parser": "*"
+ "peer": true,
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/@types/yargs-parser": {
- "version": "21.0.3",
- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz",
- "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==",
- "dev": true,
+ "node_modules/camelcase": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
+ "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
"license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.30.1",
- "@typescript-eslint/type-utils": "8.30.1",
- "@typescript-eslint/utils": "8.30.1",
- "@typescript-eslint/visitor-keys": "8.30.1",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.1"
- },
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=16"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@typescript-eslint/parser": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz",
- "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001714",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz",
+ "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.30.1",
- "@typescript-eslint/types": "8.30.1",
- "@typescript-eslint/typescript-estree": "8.30.1",
- "@typescript-eslint/visitor-keys": "8.30.1",
- "debug": "^4.3.4"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
+ "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",
+ "peer": true
},
- "node_modules/@typescript-eslint/parser/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,
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
- "ms": "^2.1.3"
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
},
"engines": {
- "node": ">=6.0"
+ "node": ">=10"
},
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/@typescript-eslint/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==",
- "dev": true,
+ "node_modules/chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"license": "MIT"
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz",
- "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==",
- "dev": true,
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.30.1",
- "@typescript-eslint/visitor-keys": "8.30.1"
- },
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=10"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz",
- "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==",
- "dev": true,
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/typescript-estree": "8.30.1",
- "@typescript-eslint/utils": "8.30.1",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.1"
+ "restore-cursor": "^5.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
- }
- },
- "node_modules/@typescript-eslint/type-utils/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
- }
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@typescript-eslint/type-utils/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/@typescript-eslint/types": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz",
- "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==",
- "dev": true,
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=6"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz",
- "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==",
- "dev": true,
+ "node_modules/cli-table3": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
+ "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.30.1",
- "@typescript-eslint/visitor-keys": "8.30.1",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.1"
+ "string-width": "^4.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": "10.* || >= 12.*"
},
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.9.0"
+ "optionalDependencies": {
+ "@colors/colors": "1.5.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree/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,
+ "node_modules/cli-table3/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==",
"license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
"engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "node": ">=8"
}
},
- "node_modules/@typescript-eslint/typescript-estree/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,
+ "node_modules/cli-table3/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==",
"license": "MIT"
},
- "node_modules/@typescript-eslint/utils": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz",
- "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==",
- "dev": true,
+ "node_modules/cli-table3/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==",
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.30.1",
- "@typescript-eslint/types": "8.30.1",
- "@typescript-eslint/typescript-estree": "8.30.1"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.9.0"
+ "node": ">=8"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.30.1",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz",
- "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==",
- "dev": true,
+ "node_modules/cli-table3/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==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.30.1",
- "eslint-visitor-keys": "^4.2.0"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": ">=8"
}
},
- "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
- "license": "MIT",
- "dependencies": {
- "event-target-shim": "^5.0.0"
- },
- "engines": {
- "node": ">=6.5"
- }
- },
- "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/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "acorn": "bin/acorn"
- },
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "license": "ISC",
"engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "node": ">= 12"
}
},
- "node_modules/agentkeepalive": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
- "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
- "license": "MIT",
+ "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==",
+ "license": "ISC",
"dependencies": {
- "humanize-ms": "^1.2.1"
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
},
"engines": {
- "node": ">= 8.0.0"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-align": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
- "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.1.0"
+ "node": ">=12"
}
},
- "node_modules/ansi-align/node_modules/ansi-regex": {
+ "node_modules/cliui/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==",
@@ -3148,13 +2598,13 @@
"node": ">=8"
}
},
- "node_modules/ansi-align/node_modules/emoji-regex": {
+ "node_modules/cliui/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==",
"license": "MIT"
},
- "node_modules/ansi-align/node_modules/string-width": {
+ "node_modules/cliui/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==",
@@ -3168,7 +2618,7 @@
"node": ">=8"
}
},
- "node_modules/ansi-align/node_modules/strip-ansi": {
+ "node_modules/cliui/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==",
@@ -3180,525 +2630,317 @@
"node": ">=8"
}
},
- "node_modules/ansi-escapes": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "node_modules/cliui/node_modules/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==",
"license": "MIT",
"dependencies": {
- "type-fest": "^0.21.3"
- },
- "engines": {
- "node": ">=8"
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ansi-escapes/node_modules/type-fest": {
- "version": "0.21.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "color-name": "~1.1.4"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ "engines": {
+ "node": ">=7.0.0"
}
},
- "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==",
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "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": {
- "color-convert": "^2.0.1"
+ "delayed-stream": "~1.0.0"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">= 0.8"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
+ "node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">=16"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "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": "Python-2.0",
+ "license": "MIT",
"peer": true
},
- "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==",
- "dev": true,
- "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/babel-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
- "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
- "dev": true,
+ "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": {
- "@jest/transform": "^29.7.0",
- "@types/babel__core": "^7.1.14",
- "babel-plugin-istanbul": "^6.1.1",
- "babel-preset-jest": "^29.6.3",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "slash": "^3.0.0"
+ "safe-buffer": "5.2.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.8.0"
+ "node": ">= 0.6"
}
},
- "node_modules/babel-plugin-istanbul": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
- "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.0.0",
- "@istanbuljs/load-nyc-config": "^1.0.0",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-instrument": "^5.0.4",
- "test-exclude": "^6.0.0"
- },
+ "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": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
- "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "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==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/core": "^7.12.3",
- "@babel/parser": "^7.14.7",
- "@istanbuljs/schema": "^0.1.2",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^6.3.0"
- },
+ "license": "MIT",
+ "peer": true
+ },
+ "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": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/babel-plugin-istanbul/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/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/babel-plugin-jest-hoist": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
- "dev": true,
+ "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": {
- "@babel/template": "^7.3.3",
- "@babel/types": "^7.3.3",
- "@types/babel__core": "^7.1.14",
- "@types/babel__traverse": "^7.0.6"
+ "object-assign": "^4",
+ "vary": "^1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/babel-preset-current-node-syntax": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
- "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/plugin-syntax-async-generators": "^7.8.4",
- "@babel/plugin-syntax-bigint": "^7.8.3",
- "@babel/plugin-syntax-class-properties": "^7.12.13",
- "@babel/plugin-syntax-class-static-block": "^7.14.5",
- "@babel/plugin-syntax-import-attributes": "^7.24.7",
- "@babel/plugin-syntax-import-meta": "^7.10.4",
- "@babel/plugin-syntax-json-strings": "^7.8.3",
- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
- "@babel/plugin-syntax-numeric-separator": "^7.10.4",
- "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
- "@babel/plugin-syntax-optional-chaining": "^7.8.3",
- "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
- "@babel/plugin-syntax-top-level-await": "^7.14.5"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
+ "node": ">= 0.10"
}
},
- "node_modules/babel-preset-jest": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
- "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
- "dev": true,
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
- "babel-plugin-jest-hoist": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0"
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
+ "node": ">= 8"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
- "license": "MIT"
- },
- "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==",
+ "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": {
- "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"
+ "ms": "2.0.0"
}
},
- "node_modules/boxen": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
- "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-align": "^3.0.1",
- "camelcase": "^8.0.0",
- "chalk": "^5.3.0",
- "cli-boxes": "^3.0.0",
- "string-width": "^7.2.0",
- "type-fest": "^4.21.0",
- "widest-line": "^5.0.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
+ "peer": true
},
- "node_modules/boxen/node_modules/chalk": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
- "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
+ "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": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">=0.4.0"
}
},
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
+ "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",
- "dependencies": {
- "balanced-match": "^1.0.0"
+ "engines": {
+ "node": ">= 0.8"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
}
},
- "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"
- },
+ "node_modules/dotenv": {
+ "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": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
}
},
- "node_modules/bs-logger": {
- "version": "0.2.6",
- "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
- "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
- "dev": true,
+ "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": {
- "fast-json-stable-stringify": "2.x"
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
},
"engines": {
- "node": ">= 6"
+ "node": ">= 0.4"
}
},
- "node_modules/bser": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
- "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
- "dev": true,
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
- "node-int64": "^0.4.0"
+ "safe-buffer": "^5.0.1"
}
},
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
+ "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/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==",
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.138",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.138.tgz",
+ "integrity": "sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==",
"dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
+ "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"license": "MIT"
},
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "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/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==",
+ "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",
- "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==",
+ "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",
- "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/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/camelcase": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
- "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
+ "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",
- "engines": {
- "node": ">=16"
+ "dependencies": {
+ "es-errors": "^1.3.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/caniuse-lite": {
- "version": "1.0.30001714",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001714.tgz",
- "integrity": "sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==",
- "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/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": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">= 0.4"
}
},
- "node_modules/char-regex": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
- "dev": true,
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">=6"
}
},
- "node_modules/chardet": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
- "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "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/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cjs-module-lexer": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
- "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/cli-boxes": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
- "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -3706,2829 +2948,1162 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/cli-cursor": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
- "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "node_modules/eslint": {
+ "version": "9.24.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
+ "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "restore-cursor": "^5.0.0"
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.0",
+ "@eslint/core": "^0.12.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.24.0",
+ "@eslint/plugin-kit": "^0.2.7",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
},
- "engines": {
- "node": ">=18"
+ "bin": {
+ "eslint": "bin/eslint.js"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cli-spinners": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
- "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
- "license": "MIT",
"engines": {
- "node": ">=6"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
}
},
- "node_modules/cli-table3": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
- "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
- "license": "MIT",
+ "node_modules/eslint-scope": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
- "string-width": "^4.2.0"
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
},
"engines": {
- "node": "10.* || >= 12.*"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "optionalDependencies": {
- "@colors/colors": "1.5.0"
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/cli-table3/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==",
- "license": "MIT",
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
"engines": {
- "node": ">=8"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/cli-table3/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==",
- "license": "MIT"
- },
- "node_modules/cli-table3/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==",
+ "node_modules/eslint/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==",
+ "dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
}
},
- "node_modules/cli-table3/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==",
+ "node_modules/eslint/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": {
- "ansi-regex": "^5.0.1"
+ "ms": "^2.1.3"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/cli-width": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
- "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
- "license": "ISC",
- "engines": {
- "node": ">= 12"
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "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==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/cliui/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==",
- "license": "MIT",
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/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==",
- "license": "MIT"
- },
- "node_modules/cliui/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==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
- "engines": {
- "node": ">=8"
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/cliui/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==",
- "license": "MIT",
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "brace-expansion": "^1.1.7"
},
"engines": {
- "node": ">=8"
+ "node": "*"
}
},
- "node_modules/cliui/node_modules/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==",
+ "node_modules/eslint/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/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
},
"engines": {
- "node": ">=10"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/co": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
+ "peer": true,
"engines": {
- "iojs": ">= 1.0.0",
- "node": ">= 0.12.0"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/collect-v8-coverage": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
- "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
+ "license": "BSD-3-Clause",
+ "peer": true,
"dependencies": {
- "color-name": "~1.1.4"
+ "estraverse": "^5.1.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=0.10"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "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",
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
- "delayed-stream": "~1.0.0"
+ "estraverse": "^5.2.0"
},
"engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/commander": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
- "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
- "license": "MIT",
- "engines": {
- "node": ">=16"
+ "node": ">=4.0"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/consola": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
- "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
- "license": "MIT",
+ "license": "BSD-2-Clause",
"peer": true,
"engines": {
- "node": "^14.18.0 || >=16.10.0"
+ "node": ">=4.0"
}
},
- "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"
- },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
"engines": {
- "node": ">= 0.6"
+ "node": ">=0.10.0"
}
},
- "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==",
+ "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/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==",
- "dev": true,
- "license": "MIT"
- },
- "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==",
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
- "node": ">= 0.6"
+ "node": ">=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==",
+ "node_modules/eventsource": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
+ "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
"license": "MIT",
"dependencies": {
- "object-assign": "^4",
- "vary": "^1"
+ "eventsource-parser": "^3.0.1"
},
"engines": {
- "node": ">= 0.10"
+ "node": ">=18.0.0"
}
},
- "node_modules/create-jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
- "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
- "dev": true,
+ "node_modules/eventsource-parser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
+ "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "exit": "^0.1.2",
- "graceful-fs": "^4.2.9",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "prompts": "^2.0.1"
- },
- "bin": {
- "create-jest": "bin/create-jest.js"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18.0.0"
}
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "node_modules/execa": {
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz",
+ "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==",
"license": "MIT",
"dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "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/dedent": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
- "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "babel-plugin-macros": "^3.1.0"
- },
- "peerDependenciesMeta": {
- "babel-plugin-macros": {
- "optional": true
- }
- }
- },
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.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/detect-newline": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/dotenv": {
- "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"
- },
- "funding": {
- "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/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "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/ejs": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
- "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "jake": "^10.8.5"
- },
- "bin": {
- "ejs": "bin/cli.js"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/electron-to-chromium": {
- "version": "1.5.138",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.138.tgz",
- "integrity": "sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/emittery": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
- "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/emittery?sponsor=1"
- }
- },
- "node_modules/emoji-regex": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
- "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
- "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/error-ex": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.2.1"
- }
- },
- "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==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eslint": {
- "version": "9.24.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
- "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.20.0",
- "@eslint/config-helpers": "^0.2.0",
- "@eslint/core": "^0.12.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.24.0",
- "@eslint/plugin-kit": "^0.2.7",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.3.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
- }
- },
- "node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/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==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/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/eslint/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/eslint/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/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/espree/node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
- "peer": true,
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "peer": true,
- "dependencies": {
- "estraverse": "^5.1.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "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/event-target-shim": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
- "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/eventsource": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz",
- "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==",
- "license": "MIT",
- "dependencies": {
- "eventsource-parser": "^3.0.1"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/eventsource-parser": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
- "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/execa": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz",
- "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==",
- "license": "MIT",
- "dependencies": {
- "@sindresorhus/merge-streams": "^4.0.0",
- "cross-spawn": "^7.0.3",
- "figures": "^6.1.0",
- "get-stream": "^9.0.0",
- "human-signals": "^8.0.0",
- "is-plain-obj": "^4.1.0",
- "is-stream": "^4.0.1",
- "npm-run-path": "^6.0.0",
- "pretty-ms": "^9.0.0",
- "signal-exit": "^4.1.0",
- "strip-final-newline": "^4.0.0",
- "yoctocolors": "^2.0.0"
- },
- "engines": {
- "node": "^18.19.0 || >=20.5.0"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
- }
- },
- "node_modules/exit": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/expect": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/expect-utils": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "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/express-rate-limit": {
- "version": "7.5.0",
- "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
- "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://github.com/sponsors/express-rate-limit"
- },
- "peerDependencies": {
- "express": "^4.11 || 5 || ^5.0.0-beta.1"
- }
- },
- "node_modules/external-editor": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
- "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
- "license": "MIT",
- "dependencies": {
- "chardet": "^0.7.0",
- "iconv-lite": "^0.4.24",
- "tmp": "^0.0.33"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "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": ">=8.6.0"
- }
- },
- "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": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/fast-safe-stringify": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
- "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/fastmcp": {
- "version": "1.21.0",
- "resolved": "https://registry.npmjs.org/fastmcp/-/fastmcp-1.21.0.tgz",
- "integrity": "sha512-q4edIA6Mxo8/63JCvdZqnD2IfWVGXfLteZRDwewnr4BVmdv9TylaV+KBBPngf73KdAWc47UtXzDqUQUPL9vxOA==",
- "license": "MIT",
- "dependencies": {
- "@modelcontextprotocol/sdk": "^1.6.0",
- "@standard-schema/spec": "^1.0.0",
- "execa": "^9.5.2",
- "file-type": "^20.3.0",
- "fuse.js": "^7.1.0",
- "mcp-proxy": "^2.10.4",
- "strict-event-emitter-types": "^2.0.0",
- "undici": "^7.4.0",
- "uri-templates": "^0.2.0",
- "xsschema": "0.2.0-beta.2",
- "yargs": "^17.7.2",
- "zod": "^3.24.2",
- "zod-to-json-schema": "^3.24.5"
- },
- "bin": {
- "fastmcp": "dist/bin/fastmcp.js"
- }
- },
- "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": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/fb-watchman": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
- "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "bser": "2.1.1"
- }
- },
- "node_modules/fflate": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
- "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
- "license": "MIT"
- },
- "node_modules/figlet": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.1.tgz",
- "integrity": "sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg==",
- "license": "MIT",
- "bin": {
- "figlet": "bin/index.js"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/figures": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
- "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
- "license": "MIT",
- "dependencies": {
- "is-unicode-supported": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/file-type": {
- "version": "20.4.1",
- "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz",
- "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==",
- "license": "MIT",
- "dependencies": {
- "@tokenizer/inflate": "^0.2.6",
- "strtok3": "^10.2.0",
- "token-types": "^6.0.0",
- "uint8array-extras": "^1.4.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/file-type?sponsor=1"
- }
- },
- "node_modules/filelist": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
- "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "minimatch": "^5.0.1"
- }
- },
- "node_modules/filelist/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "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-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
- "license": "ISC",
- "peer": 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/form-data-encoder": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
- "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
- "license": "MIT"
- },
- "node_modules/formdata-node": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
- "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
- "license": "MIT",
- "dependencies": {
- "node-domexception": "1.0.0",
- "web-streams-polyfill": "4.0.0-beta.3"
- },
- "engines": {
- "node": ">= 12.20"
- }
- },
- "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",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "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/fuse.js": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
- "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=10"
- }
- },
- "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",
- "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==",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-east-asian-width": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
- "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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-package-type": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "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/get-stream": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
- "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
- "license": "MIT",
- "dependencies": {
- "@sec-ant/readable-stream": "^0.4.1",
- "is-stream": "^4.0.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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",
- "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"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "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",
- "peer": true,
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/glob/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/glob/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/gradient-string": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/gradient-string/-/gradient-string-3.0.0.tgz",
- "integrity": "sha512-frdKI4Qi8Ihp4C6wZNB565de/THpIaw3DjP5ku87M+N9rNSGmPTjfkq61SdRXB7eCaL8O1hkKDvf6CDMtOzIAg==",
- "license": "MIT",
- "dependencies": {
- "chalk": "^5.3.0",
- "tinygradient": "^1.1.5"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/gradient-string/node_modules/chalk": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
- "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
- "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==",
- "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"
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.3",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.0",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^18.19.0 || >=20.5.0"
},
"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/helmet": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
- "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
- "license": "MIT"
- },
- "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==",
+ "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",
- "inherits": "2.0.4",
+ "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",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/human-signals": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
- "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.18.0"
- }
- },
- "node_modules/humanize-ms": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.0.0"
- }
- },
- "node_modules/husky": {
- "version": "9.1.7",
- "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
- "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "husky": "bin.js"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/typicode"
- }
- },
- "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/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "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": "BSD-3-Clause"
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/import-local": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
- "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pkg-dir": "^4.2.0",
- "resolve-cwd": "^3.0.0"
- },
- "bin": {
- "import-local-fixture": "fixtures/cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
- "license": "MIT",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
"engines": {
- "node": ">=0.8.19"
- }
- },
- "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": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "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/inquirer": {
- "version": "12.5.2",
- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.5.2.tgz",
- "integrity": "sha512-qoDk/vdSTIaXNXAoNnlg7ubexpJfUo7t8GT2vylxvE49BrLhToFuPPdMViidG2boHV7+AcP1TCkJs/+PPoF2QQ==",
+ "node_modules/express-rate-limit": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
"license": "MIT",
- "dependencies": {
- "@inquirer/core": "^10.1.10",
- "@inquirer/prompts": "^7.4.1",
- "@inquirer/type": "^3.0.6",
- "ansi-escapes": "^4.3.2",
- "mute-stream": "^2.0.0",
- "run-async": "^3.0.0",
- "rxjs": "^7.8.2"
- },
"engines": {
- "node": ">=18"
+ "node": ">= 16"
},
- "peerDependencies": {
- "@types/node": ">=18"
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
},
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- }
+ "peerDependencies": {
+ "express": "^4.11 || 5 || ^5.0.0-beta.1"
}
},
- "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==",
+ "node_modules/external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"license": "MIT",
+ "dependencies": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ },
"engines": {
- "node": ">= 0.10"
+ "node": ">=4"
}
},
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "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": {
- "hasown": "^2.0.2"
+ "@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": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8.6.0"
}
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "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": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "license": "MIT",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 6"
}
},
- "node_modules/is-generator-fn": {
+ "node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "peer": true
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
+ "peer": true
},
- "node_modules/is-interactive": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
- "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "node_modules/fastmcp": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/fastmcp/-/fastmcp-1.21.0.tgz",
+ "integrity": "sha512-q4edIA6Mxo8/63JCvdZqnD2IfWVGXfLteZRDwewnr4BVmdv9TylaV+KBBPngf73KdAWc47UtXzDqUQUPL9vxOA==",
"license": "MIT",
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.6.0",
+ "@standard-schema/spec": "^1.0.0",
+ "execa": "^9.5.2",
+ "file-type": "^20.3.0",
+ "fuse.js": "^7.1.0",
+ "mcp-proxy": "^2.10.4",
+ "strict-event-emitter-types": "^2.0.0",
+ "undici": "^7.4.0",
+ "uri-templates": "^0.2.0",
+ "xsschema": "0.2.0-beta.2",
+ "yargs": "^17.7.2",
+ "zod": "^3.24.2",
+ "zod-to-json-schema": "^3.24.5"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "bin": {
+ "fastmcp": "dist/bin/fastmcp.js"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "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": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
+ "node_modules/figlet": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.1.tgz",
+ "integrity": "sha512-kEC3Sme+YvA8Hkibv0NR1oClGcWia0VB2fC1SlMy027cwe795Xx40Xiv/nw/iFAwQLupymWh+uhAAErn/7hwPg==",
"license": "MIT",
+ "bin": {
+ "figlet": "bin/index.js"
+ },
"engines": {
- "node": ">=0.12.0"
+ "node": ">= 0.4.0"
}
},
- "node_modules/is-plain-obj": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
- "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
"license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
- "license": "MIT"
- },
- "node_modules/is-stream": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
- "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=18"
+ "peer": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">=16.0.0"
}
},
- "node_modules/is-unicode-supported": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
- "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "node_modules/file-type": {
+ "version": "20.4.1",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz",
+ "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==",
"license": "MIT",
+ "dependencies": {
+ "@tokenizer/inflate": "^0.2.6",
+ "strtok3": "^10.2.0",
+ "token-types": "^6.0.0",
+ "uint8array-extras": "^1.4.0"
+ },
"engines": {
"node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC"
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
"engines": {
"node": ">=8"
}
},
- "node_modules/istanbul-lib-instrument": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "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": {
- "@babel/core": "^7.23.9",
- "@babel/parser": "^7.23.9",
- "@istanbuljs/schema": "^0.1.3",
- "istanbul-lib-coverage": "^3.2.0",
- "semver": "^7.5.4"
+ "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": ">=10"
+ "node": ">= 0.8"
}
},
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
+ "peer": true,
"dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/istanbul-lib-source-maps": {
+ "node_modules/flat-cache": {
"version": "4.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "MIT",
+ "peer": true,
"dependencies": {
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0",
- "source-map": "^0.6.1"
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
},
"engines": {
- "node": ">=10"
+ "node": ">=16"
}
},
- "node_modules/istanbul-lib-source-maps/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==",
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
+ "license": "ISC",
+ "peer": 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": {
- "ms": "^2.1.3"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "node": ">= 6"
}
},
- "node_modules/istanbul-lib-source-maps/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,
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
"license": "MIT"
},
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "license": "MIT",
"dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
- "node": ">=8"
+ "node": ">= 12.20"
}
},
- "node_modules/iterare": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
- "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==",
- "license": "ISC",
+ "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": ">=6"
+ "node": ">= 0.6"
}
},
- "node_modules/jake": {
- "version": "10.9.2",
- "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
- "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==",
- "dev": true,
+ "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/fuse.js": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
+ "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
- "dependencies": {
- "async": "^3.2.3",
- "chalk": "^4.0.2",
- "filelist": "^1.0.4",
- "minimatch": "^3.1.2"
- },
- "bin": {
- "jake": "bin/cli.js"
- },
"engines": {
"node": ">=10"
}
},
- "node_modules/jake/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==",
+ "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",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
}
},
- "node_modules/jake/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
+ "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==",
"license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
"engines": {
- "node": "*"
+ "node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/jest": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
- "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
- "dev": true,
+ "node_modules/get-east-asian-width": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
+ "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"license": "MIT",
- "dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/types": "^29.6.3",
- "import-local": "^3.0.2",
- "jest-cli": "^29.7.0"
- },
- "bin": {
- "jest": "bin/jest.js"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">=18"
},
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-changed-files": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
- "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
- "dev": true,
+ "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": {
- "execa": "^5.0.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0"
+ "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": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-changed-files/node_modules/execa": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
- "dev": true,
+ "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": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^6.0.0",
- "human-signals": "^2.1.0",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.1",
- "onetime": "^5.1.2",
- "signal-exit": "^3.0.3",
- "strip-final-newline": "^2.0.0"
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
},
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ "node": ">= 0.4"
}
},
- "node_modules/jest-changed-files/node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
- "dev": true,
+ "node_modules/get-stream": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-changed-files/node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "node_modules/glob-parent": {
+ "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": "Apache-2.0",
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
"engines": {
- "node": ">=10.17.0"
+ "node": ">=10.13.0"
}
},
- "node_modules/jest-changed-files/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==",
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
- "node": ">=8"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-changed-files/node_modules/npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
- "dev": true,
+ "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/gradient-string": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/gradient-string/-/gradient-string-3.0.0.tgz",
+ "integrity": "sha512-frdKI4Qi8Ihp4C6wZNB565de/THpIaw3DjP5ku87M+N9rNSGmPTjfkq61SdRXB7eCaL8O1hkKDvf6CDMtOzIAg==",
"license": "MIT",
"dependencies": {
- "path-key": "^3.0.0"
+ "chalk": "^5.3.0",
+ "tinygradient": "^1.1.5"
},
"engines": {
- "node": ">=8"
+ "node": ">=14"
}
},
- "node_modules/jest-changed-files/node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
- "dev": true,
+ "node_modules/gradient-string/node_modules/chalk": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"license": "MIT",
- "dependencies": {
- "mimic-fn": "^2.1.0"
- },
"engines": {
- "node": ">=6"
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/jest-changed-files/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/jest-changed-files/node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "license": "MIT"
},
- "node_modules/jest-circus": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
- "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
- "dev": true,
+ "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==",
"license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/expect": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "co": "^4.6.0",
- "dedent": "^1.0.0",
- "is-generator-fn": "^2.0.0",
- "jest-each": "^29.7.0",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "p-limit": "^3.1.0",
- "pretty-format": "^29.7.0",
- "pure-rand": "^6.0.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-cli": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
- "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
- "dev": true,
+ "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",
- "dependencies": {
- "@jest/core": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "create-jest": "^29.7.0",
- "exit": "^0.1.2",
- "import-local": "^3.0.2",
- "jest-config": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "yargs": "^17.3.1"
- },
- "bin": {
- "jest": "bin/jest.js"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-config": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
- "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
- "dev": true,
+ "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": {
- "@babel/core": "^7.11.6",
- "@jest/test-sequencer": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-jest": "^29.7.0",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "deepmerge": "^4.2.2",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-circus": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-runner": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "micromatch": "^4.0.4",
- "parse-json": "^5.2.0",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "strip-json-comments": "^3.1.1"
+ "has-symbols": "^1.0.3"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- },
- "peerDependencies": {
- "@types/node": "*",
- "ts-node": ">=9.0.0"
+ "node": ">= 0.4"
},
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/jest-diff": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
- "dev": true,
+ "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": {
- "chalk": "^4.0.0",
- "diff-sequences": "^29.6.3",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "function-bind": "^1.1.2"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.4"
}
},
- "node_modules/jest-docblock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
- "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
- "dev": true,
+ "node_modules/helmet": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+ "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
"license": "MIT",
- "dependencies": {
- "detect-newline": "^3.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18.0.0"
}
},
- "node_modules/jest-each": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
- "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
- "dev": true,
+ "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": {
- "@jest/types": "^29.6.3",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "jest-util": "^29.7.0",
- "pretty-format": "^29.7.0"
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.8"
}
},
- "node_modules/jest-environment-node": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
- "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-mock": "^29.7.0",
- "jest-util": "^29.7.0"
- },
+ "node_modules/human-signals": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
+ "license": "Apache-2.0",
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18.18.0"
}
},
- "node_modules/jest-get-type": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
- "dev": true,
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"license": "MIT",
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "dependencies": {
+ "ms": "^2.0.0"
}
},
- "node_modules/jest-haste-map": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
- "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "node_modules/husky": {
+ "version": "9.1.7",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/graceful-fs": "^4.1.3",
- "@types/node": "*",
- "anymatch": "^3.0.3",
- "fb-watchman": "^2.0.0",
- "graceful-fs": "^4.2.9",
- "jest-regex-util": "^29.6.3",
- "jest-util": "^29.7.0",
- "jest-worker": "^29.7.0",
- "micromatch": "^4.0.4",
- "walker": "^1.0.8"
+ "bin": {
+ "husky": "bin.js"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18"
},
- "optionalDependencies": {
- "fsevents": "^2.3.2"
+ "funding": {
+ "url": "https://github.com/sponsors/typicode"
}
},
- "node_modules/jest-leak-detector": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
- "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
- "dev": true,
+ "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": {
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
+ "safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-matcher-utils": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "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": "BSD-3-Clause"
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.0.0",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "pretty-format": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 4"
}
},
- "node_modules/jest-message-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
- "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@babel/code-frame": "^7.12.13",
- "@jest/types": "^29.6.3",
- "@types/stack-utils": "^2.0.0",
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "micromatch": "^4.0.4",
- "pretty-format": "^29.7.0",
- "slash": "^3.0.0",
- "stack-utils": "^2.0.3"
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-mock": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
- "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "jest-util": "^29.7.0"
- },
+ "peer": true,
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.8.19"
}
},
- "node_modules/jest-pnp-resolver": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
- "dev": true,
+ "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/inquirer": {
+ "version": "12.5.2",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.5.2.tgz",
+ "integrity": "sha512-qoDk/vdSTIaXNXAoNnlg7ubexpJfUo7t8GT2vylxvE49BrLhToFuPPdMViidG2boHV7+AcP1TCkJs/+PPoF2QQ==",
"license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^10.1.10",
+ "@inquirer/prompts": "^7.4.1",
+ "@inquirer/type": "^3.0.6",
+ "ansi-escapes": "^4.3.2",
+ "mute-stream": "^2.0.0",
+ "run-async": "^3.0.0",
+ "rxjs": "^7.8.2"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=18"
},
"peerDependencies": {
- "jest-resolve": "*"
+ "@types/node": ">=18"
},
"peerDependenciesMeta": {
- "jest-resolve": {
+ "@types/node": {
"optional": true
}
}
},
- "node_modules/jest-regex-util": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
- "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
- "dev": true,
+ "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": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">= 0.10"
}
},
- "node_modules/jest-resolve": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
- "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.0.0",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-pnp-resolver": "^1.2.2",
- "jest-util": "^29.7.0",
- "jest-validate": "^29.7.0",
- "resolve": "^1.20.0",
- "resolve.exports": "^2.0.0",
- "slash": "^3.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-resolve-dependencies": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
- "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
- "dev": true,
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
- "dependencies": {
- "jest-regex-util": "^29.6.3",
- "jest-snapshot": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=8"
}
},
- "node_modules/jest-runner": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
- "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jest/console": "^29.7.0",
- "@jest/environment": "^29.7.0",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "graceful-fs": "^4.2.9",
- "jest-docblock": "^29.7.0",
- "jest-environment-node": "^29.7.0",
- "jest-haste-map": "^29.7.0",
- "jest-leak-detector": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-resolve": "^29.7.0",
- "jest-runtime": "^29.7.0",
- "jest-util": "^29.7.0",
- "jest-watcher": "^29.7.0",
- "jest-worker": "^29.7.0",
- "p-limit": "^3.1.0",
- "source-map-support": "0.5.13"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-runtime": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
- "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/environment": "^29.7.0",
- "@jest/fake-timers": "^29.7.0",
- "@jest/globals": "^29.7.0",
- "@jest/source-map": "^29.6.3",
- "@jest/test-result": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "cjs-module-lexer": "^1.0.0",
- "collect-v8-coverage": "^1.0.0",
- "glob": "^7.1.3",
- "graceful-fs": "^4.2.9",
- "jest-haste-map": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-mock": "^29.7.0",
- "jest-regex-util": "^29.6.3",
- "jest-resolve": "^29.7.0",
- "jest-snapshot": "^29.7.0",
- "jest-util": "^29.7.0",
- "slash": "^3.0.0",
- "strip-bom": "^4.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/jest-snapshot": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/core": "^7.11.6",
- "@babel/generator": "^7.7.2",
- "@babel/plugin-syntax-jsx": "^7.7.2",
- "@babel/plugin-syntax-typescript": "^7.7.2",
- "@babel/types": "^7.3.3",
- "@jest/expect-utils": "^29.7.0",
- "@jest/transform": "^29.7.0",
- "@jest/types": "^29.6.3",
- "babel-preset-current-node-syntax": "^1.0.0",
- "chalk": "^4.0.0",
- "expect": "^29.7.0",
- "graceful-fs": "^4.2.9",
- "jest-diff": "^29.7.0",
- "jest-get-type": "^29.6.3",
- "jest-matcher-utils": "^29.7.0",
- "jest-message-util": "^29.7.0",
- "jest-util": "^29.7.0",
- "natural-compare": "^1.4.0",
- "pretty-format": "^29.7.0",
- "semver": "^7.5.3"
+ "is-extglob": "^2.1.1"
},
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/jest-util": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
- "dev": true,
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "chalk": "^4.0.0",
- "ci-info": "^3.2.0",
- "graceful-fs": "^4.2.9",
- "picomatch": "^2.2.3"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-validate": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
- "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jest/types": "^29.6.3",
- "camelcase": "^6.2.0",
- "chalk": "^4.0.0",
- "jest-get-type": "^29.6.3",
- "leven": "^3.1.0",
- "pretty-format": "^29.7.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=0.12.0"
}
},
- "node_modules/jest-validate/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-watcher": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
- "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/test-result": "^29.7.0",
- "@jest/types": "^29.6.3",
- "@types/node": "*",
- "ansi-escapes": "^4.2.1",
- "chalk": "^4.0.0",
- "emittery": "^0.13.1",
- "jest-util": "^29.7.0",
- "string-length": "^4.0.1"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
},
- "node_modules/jest-worker": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
- "dev": true,
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "jest-util": "^29.7.0",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
- },
"engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/jest-worker/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==",
- "dev": true,
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6571,13 +4146,6 @@
"license": "MIT",
"peer": true
},
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6600,6 +4168,7 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"json5": "lib/cli.js"
},
@@ -6667,26 +4236,6 @@
"json-buffer": "3.0.1"
}
},
- "node_modules/kleur": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/leven": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -6702,33 +4251,6 @@
"node": ">= 0.8.0"
}
},
- "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/load-esm": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
- "integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/Borewit"
- },
- {
- "type": "buymeacoffee",
- "url": "https://buymeacoffee.com/borewit"
- }
- ],
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=13.2.0"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -6782,13 +4304,6 @@
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
- "node_modules/lodash.memoize": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
- "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -6849,39 +4364,6 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/makeerror": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
- "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "tmpl": "1.0.5"
- }
- },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -6923,13 +4405,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -6996,16 +4471,6 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
@@ -7104,29 +4569,13 @@
}
}
},
- "node_modules/node-int64": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
- "dev": true,
- "license": "MIT"
- },
"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/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
+ "peer": true
},
"node_modules/npm-run-path": {
"version": "6.0.0",
@@ -7312,6 +4761,7 @@
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -7339,16 +4789,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -7363,25 +4803,6 @@
"node": ">=6"
}
},
- "node_modules/parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/parse-ms": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
@@ -7409,20 +4830,11 @@
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
},
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -7432,13 +4844,6 @@
"node": ">=8"
}
},
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "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",
@@ -7478,16 +4883,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pirates": {
- "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/pkce-challenge": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
@@ -7497,75 +4892,6 @@
"node": ">=16.20.0"
}
},
- "node_modules/pkg-dir": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-dir/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/pkg-dir/node_modules/p-locate": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -7577,34 +4903,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/pretty-ms": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz",
@@ -7614,24 +4912,10 @@
"parse-ms": "^4.0.0"
},
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/prompts": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kleur": "^3.0.3",
- "sisteransi": "^1.0.5"
- },
- "engines": {
- "node": ">= 6"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/proxy-addr": {
@@ -7658,23 +4942,6 @@
"node": ">=6"
}
},
- "node_modules/pure-rand": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
- "dev": true,
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/dubzzz"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fast-check"
- }
- ],
- "license": "MIT"
- },
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -7735,20 +5002,6 @@
"node": ">= 0.8"
}
},
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/reflect-metadata": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
- "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
- "license": "Apache-2.0",
- "peer": true
- },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -7758,50 +5011,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-cwd": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
- "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "resolve-from": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/resolve-cwd/node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -7813,16 +5022,6 @@
"node": ">=4"
}
},
- "node_modules/resolve.exports": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
- "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
@@ -8143,74 +5342,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/sisteransi": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
- "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/slash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "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==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.13",
- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
- "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/stack-utils": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "escape-string-regexp": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/stack-utils/node_modules/escape-string-regexp": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -8238,43 +5369,6 @@
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==",
"license": "ISC"
},
- "node_modules/string-length": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "char-regex": "^1.0.2",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/string-length/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-length/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/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@@ -8307,16 +5401,6 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/strip-bom": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-final-newline": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
@@ -8335,6 +5419,7 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
},
@@ -8371,58 +5456,6 @@
"node": ">=8"
}
},
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/test-exclude/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==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
@@ -8451,13 +5484,6 @@
"node": ">=0.6.0"
}
},
- "node_modules/tmpl": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
- "dev": true,
- "license": "BSD-3-Clause"
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -8516,56 +5542,6 @@
"typescript": ">=4.8.4"
}
},
- "node_modules/ts-jest": {
- "version": "29.3.2",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz",
- "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bs-logger": "^0.2.6",
- "ejs": "^3.1.10",
- "fast-json-stable-stringify": "^2.1.0",
- "jest-util": "^29.0.0",
- "json5": "^2.2.3",
- "lodash.memoize": "^4.1.2",
- "make-error": "^1.3.6",
- "semver": "^7.7.1",
- "type-fest": "^4.39.1",
- "yargs-parser": "^21.1.1"
- },
- "bin": {
- "ts-jest": "cli.js"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0"
- },
- "peerDependencies": {
- "@babel/core": ">=7.0.0-beta.0 <8",
- "@jest/transform": "^29.0.0",
- "@jest/types": "^29.0.0",
- "babel-jest": "^29.0.0",
- "jest": "^29.0.0",
- "typescript": ">=4.3 <6"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "@jest/transform": {
- "optional": true
- },
- "@jest/types": {
- "optional": true
- },
- "babel-jest": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- }
- }
- },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -8586,16 +5562,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/type-fest": {
"version": "4.40.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
@@ -8636,19 +5602,6 @@
"node": ">=14.17"
}
},
- "node_modules/uid": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",
- "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@lukeed/csprng": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/uint8array-extras": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
@@ -8717,6 +5670,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
@@ -8754,21 +5708,6 @@
"node": ">= 0.4.0"
}
},
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
- "engines": {
- "node": ">=10.12.0"
- }
- },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -8778,16 +5717,6 @@
"node": ">= 0.8"
}
},
- "node_modules/walker": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
- "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "makeerror": "1.0.12"
- }
- },
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
@@ -8889,27 +5818,6 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
- "node_modules/write-file-atomic": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
- "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4",
- "signal-exit": "^3.0.7"
- },
- "engines": {
- "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
- }
- },
- "node_modules/write-file-atomic/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/xsschema": {
"version": "0.2.0-beta.2",
"resolved": "https://registry.npmjs.org/xsschema/-/xsschema-0.2.0-beta.2.tgz",
@@ -8950,7 +5858,8 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/yargs": {
"version": "17.7.2",
@@ -9026,6 +5935,7 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
diff --git a/plugins b/plugins
index 43384b30a..6be0ac966 160000
--- a/plugins
+++ b/plugins
@@ -1 +1 @@
-Subproject commit 43384b30a227733c8777ef8a06e33460a54ca235
+Subproject commit 6be0ac9668f95f5a7fe00d3984040f730e486626
diff --git a/server/README-improved.md b/server/README-improved.md
new file mode 100644
index 000000000..b3314a157
--- /dev/null
+++ b/server/README-improved.md
@@ -0,0 +1,295 @@
+
+
+---
+
+## 🎯 Overview
+
+**Squirrel Servers Manager (SSM)** is the backend service that powers the server management platform. Built on **NestJS** with **Clean Architecture** principles, it provides robust APIs for:
+
+- 🐳 **Container orchestration** and management
+- 📚 **Ansible playbook** execution and automation
+- 🔐 **SSH/SFTP** secure connectivity
+- 📊 **Real-time monitoring** and statistics
+- 🔔 **Smart notifications** and alerts
+- 🛡️ **Role-based access control**
+
+## 🚀 Quick Start
+
+### Prerequisites
+
+- Node.js 18+
+- MongoDB 4.4+
+- Docker (optional, for container management)
+- Ansible (optional, for playbook execution)
+
+### Installation
+
+```bash
+# Clone the repository
+git clone https://github.com/SquirrelCorporation/SquirrelServersManager.git
+cd SquirrelServersManager/server
+
+# Install dependencies
+npm install
+
+# Configure environment
+cp .env.example .env
+
+# Start development server
+npm run dev
+```
+
+### Environment Configuration
+
+```env
+# Database
+DB_HOST=localhost
+DB_PORT=27017
+DB_NAME=ssm
+DB_USER=ssm_user
+DB_USER_PWD=your_password
+DB_AUTH_SOURCE=admin
+
+# Application
+APP_PORT=3000
+JWT_SECRET=your_jwt_secret
+```
+
+## 🌟 Features
+
+
+
+
+
+### 🐳 Container Management
+- Full Docker API integration
+- Multi-registry support
+- Real-time container stats
+- Docker Compose orchestration
+
+
+
+
+### 📚 Ansible Integration
+- Playbook execution engine
+- Dynamic inventory management
+- Galaxy collections support
+- Real-time execution logs
+
+
+
+
+
+
+### 🔐 Security & Access
+- JWT-based authentication
+- Role-based permissions
+- SSH key management
+- Encrypted credential storage
+
+
+
+
+### 📊 Monitoring & Analytics
+- Real-time system metrics
+- Custom dashboards
+- Alert management
+- Performance tracking
+
+
+
+
+
+## 🏗️ Architecture
+
+### Clean Architecture Layers
+
+```mermaid
+graph TD
+ A[Presentation Layer] --> B[Application Layer]
+ B --> C[Domain Layer]
+ B --> D[Infrastructure Layer]
+
+ style A fill:#e1f5fe
+ style B fill:#b3e5fc
+ style C fill:#4fc3f7
+ style D fill:#81c784
+```
+
+### Module Structure
+
+Each module follows a consistent structure:
+
+```
+📦 module-name/
+├── 📁 domain/
+│ ├── entities/ # Business entities
+│ ├── interfaces/ # Repository contracts
+│ └── types/ # Domain types
+├── 📁 application/
+│ ├── services/ # Business logic
+│ └── interfaces/ # Service contracts
+├── 📁 infrastructure/
+│ ├── repositories/ # Data access
+│ └── schemas/ # Database models
+├── 📁 presentation/
+│ ├── controllers/ # REST endpoints
+│ ├── gateways/ # WebSocket handlers
+│ └── dto/ # Data transfer objects
+└── 📁 __tests__/ # Comprehensive tests
+```
+
+## 📦 Core Modules
+
+
+🎭 Ansible Module
+
+Manages Ansible playbook execution and inventory:
+- Dynamic inventory generation
+- Playbook repository management
+- Real-time execution monitoring
+- Galaxy collection support
+
+
+
+🐳 Containers Module
+
+Complete Docker container lifecycle management:
+- Container CRUD operations
+- Image management
+- Network configuration
+- Volume handling
+
+
+
+🖥️ Devices Module
+
+Remote device management and monitoring:
+- SSH connectivity
+- System metrics collection
+- Health monitoring
+- Batch operations
+
+
+
+👥 Users Module
+
+Authentication and authorization:
+- JWT token management
+- Role-based access control
+- User preferences
+- API key generation
+
+
+## 🧪 Testing
+
+```bash
+# Run all tests
+npm test
+
+# Run tests with coverage
+npm run test:cov
+
+# Run specific test file
+npm run test -- path/to/test.spec.ts
+
+# Watch mode
+npm run test:dev
+```
+
+### Test Structure
+- **Unit Tests**: Service and utility function tests
+- **Integration Tests**: Module interaction tests
+- **E2E Tests**: Full API endpoint tests
+
+## 📚 API Documentation
+
+The server provides comprehensive API documentation:
+
+- **Swagger UI**: Available at `/api/docs` when running
+- **OpenAPI Spec**: Exportable for client generation
+
+## 🔧 Development
+
+### Code Style
+
+We follow strict TypeScript and NestJS conventions:
+
+```typescript
+// ✅ Good: Clear separation of concerns
+@Injectable()
+export class DeviceService implements IDeviceService {
+ constructor(
+ @Inject('IDeviceRepository')
+ private readonly deviceRepository: IDeviceRepository,
+ ) {}
+}
+
+// ❌ Bad: Direct repository usage in controllers
+```
+
+### Build Commands
+
+```bash
+# Development
+npm run dev
+
+# Production build
+npm run build
+
+# Linting
+npm run lint:check
+npm run lint:fix
+
+# Type checking
+npm run typecheck
+```
+
+## 🤝 Contributing
+
+We welcome contributions! Please see our [Contributing Guide](../CONTRIBUTING.md) for details.
+
+### Development Workflow
+
+1. Fork the repository
+2. Create a feature branch
+3. Make your changes with tests
+4. Run linting and tests
+5. Submit a pull request
+
+## 📄 License
+
+This project is licensed under the GNU Affero General Public License v3.0 - see the [LICENSE](../LICENSE) file for details.
+
+---
+
+
+
Built with ❤️ by the Squirrel Corporation team
+
+
+
+
+
\ No newline at end of file
diff --git a/server/README-ultimate.md b/server/README-ultimate.md
new file mode 100644
index 000000000..84e00373b
--- /dev/null
+++ b/server/README-ultimate.md
@@ -0,0 +1,608 @@
+
+
+
+
+---
+
+## 🌟 Why SSM Backend?
+
+
+
+
+
+ Clean Architecture
+ Modular, testable, and maintainable code following SOLID principles
+
+
+
+ Enterprise Security
+ JWT auth, role-based access, encrypted storage, and SSH key management
+
+
+
+ Powerful Integrations
+ Native support for Docker, Ansible, SSH, Git, and more
+
+
+
+
+
+
+## 🚀 Quick Start
+
+### 📋 Prerequisites
+
+
+
+
+
+**Required**
+- Node.js 18+ (LTS)
+- MongoDB 4.4+
+- npm or yarn
+
+
+
+
+**Optional**
+- Docker (for container features)
+- Ansible (for playbook features)
+- Git (for repository features)
+
+
+
+
+
+### 🏃 Get Running in 60 Seconds
+
+```bash
+# 1️⃣ Clone and enter the project
+git clone https://github.com/SquirrelCorporation/SquirrelServersManager.git
+cd SquirrelServersManager/server
+
+# 2️⃣ Install dependencies
+npm install
+
+# 3️⃣ Set up your environment
+cp .env.example .env
+# Edit .env with your settings
+
+# 4️⃣ Start the development server
+npm run dev
+
+# 🎉 Server is running at http://localhost:3000
+# 📚 API docs at http://localhost:3000/api/docs
+```
+
+### 🔧 Essential Configuration
+
+
+Environment Variables Guide
+
+```env
+# 🗄️ Database Configuration
+DB_HOST=localhost # MongoDB host
+DB_PORT=27017 # MongoDB port
+DB_NAME=ssm # Database name
+DB_USER=ssm_user # Database user (optional)
+DB_USER_PWD=your_password # Database password (optional)
+DB_AUTH_SOURCE=admin # Auth database (default: admin)
+
+# 🌐 Application Settings
+APP_PORT=3000 # Server port
+JWT_SECRET=your-secret-key # JWT signing secret
+NODE_ENV=development # Environment (development/production)
+
+# 🔐 Security
+ENCRYPTION_KEY=32-char-key # For sensitive data encryption
+SESSION_TIMEOUT=3600 # Session timeout in seconds
+
+# 📦 Optional Features
+ENABLE_TELEMETRY=false # Anonymous usage stats
+ENABLE_UPDATES=true # Auto-update checks
+LOG_LEVEL=info # Logging level
+```
+
+
+
+
+
+## ✨ Features
+
+### 🎯 Core Capabilities
+
+
+
+
+
+#### 🐳 Container Management
+```typescript
+// Full Docker API integration
+await containerService.create({
+ name: 'my-app',
+ image: 'nginx:latest',
+ ports: { '80/tcp': [{ HostPort: '8080' }] }
+});
+```
+- Complete container lifecycle
+- Multi-registry support
+- Docker Compose orchestration
+- Real-time stats & logs
+
+
+
+
+#### 📚 Ansible Automation
+```typescript
+// Execute playbooks with real-time feedback
+const execution = await ansibleService.runPlaybook({
+ playbookPath: 'deploy.yml',
+ inventory: 'production',
+ extraVars: { version: '2.0' }
+});
+```
+- Dynamic inventory management
+- Galaxy collections
+- Vault support
+- Execution history
+
+
+
+
+
+
+#### 🔐 SSH & Security
+```typescript
+// Secure remote connections
+const session = await sshService.connect({
+ host: 'server.example.com',
+ username: 'admin',
+ privateKey: await keyManager.get('prod-key')
+});
+```
+- SSH key management
+- SFTP file transfers
+- Tunneling support
+- Session pooling
+
+
+
+
+#### 📊 Monitoring & Analytics
+```typescript
+// Real-time metrics streaming
+deviceStats.on('metrics', (data) => {
+ // CPU, memory, disk, network stats
+ dashboard.update(data);
+});
+```
+- Real-time WebSocket updates
+- Custom metric collectors
+- Alert management
+- Historical data
+
+
+
+
+
+### 🛠️ Advanced Features
+
+
+🤖 Smart Automation
+
+- **Cron-based scheduling** for recurring tasks
+- **Event-driven triggers** for reactive automation
+- **Workflow orchestration** with dependencies
+- **Failure recovery** with retry strategies
+
+
+
+
+🔔 Intelligent Notifications
+
+- **Multi-channel delivery** (Email, Webhook, Slack)
+- **Smart grouping** to prevent spam
+- **Priority levels** for critical alerts
+- **Custom templates** for branded messages
+
+
+
+
+📈 Performance Optimizations
+
+- **Connection pooling** for database and SSH
+- **Request caching** with Redis support
+- **Lazy loading** for heavy operations
+- **Background job queues** for async tasks
+
+
+
+
+
+## 🏗️ Architecture
+
+### 📐 Clean Architecture in Action
+
+```mermaid
+graph TB
+ subgraph "External World"
+ Client[REST API Client]
+ WS[WebSocket Client]
+ DB[(MongoDB)]
+ Docker[Docker API]
+ SSH[SSH Servers]
+ end
+
+ subgraph "Presentation Layer"
+ Controllers[Controllers]
+ Gateways[WebSocket Gateways]
+ DTO[DTOs & Validation]
+ end
+
+ subgraph "Application Layer"
+ Services[Service Interfaces]
+ UseCases[Use Cases]
+ Events[Event Handlers]
+ end
+
+ subgraph "Domain Layer"
+ Entities[Business Entities]
+ DomainServices[Domain Services]
+ Repositories[Repository Interfaces]
+ end
+
+ subgraph "Infrastructure Layer"
+ RepoImpl[Repository Implementations]
+ Adapters[External Adapters]
+ Providers[Service Providers]
+ end
+
+ Client --> Controllers
+ WS --> Gateways
+ Controllers --> Services
+ Gateways --> Services
+ Services --> UseCases
+ UseCases --> DomainServices
+ DomainServices --> Repositories
+ Repositories --> RepoImpl
+ RepoImpl --> DB
+ Adapters --> Docker
+ Adapters --> SSH
+
+ style Domain fill:#ffd700
+ style Application fill:#87ceeb
+ style Infrastructure fill:#98fb98
+ style Presentation fill:#ffb6c1
+```
+
+### 📦 Module Structure
+
+
+See Complete Module Anatomy
+
+```
+📦 src/modules/example/
+├── 📁 domain/ # Business Logic Core
+│ ├── 📄 entities/ # Business entities
+│ ├── 📄 interfaces/ # Repository contracts
+│ ├── 📄 types/ # Domain types
+│ └── 📄 errors/ # Domain exceptions
+│
+├── 📁 application/ # Use Cases & Services
+│ ├── 📄 services/ # Service implementations
+│ ├── 📄 interfaces/ # Service contracts
+│ ├── 📄 use-cases/ # Business operations
+│ └── 📄 events/ # Domain events
+│
+├── 📁 infrastructure/ # External Integrations
+│ ├── 📄 repositories/ # Data persistence
+│ ├── 📄 schemas/ # Database models
+│ ├── 📄 adapters/ # External services
+│ └── 📄 mappers/ # Data transformers
+│
+├── 📁 presentation/ # API Layer
+│ ├── 📄 controllers/ # REST endpoints
+│ ├── 📄 gateways/ # WebSocket handlers
+│ ├── 📄 dto/ # Request/Response DTOs
+│ └── 📄 validators/ # Input validation
+│
+├── 📁 __tests__/ # Comprehensive Testing
+│ ├── 📄 unit/ # Unit tests
+│ ├── 📄 integration/ # Integration tests
+│ └── 📄 e2e/ # End-to-end tests
+│
+├── 📄 module.ts # Module definition
+└── 📄 index.ts # Public exports
+```
+
+
+
+### 🧩 Core Modules Deep Dive
+
+
+
+
+
+#### 🎭 Ansible Module
+```typescript
+@Module({
+ imports: [CommonModule],
+ providers: [
+ AnsibleService,
+ PlaybookExecutor,
+ InventoryManager,
+ GalaxyService
+ ],
+ exports: [AnsibleService]
+})
+```
+
+
+
+
+#### 🐳 Containers Module
+```typescript
+@Module({
+ imports: [CommonModule],
+ providers: [
+ ContainerService,
+ ImageService,
+ NetworkService,
+ VolumeService
+ ],
+ exports: [ContainerService]
+})
+```
+
+
+
+
+
+
+
+## 🧪 Testing
+
+### 🎯 Comprehensive Test Coverage
+
+```bash
+# 🧪 Run all tests
+npm test
+
+# 📊 Generate coverage report
+npm run test:cov
+
+# 🔍 Run specific test
+npm test -- devices.service.spec.ts
+
+# 👀 Watch mode for TDD
+npm run test:watch
+
+# 🚀 E2E tests
+npm run test:e2e
+```
+
+### 📈 Test Pyramid
+
+
+
+
+ Unit Tests
+ src/**/*.spec.ts
+
+ Fast, isolated, numerous
+
+
+ Integration Tests
+ __tests__/integration/
+
+ Module interactions
+
+
+ E2E Tests
+ __tests__/e2e/
+
+ Full API flows
+
+
+
+
+
+
+## 📚 API Documentation
+
+### 🌐 Interactive API Explorer
+
+
+
+
+
+Access comprehensive API documentation:
+
+- **Development**: http://localhost:3000/api/docs
+- **OpenAPI Spec**: http://localhost:3000/api/docs-json
+- **Postman Collection**: Available in `/docs`
+
+### 🔑 Authentication
+
+```bash
+# Get JWT token
+POST /api/auth/login
+{
+ "username": "admin",
+ "password": "secure-password"
+}
+
+# Use token in requests
+Authorization: Bearer
+```
+
+
+
+## 👨💻 Development
+
+### 🛠️ Essential Commands
+
+
+
+
+
+**Development**
+```bash
+npm run dev # Start dev server
+npm run build # Production build
+npm run start:prod # Production server
+```
+
+
+
+
+**Code Quality**
+```bash
+npm run lint # Check code style
+npm run lint:fix # Auto-fix issues
+npm run format # Format code
+```
+
+
+
+
+**Testing**
+```bash
+npm test # Run tests
+npm run test:cov # Coverage report
+npm run test:debug # Debug tests
+```
+
+
+
+
+
+### 📝 Code Guidelines
+
+
+Best Practices & Examples
+
+#### ✅ Do: Use Dependency Injection
+```typescript
+@Injectable()
+export class DeviceService {
+ constructor(
+ @Inject('IDeviceRepository')
+ private readonly deviceRepository: IDeviceRepository,
+ private readonly eventEmitter: EventEmitter2,
+ ) {}
+}
+```
+
+#### ✅ Do: Handle Errors Properly
+```typescript
+async createDevice(dto: CreateDeviceDto): Promise {
+ try {
+ return await this.deviceRepository.create(dto);
+ } catch (error) {
+ if (error.code === 11000) {
+ throw new ConflictException('Device already exists');
+ }
+ throw new InternalServerErrorException('Failed to create device');
+ }
+}
+```
+
+#### ❌ Don't: Mix Layers
+```typescript
+// Bad: Controller accessing repository directly
+@Controller('devices')
+export class DeviceController {
+ constructor(private readonly deviceRepo: DeviceRepository) {} // ❌
+}
+```
+
+
+
+
+
+## 🤝 Contributing
+
+We ❤️ contributions! See our [Contributing Guide](../CONTRIBUTING.md) for details.
+
+### 🌟 How to Contribute
+
+1. 🍴 **Fork** the repository
+2. 🌿 **Create** a feature branch (`git checkout -b feature/amazing-feature`)
+3. 💻 **Code** your feature with tests
+4. ✅ **Test** thoroughly (`npm test`)
+5. 📝 **Commit** with conventional commits (`git commit -m 'feat: add amazing feature'`)
+6. 🚀 **Push** to your fork (`git push origin feature/amazing-feature`)
+7. 🔄 **Open** a Pull Request
+
+### 🏆 Contributors
+
+
+
+
+
+
+
+## 📄 License
+
+This project is licensed under the **GNU Affero General Public License v3.0** - see the [LICENSE](../LICENSE) file for details.
+
+---
+
+
\ No newline at end of file
diff --git a/server/documentation/MONGODB_AUTH_FIX.md b/server/documentation/MONGODB_AUTH_FIX.md
new file mode 100644
index 000000000..887a23b1e
--- /dev/null
+++ b/server/documentation/MONGODB_AUTH_FIX.md
@@ -0,0 +1,116 @@
+# MongoDB Authentication Fix Documentation
+
+## Problem Summary
+Users reported authentication failures when using MongoDB with custom `authSource` settings. The issue occurred because:
+1. The MongooseModule configuration in `app.module.ts` was hardcoding `authSource: 'admin'`
+2. The pino-mongodb logger configuration was missing the `authSource` parameter entirely
+
+When users had MongoDB users created in custom databases (e.g., `mash-ssm`), the authentication would fail because the code was looking for users in the wrong database.
+
+## Root Cause
+MongoDB authentication works by looking for users in a specific database (the `authSource`). When not specified:
+- MongoDB defaults to using the connection database name as the authSource
+- Our code was overriding this with hardcoded `authSource: 'admin'`
+
+### User's Scenario
+```bash
+# User created in 'mash-ssm' database
+DB_AUTH_SOURCE=mash-ssm
+DB_USER=mash-ssm
+DB_NAME=mash-ssm
+
+# But code was looking in 'admin' database → Authentication failed
+```
+
+## Fix Applied
+
+### 1. Fixed MongooseModule Configuration
+**File**: `src/app.module.ts` (line 203)
+
+**Before**:
+```typescript
+...(db.user && db.password && { authSource: 'admin' }),
+```
+
+**After**:
+```typescript
+...(db.user && db.password && { authSource: db.authSource }),
+```
+
+### 2. Fixed Pino MongoDB Logger Configuration
+**File**: `src/app.module.ts` (line 79)
+
+**Before**:
+```typescript
+mongoOptions: {
+ auth: {
+ username: db.user,
+ password: db.password,
+ },
+ // authSource was missing!
+},
+```
+
+**After**:
+```typescript
+mongoOptions: {
+ auth: {
+ username: db.user,
+ password: db.password,
+ },
+ authSource: db.authSource,
+},
+```
+
+## Tests Created
+
+### 1. Unit Tests (`app-module-mongodb-config.spec.ts`)
+- Verifies configuration building logic
+- Tests edge cases (special characters, missing auth)
+- Regression test to prevent hardcoding
+
+### 2. Integration Tests (`mongodb-connection.spec.ts`)
+- Tests connection string building
+- Verifies both Mongoose and pino-mongodb patterns
+- Tests the exact user scenario
+
+### 3. Behavior Tests (`mongodb-auth-behavior.spec.ts`)
+- Tests with real MongoDB instances (mongodb-memory-server)
+- Demonstrates why hardcoding fails
+- Verifies the fix works with actual authentication
+
+## How MongoDB Authentication Works
+
+1. **Default Behavior**: When `authSource` is not specified in the connection string, MongoDB uses the database name as the authSource
+2. **Options Override**: When `authSource` is specified in connection options, it overrides any default
+3. **Our Issue**: We were hardcoding `authSource: 'admin'`, ignoring user configuration
+
+## Testing the Fix
+
+Run the tests:
+```bash
+npm run test -- src/__tests__/unit/app-module-mongodb-config.spec.ts
+npm run test -- src/__tests__/integration/infrastructure/mongodb-connection.spec.ts
+npm run test -- src/__tests__/integration/infrastructure/mongodb-auth-behavior.spec.ts
+```
+
+## Environment Configuration
+
+Users can now properly configure authentication:
+```env
+DB_HOST=mongodb-host
+DB_PORT=27017
+DB_NAME=mydb
+DB_USER=myuser
+DB_USER_PWD=mypassword
+DB_AUTH_SOURCE=myauthdb # This is now respected!
+```
+
+If `DB_AUTH_SOURCE` is not set, it defaults to `'admin'` (MongoDB standard).
+
+## Verification Steps
+
+1. Both MongoDB connections now use the configured `authSource`
+2. Pino logger can authenticate to MongoDB with custom auth databases
+3. All existing functionality remains intact
+4. Tests prevent regression to hardcoded values
\ No newline at end of file
diff --git a/server/package-lock.json b/server/package-lock.json
index 30cca1a94..7a5171952 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -38,6 +38,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"connect-mongo": "^5.1.0",
+ "consola": "^3.4.2",
"cookie-parser": "^1.4.7",
"debounce": "^2.2.0",
"dockerode": "^4.0.6",
@@ -77,6 +78,7 @@
"redis": "^5.6.0",
"reflect-metadata": "^0.2.2",
"rewire": "^7.0.0",
+ "rss-parser": "^3.13.0",
"rxjs": "^7.8.2",
"semver": "^7.7.1",
"shelljs": "^0.9.2",
@@ -9254,6 +9256,15 @@
"node": ">=10.13.0"
}
},
+ "node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -14780,6 +14791,16 @@
"node": ">= 18"
}
},
+ "node_modules/rss-parser": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
+ "integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^2.0.3",
+ "xml2js": "^0.5.0"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -14847,6 +14868,12 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+ "license": "ISC"
+ },
"node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@@ -17301,6 +17328,28 @@
}
}
},
+ "node_modules/xml2js": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
diff --git a/server/package.json b/server/package.json
index f3aca35bb..f5689d11c 100644
--- a/server/package.json
+++ b/server/package.json
@@ -50,6 +50,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"connect-mongo": "^5.1.0",
+ "consola": "^3.4.2",
"cookie-parser": "^1.4.7",
"debounce": "^2.2.0",
"dockerode": "^4.0.6",
@@ -89,6 +90,7 @@
"redis": "^5.6.0",
"reflect-metadata": "^0.2.2",
"rewire": "^7.0.0",
+ "rss-parser": "^3.13.0",
"rxjs": "^7.8.2",
"semver": "^7.7.1",
"shelljs": "^0.9.2",
diff --git a/server/src/__tests__/TEST_COVERAGE_SUMMARY.md b/server/src/__tests__/TEST_COVERAGE_SUMMARY.md
new file mode 100644
index 000000000..07540f941
--- /dev/null
+++ b/server/src/__tests__/TEST_COVERAGE_SUMMARY.md
@@ -0,0 +1,61 @@
+# MongoDB Authentication Test Coverage Summary
+
+## Complete Test Coverage: Both Authenticated and Unauthenticated Scenarios
+
+### 1. **Unauthenticated MongoDB Tests** ✅
+- ✅ Direct MongoDB connection without credentials
+- ✅ Mongoose connection without credentials
+- ✅ Pino-mongodb logger without authentication
+- ✅ App.module.ts pattern simulation without auth (no authSource added)
+
+### 2. **Authenticated MongoDB Tests** ✅
+- ✅ Direct MongoDB connection with credentials and authSource
+- ✅ Authentication failure with wrong authSource
+- ✅ Mongoose connection with correct authSource
+- ✅ Pino-mongodb logger with authentication and authSource
+- ✅ App.module.ts pattern simulation with auth (authSource properly added)
+
+### 3. **Edge Cases** ✅
+- ✅ Empty string credentials treated as no auth
+- ✅ Whitespace-only credentials treated as no auth
+- ✅ Special characters in credentials properly encoded
+
+## Test Files Created
+
+1. **`mongodb-auth-complete.spec.ts`** - Comprehensive test covering both scenarios
+2. **`mongodb-auth-behavior.spec.ts`** - Behavior tests showing why the fix was needed
+3. **`mongodb-connection.spec.ts`** - Connection string building tests
+4. **`app-module-mongodb-config.spec.ts`** - Unit tests for configuration logic
+
+## Key Test Patterns
+
+### Unauthenticated Pattern
+```typescript
+// When no credentials, no authSource is added
+const options = {
+ ...(db.user && db.password && { authSource: db.authSource })
+};
+// Result: options = {} (no authSource)
+```
+
+### Authenticated Pattern
+```typescript
+// When credentials exist, authSource is added
+const options = {
+ ...(db.user && db.password && { authSource: db.authSource })
+};
+// Result: options = { authSource: 'customdb' }
+```
+
+## Test Results
+- **22 tests** in mongodb-auth-complete.spec.ts alone
+- **46+ total tests** across all MongoDB auth test files
+- All tests passing ✅
+
+## What We're Testing
+
+1. **Configuration Logic**: The conditional spread operator correctly adds/omits authSource
+2. **Connection Behavior**: MongoDB connections work in both auth and no-auth scenarios
+3. **Logger Behavior**: Pino-mongodb works with and without authentication
+4. **Failure Scenarios**: Wrong authSource causes authentication failures as expected
+5. **Real App Patterns**: The exact patterns used in app.module.ts are tested
\ No newline at end of file
diff --git a/server/src/__tests__/examples/mongodb-memory-server-auth-example.ts b/server/src/__tests__/examples/mongodb-memory-server-auth-example.ts
new file mode 100644
index 000000000..854e04cc1
--- /dev/null
+++ b/server/src/__tests__/examples/mongodb-memory-server-auth-example.ts
@@ -0,0 +1,251 @@
+import { MongoMemoryServer, MongoMemoryReplSet } from 'mongodb-memory-server';
+import { MongoClient } from 'mongodb';
+
+// Example 1: Basic Authentication with MongoMemoryServer
+async function basicAuthExample() {
+ // Create server with auth enabled
+ const mongoServer = await MongoMemoryServer.create({
+ auth: {
+ enable: true,
+ customRootName: 'admin', // Root username
+ customRootPwd: 'adminpass', // Root password
+ },
+ });
+
+ // Connect as root to create users
+ const uri = mongoServer.getUri();
+ const adminClient = new MongoClient(uri, {
+ auth: {
+ username: 'admin',
+ password: 'adminpass',
+ },
+ authSource: 'admin',
+ });
+
+ await adminClient.connect();
+
+ // Create users in different databases
+ await adminClient.db('myapp').command({
+ createUser: 'appuser',
+ pwd: 'apppass',
+ roles: [
+ { role: 'readWrite', db: 'myapp' },
+ { role: 'readWrite', db: 'logs' },
+ ],
+ });
+
+ await adminClient.close();
+
+ // Now connect as the created user
+ const userClient = new MongoClient(
+ `mongodb://appuser:apppass@${uri.replace('mongodb://', '').split('/')[0]}/myapp?authSource=myapp`
+ );
+ await userClient.connect();
+
+ // Use the connection...
+ await userClient.close();
+ await mongoServer.stop();
+}
+
+// Example 2: Using Replica Set for More Complex Auth Scenarios
+async function replicaSetAuthExample() {
+ // Replica set supports more auth features
+ const mongoServer = await MongoMemoryReplSet.create({
+ replSet: {
+ count: 1, // Single node replica set
+ storageEngine: 'wiredTiger',
+ },
+ auth: {
+ enable: true,
+ customRootName: 'root',
+ customRootPwd: 'rootpass',
+ },
+ });
+
+ await mongoServer.waitUntilRunning();
+
+ const uri = mongoServer.getUri();
+
+ // Connect as root
+ const adminClient = new MongoClient(`${uri}admin?authSource=admin`, {
+ auth: { username: 'root', password: 'rootpass' },
+ });
+
+ await adminClient.connect();
+
+ // Create users in different databases
+ // User in 'admin' database
+ await adminClient.db('admin').command({
+ createUser: 'adminuser',
+ pwd: 'adminpass',
+ roles: [{ role: 'root', db: 'admin' }],
+ });
+
+ // User in custom database
+ await adminClient.db('customdb').command({
+ createUser: 'customuser',
+ pwd: 'custompass',
+ roles: [
+ { role: 'readWrite', db: 'customdb' },
+ { role: 'readWrite', db: 'anotherdb' },
+ ],
+ });
+
+ await adminClient.close();
+
+ // Test different auth scenarios
+ // This works - user exists in customdb
+ const customClient = new MongoClient(
+ `${uri}customdb?authSource=customdb`,
+ { auth: { username: 'customuser', password: 'custompass' } }
+ );
+ await customClient.connect();
+ await customClient.close();
+
+ // This fails - wrong authSource
+ try {
+ const wrongClient = new MongoClient(
+ `${uri}customdb?authSource=admin`,
+ { auth: { username: 'customuser', password: 'custompass' } }
+ );
+ await wrongClient.connect();
+ } catch (error) {
+ console.log('Expected error:', error.message); // Authentication failed
+ }
+
+ await mongoServer.stop();
+}
+
+// Example 3: Complete Test Setup with Both Auth and No-Auth
+async function completeTestSetup() {
+ // 1. Server WITHOUT authentication
+ const noAuthServer = await MongoMemoryServer.create({
+ instance: {
+ auth: false, // Explicitly disable auth
+ },
+ });
+
+ // 2. Server WITH authentication
+ const authServer = await MongoMemoryServer.create({
+ auth: {
+ enable: true,
+ customRootName: 'root',
+ customRootPwd: 'rootpass',
+ },
+ });
+
+ // Setup users on auth server
+ const authUri = authServer.getUri();
+ const setupClient = new MongoClient(authUri, {
+ auth: { username: 'root', password: 'rootpass' },
+ authSource: 'admin',
+ });
+
+ await setupClient.connect();
+
+ // Create user in specific database (simulating mash-ssm scenario)
+ await setupClient.db('mash-ssm').command({
+ createUser: 'mash-ssm',
+ pwd: 'password',
+ roles: [
+ { role: 'readWrite', db: 'mash-ssm' },
+ { role: 'readWrite', db: 'logs' },
+ ],
+ });
+
+ await setupClient.close();
+
+ // Test connections
+ // No-auth connection
+ const noAuthClient = new MongoClient(noAuthServer.getUri());
+ await noAuthClient.connect();
+ console.log('Connected without auth');
+ await noAuthClient.close();
+
+ // Auth connection with correct authSource
+ const authClient = new MongoClient(
+ `mongodb://mash-ssm:password@${authUri.replace('mongodb://', '').split('/')[0]}/mash-ssm?authSource=mash-ssm`
+ );
+ await authClient.connect();
+ console.log('Connected with auth');
+ await authClient.close();
+
+ // Cleanup
+ await noAuthServer.stop();
+ await authServer.stop();
+}
+
+// Example 4: Configuration Options
+const authOptions = {
+ // Basic auth
+ auth: {
+ enable: true,
+ customRootName: 'myroot', // Default: 'mongodb-memory-server-root'
+ customRootPwd: 'mypassword', // Default: 'rootuser'
+ },
+
+ // Instance options
+ instance: {
+ port: 27018, // Custom port
+ auth: true, // Alternative way to enable auth
+ },
+};
+
+// Example 5: Creating Users with Different Roles
+async function createUsersExample() {
+ const server = await MongoMemoryServer.create({
+ auth: { enable: true },
+ });
+
+ const adminClient = new MongoClient(server.getUri(), {
+ auth: {
+ username: 'mongodb-memory-server-root', // Default root user
+ password: 'rootuser', // Default root password
+ },
+ authSource: 'admin',
+ });
+
+ await adminClient.connect();
+
+ // Different user creation patterns
+
+ // 1. User with multiple database access
+ await adminClient.db('admin').command({
+ createUser: 'multiDbUser',
+ pwd: 'password',
+ roles: [
+ { role: 'readWrite', db: 'db1' },
+ { role: 'readWrite', db: 'db2' },
+ { role: 'read', db: 'db3' },
+ ],
+ });
+
+ // 2. User in specific database (auth source)
+ await adminClient.db('myAuthDb').command({
+ createUser: 'specificDbUser',
+ pwd: 'password',
+ roles: [
+ { role: 'dbOwner', db: 'myAuthDb' },
+ ],
+ });
+
+ // 3. Admin user
+ await adminClient.db('admin').command({
+ createUser: 'superadmin',
+ pwd: 'superpass',
+ roles: [
+ { role: 'root', db: 'admin' },
+ ],
+ });
+
+ await adminClient.close();
+ await server.stop();
+}
+
+// Export examples
+export {
+ basicAuthExample,
+ replicaSetAuthExample,
+ completeTestSetup,
+ createUsersExample,
+};
\ No newline at end of file
diff --git a/server/src/app.module.ts b/server/src/app.module.ts
index ddbf1d001..583fb0f9c 100644
--- a/server/src/app.module.ts
+++ b/server/src/app.module.ts
@@ -32,6 +32,7 @@ import { ShellModule } from './modules/shell/shell.module';
import { SmartFailureModule } from './modules/smart-failure/smart-failure.module';
import { UpdateModule } from './modules/update/update.module';
import { UsersModule } from './modules/users/users.module';
+import { DashboardModule } from './modules/dashboard/dashboard.module';
import { SshInfrastructureModule } from './infrastructure/ssh/ssh-infrastructure.module';
import { HealthModule } from './modules/health/health.module';
import { StatisticsModule } from './modules/statistics/statistics.module';
@@ -45,6 +46,7 @@ import { BootstrapModule } from './core/bootstrap/bootstrap.module';
import { DatabaseConnectionException } from './infrastructure/exceptions/app-exceptions';
import { SystemService } from './infrastructure/system/system.service';
import { McpModule } from './modules/mcp/mcp.module';
+import { RSSModule } from './core/rss/rss.module';
// Store the connection for legacy code to access
let sharedConnection: mongoose.Connection | null = null;
@@ -243,6 +245,7 @@ let connectionReady = false;
ContainerStacksModule,
ContainersModule,
DevicesModule,
+ DashboardModule,
UpdateModule,
DiagnosticModule,
ShellModule,
@@ -265,6 +268,7 @@ let connectionReady = false;
RemoteSystemInformationModule,
McpModule.registerAsync(),
BootstrapModule,
+ RSSModule,
],
providers: [SystemService],
})
diff --git a/server/src/core/rss/__tests__/application/services/rss.service.spec.ts b/server/src/core/rss/__tests__/application/services/rss.service.spec.ts
new file mode 100644
index 000000000..8dbf26209
--- /dev/null
+++ b/server/src/core/rss/__tests__/application/services/rss.service.spec.ts
@@ -0,0 +1,247 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { Test } from '@nestjs/testing';
+import { Logger } from '@nestjs/common';
+import { RSSService } from '../../../application/rss.service';
+import { RSS_REPOSITORY } from '../../../constants/injection-tokens';
+import { IRSSRepository } from '../../../domain/rss.repository.interface';
+import { RSSFeed, RSSFeedItem, FeedConfig } from '../../../domain/feed.entity';
+
+describe('RSSService', () => {
+ let service: RSSService;
+ let rssRepository: IRSSRepository;
+
+ // Mock data
+ const mockFeedConfig1: FeedConfig = {
+ id: 'feed-1',
+ name: 'Docker Blog',
+ url: 'https://www.docker.com/blog/feed/',
+ enabled: true,
+ };
+
+ const mockFeedConfig2: FeedConfig = {
+ id: 'feed-2',
+ name: 'Kubernetes Blog',
+ url: 'https://kubernetes.io/feed.xml',
+ enabled: true,
+ };
+
+ const mockFeedConfig3: FeedConfig = {
+ id: 'feed-3',
+ name: 'Disabled Feed',
+ url: 'https://disabled.com/feed/',
+ enabled: false,
+ };
+
+ const mockRSSFeed1: RSSFeed = {
+ title: 'Docker Blog',
+ link: 'https://www.docker.com/blog',
+ description: 'Docker Blog',
+ lastBuildDate: '2024-01-15T10:00:00Z',
+ items: [
+ {
+ title: 'Docker Desktop Update',
+ link: 'https://www.docker.com/blog/docker-desktop-update',
+ description: 'New features in Docker Desktop',
+ pubDate: '2024-01-15T10:00:00Z',
+ source: 'Docker Blog',
+ },
+ {
+ title: 'Container Security Best Practices',
+ link: 'https://www.docker.com/blog/security-best-practices',
+ description: 'Learn about container security',
+ pubDate: '2024-01-14T15:00:00Z',
+ source: 'Docker Blog',
+ },
+ ],
+ };
+
+ const mockRSSFeed2: RSSFeed = {
+ title: 'Kubernetes Blog',
+ link: 'https://kubernetes.io/blog',
+ description: 'Kubernetes Blog',
+ lastBuildDate: '2024-01-15T12:00:00Z',
+ items: [
+ {
+ title: 'Kubernetes 1.29 Released',
+ link: 'https://kubernetes.io/blog/k8s-1-29',
+ description: 'New features in Kubernetes 1.29',
+ pubDate: '2024-01-15T12:00:00Z',
+ source: 'Kubernetes Blog',
+ },
+ ],
+ };
+
+ beforeEach(async () => {
+ // Create mock repository
+ const mockRssRepository: Partial = {
+ fetchFeed: vi.fn(),
+ fetchMultipleFeeds: vi.fn(),
+ };
+
+ const module = await Test.createTestingModule({
+ providers: [
+ RSSService,
+ {
+ provide: RSS_REPOSITORY,
+ useValue: mockRssRepository,
+ },
+ ],
+ }).compile();
+
+ service = module.get(RSSService);
+ rssRepository = module.get(RSS_REPOSITORY);
+
+ // Spy on logger to suppress output during tests
+ vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+ vi.spyOn(Logger.prototype, 'log').mockImplementation(() => {});
+ });
+
+ describe('fetchFeeds', () => {
+ it('should fetch and combine feeds from enabled sources', async () => {
+ // Arrange
+ const feedConfigs = [mockFeedConfig1, mockFeedConfig2, mockFeedConfig3];
+ vi.mocked(rssRepository.fetchMultipleFeeds).mockResolvedValue([
+ mockRSSFeed1,
+ mockRSSFeed2,
+ ]);
+
+ // Act
+ const result = await service.fetchFeeds(feedConfigs);
+
+ // Assert
+ expect(rssRepository.fetchMultipleFeeds).toHaveBeenCalledWith([
+ mockFeedConfig1.url,
+ mockFeedConfig2.url,
+ ]);
+ expect(result).toHaveLength(3);
+ expect(result[0].source).toBe('Kubernetes Blog'); // Should be sorted by date (newest first)
+ expect(result[1].source).toBe('Docker Blog');
+ expect(result[2].source).toBe('Docker Blog');
+ });
+
+ it('should return empty array when no feeds are enabled', async () => {
+ // Arrange
+ const feedConfigs = [mockFeedConfig3]; // Only disabled feed
+
+ // Act
+ const result = await service.fetchFeeds(feedConfigs);
+
+ // Assert
+ expect(rssRepository.fetchMultipleFeeds).not.toHaveBeenCalled();
+ expect(result).toEqual([]);
+ });
+
+ it('should return empty array when feedConfigs is empty', async () => {
+ // Act
+ const result = await service.fetchFeeds([]);
+
+ // Assert
+ expect(rssRepository.fetchMultipleFeeds).not.toHaveBeenCalled();
+ expect(result).toEqual([]);
+ });
+
+ it('should limit the number of returned items to 50', async () => {
+ // Arrange
+ const mockFeedWithManyItems: RSSFeed = {
+ ...mockRSSFeed1,
+ items: Array(60).fill(null).map((_, index) => ({
+ title: `Item ${index}`,
+ link: `https://example.com/item-${index}`,
+ description: `Description ${index}`,
+ pubDate: new Date(Date.now() - index * 1000).toISOString(),
+ source: 'Test Feed',
+ })),
+ };
+
+ vi.mocked(rssRepository.fetchMultipleFeeds).mockResolvedValue([mockFeedWithManyItems]);
+
+ // Act
+ const result = await service.fetchFeeds([mockFeedConfig1]);
+
+ // Assert
+ expect(result).toHaveLength(50);
+ });
+
+ it('should handle repository errors', async () => {
+ // Arrange
+ const error = new Error('Network error');
+ vi.mocked(rssRepository.fetchMultipleFeeds).mockRejectedValue(error);
+
+ // Act & Assert
+ await expect(service.fetchFeeds([mockFeedConfig1])).rejects.toThrow('Network error');
+ expect(Logger.prototype.error).toHaveBeenCalledWith(
+ 'Failed to fetch RSS feeds',
+ error
+ );
+ });
+
+ it('should properly assign source names from feed configs', async () => {
+ // Arrange
+ const feedWithoutSource: RSSFeed = {
+ ...mockRSSFeed1,
+ items: [
+ {
+ title: 'Test Item',
+ link: 'https://example.com/test',
+ description: 'Test description',
+ pubDate: '2024-01-15T10:00:00Z',
+ source: '', // Empty source
+ },
+ ],
+ };
+
+ vi.mocked(rssRepository.fetchMultipleFeeds).mockResolvedValue([feedWithoutSource]);
+
+ // Act
+ const result = await service.fetchFeeds([mockFeedConfig1]);
+
+ // Assert
+ expect(result[0].source).toBe('Docker Blog'); // Should use config name
+ });
+ });
+
+ describe('fetchSingleFeed', () => {
+ it('should fetch items from a single feed URL', async () => {
+ // Arrange
+ const url = 'https://example.com/feed.xml';
+ vi.mocked(rssRepository.fetchFeed).mockResolvedValue(mockRSSFeed1);
+
+ // Act
+ const result = await service.fetchSingleFeed(url);
+
+ // Assert
+ expect(rssRepository.fetchFeed).toHaveBeenCalledWith(url);
+ expect(result).toEqual(mockRSSFeed1.items);
+ });
+
+ it('should handle repository errors for single feed', async () => {
+ // Arrange
+ const url = 'https://example.com/feed.xml';
+ const error = new Error('Invalid RSS format');
+ vi.mocked(rssRepository.fetchFeed).mockRejectedValue(error);
+
+ // Act & Assert
+ await expect(service.fetchSingleFeed(url)).rejects.toThrow('Invalid RSS format');
+ expect(Logger.prototype.error).toHaveBeenCalledWith(
+ `Failed to fetch RSS feed from ${url}`,
+ error
+ );
+ });
+
+ it('should return empty items array when feed has no items', async () => {
+ // Arrange
+ const url = 'https://example.com/feed.xml';
+ const emptyFeed: RSSFeed = {
+ ...mockRSSFeed1,
+ items: [],
+ };
+ vi.mocked(rssRepository.fetchFeed).mockResolvedValue(emptyFeed);
+
+ // Act
+ const result = await service.fetchSingleFeed(url);
+
+ // Assert
+ expect(result).toEqual([]);
+ });
+ });
+});
\ No newline at end of file
diff --git a/server/src/core/rss/__tests__/infrastructure/adapters/rss.repository.spec.ts b/server/src/core/rss/__tests__/infrastructure/adapters/rss.repository.spec.ts
new file mode 100644
index 000000000..674541067
--- /dev/null
+++ b/server/src/core/rss/__tests__/infrastructure/adapters/rss.repository.spec.ts
@@ -0,0 +1,309 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { HttpService } from '@nestjs/axios';
+import { Logger } from '@nestjs/common';
+import { of, throwError } from 'rxjs';
+import { AxiosResponse } from 'axios';
+import { RSSRepository } from '../../../infrastructure/adapters/rss.repository';
+import { RSSFeed, RSSFeedItem } from '../../../domain/feed.entity';
+import Parser from 'rss-parser';
+
+// Mock logger
+vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+vi.spyOn(Logger.prototype, 'log').mockImplementation(() => {});
+
+describe('RSSRepository', () => {
+ let repository: RSSRepository;
+ let httpService: HttpService;
+ let mockParser: any;
+
+ // Mock data
+ const mockRssXml = `
+
+
+ Test RSS Feed
+ https://example.com
+ Test Description
+ Mon, 15 Jan 2024 10:00:00 GMT
+ -
+
Test Article 1
+ https://example.com/article1
+ Article 1 description
+ Mon, 15 Jan 2024 10:00:00 GMT
+ https://example.com/article1
+ test@example.com
+ Technology
+
+
+ `;
+
+ const mockParsedFeed = {
+ title: 'Test RSS Feed',
+ link: 'https://example.com',
+ description: 'Test Description',
+ lastBuildDate: 'Mon, 15 Jan 2024 10:00:00 GMT',
+ items: [
+ {
+ title: 'Test Article 1',
+ link: 'https://example.com/article1',
+ contentSnippet: 'Article 1 description',
+ pubDate: 'Mon, 15 Jan 2024 10:00:00 GMT',
+ guid: 'https://example.com/article1',
+ author: 'test@example.com',
+ categories: ['Technology'],
+ },
+ {
+ title: 'Test Article 2',
+ link: 'https://example.com/article2',
+ content: 'Article 2 full content',
+ isoDate: '2024-01-14T15:00:00Z',
+ creator: 'author2',
+ },
+ ],
+ };
+
+ const mockAxiosResponse: AxiosResponse = {
+ data: mockRssXml,
+ status: 200,
+ statusText: 'OK',
+ headers: {},
+ config: {} as any,
+ };
+
+ beforeEach(async () => {
+ // Create mock HttpService
+ httpService = {
+ get: vi.fn(),
+ } as any;
+
+ // Create mock parser
+ mockParser = {
+ parseString: vi.fn(),
+ };
+
+ // Manually create the repository instance with mocked dependencies
+ repository = new RSSRepository(httpService);
+
+ // Replace the parser with our mock
+ (repository as any).parser = mockParser;
+ });
+
+ describe('fetchFeed', () => {
+ it('should successfully fetch and parse an RSS feed', async () => {
+ // Arrange
+ const url = 'https://example.com/feed.xml';
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(mockParsedFeed);
+
+ // Act
+ const result = await repository.fetchFeed(url);
+
+ // Assert
+ expect(httpService.get).toHaveBeenCalledWith(url, {
+ headers: {
+ 'User-Agent': 'SquirrelServersManager/1.0 RSS Reader',
+ 'Accept': 'application/rss+xml, application/xml, text/xml, */*',
+ },
+ timeout: 10000,
+ });
+ expect(mockParser.parseString).toHaveBeenCalledWith(mockRssXml);
+ expect(result).toEqual({
+ title: 'Test RSS Feed',
+ link: 'https://example.com',
+ description: 'Test Description',
+ lastBuildDate: 'Mon, 15 Jan 2024 10:00:00 GMT',
+ items: [
+ {
+ title: 'Test Article 1',
+ link: 'https://example.com/article1',
+ description: 'Article 1 description',
+ pubDate: 'Mon, 15 Jan 2024 10:00:00 GMT',
+ source: 'Test RSS Feed',
+ guid: 'https://example.com/article1',
+ author: 'test@example.com',
+ category: ['Technology'],
+ },
+ {
+ title: 'Test Article 2',
+ link: 'https://example.com/article2',
+ description: 'Article 2 full content',
+ pubDate: '2024-01-14T15:00:00Z',
+ source: 'Test RSS Feed',
+ guid: 'https://example.com/article2',
+ author: 'author2',
+ category: [],
+ },
+ ],
+ });
+ });
+
+ it('should handle feeds with missing optional fields', async () => {
+ // Arrange
+ const minimalParsedFeed = {
+ items: [
+ {
+ title: null,
+ link: null,
+ description: null,
+ },
+ ],
+ };
+
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(minimalParsedFeed);
+
+ // Act
+ const result = await repository.fetchFeed('https://example.com/feed.xml');
+
+ // Assert
+ expect(result.items[0]).toEqual({
+ title: '',
+ link: '',
+ description: '',
+ pubDate: expect.any(String), // Should use current date
+ source: 'Unknown',
+ guid: null,
+ author: undefined,
+ category: [],
+ });
+ });
+
+ it('should limit items to 20 per feed', async () => {
+ // Arrange
+ const feedWithManyItems = {
+ ...mockParsedFeed,
+ items: Array(30).fill(null).map((_, i) => ({
+ title: `Article ${i}`,
+ link: `https://example.com/article${i}`,
+ })),
+ };
+
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(feedWithManyItems);
+
+ // Act
+ const result = await repository.fetchFeed('https://example.com/feed.xml');
+
+ // Assert
+ expect(result.items).toHaveLength(20);
+ });
+
+ it('should handle HTTP errors', async () => {
+ // Arrange
+ const error = new Error('Network error');
+ vi.mocked(httpService.get).mockReturnValue(throwError(() => error));
+
+ // Act & Assert
+ await expect(repository.fetchFeed('https://example.com/feed.xml')).rejects.toThrow(
+ 'Failed to fetch RSS feed: Network error'
+ );
+ });
+
+ it('should handle parser errors', async () => {
+ // Arrange
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockRejectedValue(new Error('Invalid XML'));
+
+ // Act & Assert
+ await expect(repository.fetchFeed('https://example.com/feed.xml')).rejects.toThrow(
+ 'Failed to fetch RSS feed: Invalid XML'
+ );
+ });
+
+ it('should use content when contentSnippet is not available', async () => {
+ // Arrange
+ const feedWithContent = {
+ ...mockParsedFeed,
+ items: [
+ {
+ title: 'Test',
+ link: 'https://example.com/test',
+ content: 'Full content here',
+ contentSnippet: null,
+ },
+ ],
+ };
+
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(feedWithContent);
+
+ // Act
+ const result = await repository.fetchFeed('https://example.com/feed.xml');
+
+ // Assert
+ expect(result.items[0].description).toBe('Full content here');
+ });
+
+ it('should handle both author and creator fields', async () => {
+ // Arrange
+ const feedWithCreator = {
+ items: [
+ {
+ title: 'Test',
+ link: 'https://example.com/test',
+ creator: 'Creator Name',
+ },
+ ],
+ };
+
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(feedWithCreator);
+
+ // Act
+ const result = await repository.fetchFeed('https://example.com/feed.xml');
+
+ // Assert
+ expect(result.items[0].author).toBe('Creator Name');
+ });
+ });
+
+ describe('fetchMultipleFeeds', () => {
+ it('should fetch multiple feeds successfully', async () => {
+ // Arrange
+ const urls = ['https://feed1.com/rss', 'https://feed2.com/rss'];
+ vi.mocked(httpService.get).mockReturnValue(of(mockAxiosResponse));
+ mockParser.parseString.mockResolvedValue(mockParsedFeed);
+
+ // Act
+ const result = await repository.fetchMultipleFeeds(urls);
+
+ // Assert
+ expect(result).toHaveLength(2);
+ expect(httpService.get).toHaveBeenCalledTimes(2);
+ });
+
+ it('should continue fetching other feeds when one fails', async () => {
+ // Arrange
+ const urls = ['https://feed1.com/rss', 'https://feed2.com/rss', 'https://feed3.com/rss'];
+
+ // First call succeeds
+ vi.mocked(httpService.get)
+ .mockReturnValueOnce(of(mockAxiosResponse))
+ // Second call fails
+ .mockReturnValueOnce(throwError(() => new Error('Network error')))
+ // Third call succeeds
+ .mockReturnValueOnce(of(mockAxiosResponse));
+
+ mockParser.parseString.mockResolvedValue(mockParsedFeed);
+
+ // Act
+ const result = await repository.fetchMultipleFeeds(urls);
+
+ // Assert
+ expect(result).toHaveLength(3);
+ expect(result[0].title).toBe('Test RSS Feed');
+ expect(result[1].title).toBe('Error');
+ expect(result[1].description).toContain('Failed to fetch feed');
+ expect(result[1].items).toEqual([]);
+ expect(result[2].title).toBe('Test RSS Feed');
+ });
+
+ it('should handle empty URL array', async () => {
+ // Act
+ const result = await repository.fetchMultipleFeeds([]);
+
+ // Assert
+ expect(result).toEqual([]);
+ expect(httpService.get).not.toHaveBeenCalled();
+ });
+ });
+});
\ No newline at end of file
diff --git a/server/src/core/rss/__tests__/presentation/controllers/rss.controller.spec.ts b/server/src/core/rss/__tests__/presentation/controllers/rss.controller.spec.ts
new file mode 100644
index 000000000..18e0fa70d
--- /dev/null
+++ b/server/src/core/rss/__tests__/presentation/controllers/rss.controller.spec.ts
@@ -0,0 +1,193 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { Logger } from '@nestjs/common';
+import { RSSController } from '../../../presentation/rss.controller';
+import { RSSService } from '../../../application/rss.service';
+import { FetchFeedsDto, RSSFeedItemDto } from '../../../presentation/rss.dto';
+import { FeedConfig } from '../../../domain/feed.entity';
+
+// Mock logger
+vi.spyOn(Logger.prototype, 'log').mockImplementation(() => {});
+vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
+
+describe('RSSController', () => {
+ let controller: RSSController;
+ let rssService: RSSService;
+
+ // Mock data
+ const mockFeedConfig1: FeedConfig = {
+ id: 'feed-1',
+ name: 'Docker Blog',
+ url: 'https://www.docker.com/blog/feed/',
+ enabled: true,
+ };
+
+ const mockFeedConfig2: FeedConfig = {
+ id: 'feed-2',
+ name: 'Kubernetes Blog',
+ url: 'https://kubernetes.io/feed.xml',
+ enabled: false,
+ };
+
+ const mockRSSFeedItems: RSSFeedItemDto[] = [
+ {
+ title: 'Docker Desktop Update',
+ link: 'https://www.docker.com/blog/docker-desktop-update',
+ description: 'New features in Docker Desktop',
+ pubDate: '2024-01-15T10:00:00Z',
+ source: 'Docker Blog',
+ guid: 'docker-update-123',
+ author: 'Docker Team',
+ category: ['Docker', 'Updates'],
+ },
+ {
+ title: 'Kubernetes 1.29 Released',
+ link: 'https://kubernetes.io/blog/k8s-1-29',
+ description: 'New features in Kubernetes 1.29',
+ pubDate: '2024-01-15T12:00:00Z',
+ source: 'Kubernetes Blog',
+ },
+ ];
+
+ beforeEach(async () => {
+ // Create mock service
+ rssService = {
+ fetchFeeds: vi.fn(),
+ fetchSingleFeed: vi.fn(),
+ } as any;
+
+ // Manually create controller instance with mocked dependencies
+ controller = new RSSController(rssService);
+ });
+
+ describe('fetchFeeds', () => {
+ it('should fetch RSS feeds based on provided configurations', async () => {
+ // Arrange
+ const fetchFeedsDto: FetchFeedsDto = {
+ feeds: [mockFeedConfig1, mockFeedConfig2],
+ };
+ vi.mocked(rssService.fetchFeeds).mockResolvedValue(mockRSSFeedItems);
+
+ // Act
+ const result = await controller.fetchFeeds(fetchFeedsDto);
+
+ // Assert
+ expect(rssService.fetchFeeds).toHaveBeenCalledWith(fetchFeedsDto.feeds);
+ expect(result).toEqual(mockRSSFeedItems);
+ expect(Logger.prototype.log).toHaveBeenCalledWith('Fetching 2 RSS feeds');
+ });
+
+ it('should handle empty feed configurations', async () => {
+ // Arrange
+ const fetchFeedsDto: FetchFeedsDto = {
+ feeds: [],
+ };
+ vi.mocked(rssService.fetchFeeds).mockResolvedValue([]);
+
+ // Act
+ const result = await controller.fetchFeeds(fetchFeedsDto);
+
+ // Assert
+ expect(rssService.fetchFeeds).toHaveBeenCalledWith([]);
+ expect(result).toEqual([]);
+ expect(Logger.prototype.log).toHaveBeenCalledWith('Fetching 0 RSS feeds');
+ });
+
+ it('should pass through service errors', async () => {
+ // Arrange
+ const fetchFeedsDto: FetchFeedsDto = {
+ feeds: [mockFeedConfig1],
+ };
+ const error = new Error('Service error');
+ vi.mocked(rssService.fetchFeeds).mockRejectedValue(error);
+
+ // Act & Assert
+ await expect(controller.fetchFeeds(fetchFeedsDto)).rejects.toThrow('Service error');
+ });
+
+ it('should handle feeds with mixed enabled/disabled status', async () => {
+ // Arrange
+ const mixedFeeds: FeedConfig[] = [
+ { ...mockFeedConfig1, enabled: true },
+ { ...mockFeedConfig2, enabled: false },
+ { id: 'feed-3', name: 'Another Feed', url: 'https://example.com/feed', enabled: true },
+ ];
+ const fetchFeedsDto: FetchFeedsDto = { feeds: mixedFeeds };
+ vi.mocked(rssService.fetchFeeds).mockResolvedValue(mockRSSFeedItems);
+
+ // Act
+ const result = await controller.fetchFeeds(fetchFeedsDto);
+
+ // Assert
+ expect(rssService.fetchFeeds).toHaveBeenCalledWith(mixedFeeds);
+ expect(result).toEqual(mockRSSFeedItems);
+ expect(Logger.prototype.log).toHaveBeenCalledWith('Fetching 3 RSS feeds');
+ });
+ });
+
+ describe('fetchSingleFeed', () => {
+ it('should fetch a single RSS feed by URL', async () => {
+ // Arrange
+ const url = 'https://example.com/feed.xml';
+ vi.mocked(rssService.fetchSingleFeed).mockResolvedValue(mockRSSFeedItems);
+
+ // Act
+ const result = await controller.fetchSingleFeed(url);
+
+ // Assert
+ expect(rssService.fetchSingleFeed).toHaveBeenCalledWith(url);
+ expect(result).toEqual(mockRSSFeedItems);
+ expect(Logger.prototype.log).toHaveBeenCalledWith(
+ `Fetching single RSS feed from: ${url}`
+ );
+ });
+
+ it('should handle empty URL', async () => {
+ // Arrange
+ const url = '';
+ vi.mocked(rssService.fetchSingleFeed).mockResolvedValue([]);
+
+ // Act
+ const result = await controller.fetchSingleFeed(url);
+
+ // Assert
+ expect(rssService.fetchSingleFeed).toHaveBeenCalledWith('');
+ expect(result).toEqual([]);
+ });
+
+ it('should pass through service errors for single feed', async () => {
+ // Arrange
+ const url = 'https://invalid-url.com/feed';
+ const error = new Error('Invalid RSS format');
+ vi.mocked(rssService.fetchSingleFeed).mockRejectedValue(error);
+
+ // Act & Assert
+ await expect(controller.fetchSingleFeed(url)).rejects.toThrow('Invalid RSS format');
+ });
+
+ it('should handle URLs with special characters', async () => {
+ // Arrange
+ const url = 'https://example.com/feed?category=tech&format=rss';
+ vi.mocked(rssService.fetchSingleFeed).mockResolvedValue(mockRSSFeedItems);
+
+ // Act
+ const result = await controller.fetchSingleFeed(url);
+
+ // Assert
+ expect(rssService.fetchSingleFeed).toHaveBeenCalledWith(url);
+ expect(result).toEqual(mockRSSFeedItems);
+ expect(Logger.prototype.log).toHaveBeenCalledWith(
+ `Fetching single RSS feed from: ${url}`
+ );
+ });
+ });
+
+ describe('Guard and Decorator behavior', () => {
+ it('should have JwtAuthGuard applied to controller', () => {
+ // This test ensures the controller is properly decorated
+ // In a real scenario, we would test the guard behavior in integration tests
+ expect(controller).toBeDefined();
+ expect(controller.fetchFeeds).toBeDefined();
+ expect(controller.fetchSingleFeed).toBeDefined();
+ });
+ });
+});
\ No newline at end of file
diff --git a/server/src/core/rss/application/rss.service.ts b/server/src/core/rss/application/rss.service.ts
new file mode 100644
index 000000000..02dc90aee
--- /dev/null
+++ b/server/src/core/rss/application/rss.service.ts
@@ -0,0 +1,60 @@
+import { Injectable, Logger, Inject } from '@nestjs/common';
+import { IRSSRepository } from '../domain/rss.repository.interface';
+import { RSSFeedItem, FeedConfig } from '../domain/feed.entity';
+import { RSS_REPOSITORY } from '../constants/injection-tokens';
+
+@Injectable()
+export class RSSService {
+ private readonly logger = new Logger(RSSService.name);
+
+ constructor(@Inject(RSS_REPOSITORY) private readonly rssRepository: IRSSRepository) {}
+
+ async fetchFeeds(feedConfigs: FeedConfig[]): Promise {
+ try {
+ const enabledFeeds = feedConfigs.filter(feed => feed.enabled);
+ const feedUrls = enabledFeeds.map(feed => feed.url);
+
+ if (feedUrls.length === 0) {
+ return [];
+ }
+
+ const feeds = await this.rssRepository.fetchMultipleFeeds(feedUrls);
+
+ // Combine all items from all feeds
+ const allItems: RSSFeedItem[] = [];
+
+ feeds.forEach((feed, index) => {
+ const config = enabledFeeds[index];
+ feed.items.forEach(item => {
+ allItems.push({
+ ...item,
+ source: config.name
+ });
+ });
+ });
+
+ // Sort by publication date (newest first)
+ allItems.sort((a, b) => {
+ const dateA = new Date(a.pubDate).getTime();
+ const dateB = new Date(b.pubDate).getTime();
+ return dateB - dateA;
+ });
+
+ // Return limited number of items
+ return allItems.slice(0, 50);
+ } catch (error) {
+ this.logger.error('Failed to fetch RSS feeds', error);
+ throw error;
+ }
+ }
+
+ async fetchSingleFeed(url: string): Promise {
+ try {
+ const feed = await this.rssRepository.fetchFeed(url);
+ return feed.items;
+ } catch (error) {
+ this.logger.error(`Failed to fetch RSS feed from ${url}`, error);
+ throw error;
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/src/core/rss/constants/injection-tokens.ts b/server/src/core/rss/constants/injection-tokens.ts
new file mode 100644
index 000000000..0023fc6ca
--- /dev/null
+++ b/server/src/core/rss/constants/injection-tokens.ts
@@ -0,0 +1 @@
+export const RSS_REPOSITORY = Symbol('RSS_REPOSITORY');
\ No newline at end of file
diff --git a/server/src/core/rss/domain/feed.entity.ts b/server/src/core/rss/domain/feed.entity.ts
new file mode 100644
index 000000000..a391c3895
--- /dev/null
+++ b/server/src/core/rss/domain/feed.entity.ts
@@ -0,0 +1,25 @@
+export interface RSSFeedItem {
+ title: string;
+ link: string;
+ description: string;
+ pubDate: string;
+ source: string;
+ guid?: string;
+ author?: string;
+ category?: string[];
+}
+
+export interface RSSFeed {
+ title: string;
+ link: string;
+ description: string;
+ lastBuildDate?: string;
+ items: RSSFeedItem[];
+}
+
+export interface FeedConfig {
+ id: string;
+ name: string;
+ url: string;
+ enabled: boolean;
+}
\ No newline at end of file
diff --git a/server/src/core/rss/domain/rss.repository.interface.ts b/server/src/core/rss/domain/rss.repository.interface.ts
new file mode 100644
index 000000000..a5f1be07d
--- /dev/null
+++ b/server/src/core/rss/domain/rss.repository.interface.ts
@@ -0,0 +1,6 @@
+import { RSSFeed } from './feed.entity';
+
+export interface IRSSRepository {
+ fetchFeed(url: string): Promise;
+ fetchMultipleFeeds(urls: string[]): Promise;
+}
\ No newline at end of file
diff --git a/server/src/core/rss/infrastructure/adapters/rss.repository.ts b/server/src/core/rss/infrastructure/adapters/rss.repository.ts
new file mode 100644
index 000000000..159861b6a
--- /dev/null
+++ b/server/src/core/rss/infrastructure/adapters/rss.repository.ts
@@ -0,0 +1,80 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { HttpService } from '@nestjs/axios';
+import { firstValueFrom } from 'rxjs';
+import Parser from 'rss-parser';
+import { IRSSRepository } from '../../domain/rss.repository.interface';
+import { RSS_REPOSITORY } from '../../constants/injection-tokens';
+import { RSSFeed, RSSFeedItem } from '../../domain/feed.entity';
+
+@Injectable()
+export class RSSRepository implements IRSSRepository {
+ private readonly logger = new Logger(RSSRepository.name);
+ private readonly parser: Parser;
+
+ constructor(private readonly httpService: HttpService) {
+ this.parser = new Parser({
+ customFields: {
+ item: [
+ ['author', 'author'],
+ ['category', 'category', { keepArray: true }],
+ ]
+ }
+ });
+ }
+
+ async fetchFeed(url: string): Promise {
+ try {
+ // For some RSS feeds, we might need to fetch with specific headers
+ const response = await firstValueFrom(
+ this.httpService.get(url, {
+ headers: {
+ 'User-Agent': 'SquirrelServersManager/1.0 RSS Reader',
+ 'Accept': 'application/rss+xml, application/xml, text/xml, */*'
+ },
+ timeout: 10000, // 10 second timeout
+ })
+ );
+
+ const feed = await this.parser.parseString(response.data);
+
+ const items: RSSFeedItem[] = feed.items.map(item => ({
+ title: item.title || '',
+ link: item.link || '',
+ description: item.contentSnippet || item.content || '',
+ pubDate: item.pubDate || item.isoDate || new Date().toISOString(),
+ source: feed.title || 'Unknown',
+ guid: item.guid || item.link,
+ author: item.author || item.creator,
+ category: Array.isArray(item.categories) ? item.categories : []
+ }));
+
+ return {
+ title: feed.title || '',
+ link: feed.link || '',
+ description: feed.description || '',
+ lastBuildDate: feed.lastBuildDate,
+ items: items.slice(0, 20) // Limit items per feed
+ };
+ } catch (error) {
+ this.logger.error(`Failed to fetch RSS feed from ${url}`, error);
+ throw new Error(`Failed to fetch RSS feed: ${error instanceof Error ? error.message : String(error)}`);
+ }
+ }
+
+ async fetchMultipleFeeds(urls: string[]): Promise {
+ const feedPromises = urls.map(url =>
+ this.fetchFeed(url).catch(error => {
+ this.logger.error(`Failed to fetch feed from ${url}`, error);
+ // Return empty feed on error to continue with other feeds
+ return {
+ title: 'Error',
+ link: url,
+ description: `Failed to fetch feed: ${error instanceof Error ? error.message : String(error)}`,
+ items: []
+ };
+ })
+ );
+
+ return Promise.all(feedPromises);
+ }
+}
\ No newline at end of file
diff --git a/server/src/core/rss/presentation/rss.controller.ts b/server/src/core/rss/presentation/rss.controller.ts
new file mode 100644
index 000000000..51a68658d
--- /dev/null
+++ b/server/src/core/rss/presentation/rss.controller.ts
@@ -0,0 +1,40 @@
+import { Controller, Post, Body, UseGuards, Get, Query } from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
+import { JwtAuthGuard } from 'src/infrastructure/auth/strategies/jwt-auth.guard';
+import { RSSService } from '../application/rss.service';
+import { FetchFeedsDto, RSSFeedItemDto } from './rss.dto';
+import { Logger } from '@nestjs/common';
+
+@ApiTags('RSS Feeds')
+@Controller('rss')
+@UseGuards(JwtAuthGuard)
+@ApiBearerAuth()
+export class RSSController {
+ private readonly logger = new Logger(RSSController.name);
+
+ constructor(private readonly rssService: RSSService) {}
+
+ @Post('fetch')
+ @ApiOperation({ summary: 'Fetch RSS feeds based on provided configurations' })
+ @ApiResponse({
+ status: 200,
+ description: 'RSS feed items fetched successfully',
+ type: [RSSFeedItemDto]
+ })
+ async fetchFeeds(@Body() fetchFeedsDto: FetchFeedsDto): Promise {
+ this.logger.log(`Fetching ${fetchFeedsDto.feeds.length} RSS feeds`);
+ return this.rssService.fetchFeeds(fetchFeedsDto.feeds);
+ }
+
+ @Get('fetch-single')
+ @ApiOperation({ summary: 'Fetch a single RSS feed by URL' })
+ @ApiResponse({
+ status: 200,
+ description: 'RSS feed items fetched successfully',
+ type: [RSSFeedItemDto]
+ })
+ async fetchSingleFeed(@Query('url') url: string): Promise {
+ this.logger.log(`Fetching single RSS feed from: ${url}`);
+ return this.rssService.fetchSingleFeed(url);
+ }
+}
\ No newline at end of file
diff --git a/server/src/core/rss/presentation/rss.dto.ts b/server/src/core/rss/presentation/rss.dto.ts
new file mode 100644
index 000000000..ca52c6a25
--- /dev/null
+++ b/server/src/core/rss/presentation/rss.dto.ts
@@ -0,0 +1,61 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsString, IsBoolean, IsUrl, IsArray, ValidateNested, IsOptional } from 'class-validator';
+import { Type } from 'class-transformer';
+
+export class FeedConfigDto {
+ @ApiProperty({ description: 'Unique identifier for the feed' })
+ @IsString()
+ id!: string;
+
+ @ApiProperty({ description: 'Display name for the feed' })
+ @IsString()
+ name!: string;
+
+ @ApiProperty({ description: 'RSS feed URL' })
+ @IsUrl()
+ url!: string;
+
+ @ApiProperty({ description: 'Whether the feed is enabled' })
+ @IsBoolean()
+ enabled!: boolean;
+}
+
+export class FetchFeedsDto {
+ @ApiProperty({
+ description: 'Array of feed configurations',
+ type: [FeedConfigDto]
+ })
+ @IsArray()
+ @ValidateNested({ each: true })
+ @Type(() => FeedConfigDto)
+ feeds!: FeedConfigDto[];
+}
+
+export class RSSFeedItemDto {
+ @ApiProperty({ description: 'Item title' })
+ title!: string;
+
+ @ApiProperty({ description: 'Item link' })
+ link!: string;
+
+ @ApiProperty({ description: 'Item description' })
+ description!: string;
+
+ @ApiProperty({ description: 'Publication date' })
+ pubDate!: string;
+
+ @ApiProperty({ description: 'Source feed name' })
+ source!: string;
+
+ @ApiProperty({ description: 'Unique identifier', required: false })
+ @IsOptional()
+ guid?: string;
+
+ @ApiProperty({ description: 'Author name', required: false })
+ @IsOptional()
+ author?: string;
+
+ @ApiProperty({ description: 'Categories', required: false })
+ @IsOptional()
+ category?: string[];
+}
\ No newline at end of file
diff --git a/server/src/core/rss/rss.module.ts b/server/src/core/rss/rss.module.ts
new file mode 100644
index 000000000..57faaf525
--- /dev/null
+++ b/server/src/core/rss/rss.module.ts
@@ -0,0 +1,25 @@
+import { Module } from '@nestjs/common';
+import { HttpModule } from '@nestjs/axios';
+import { RSSController } from './presentation/rss.controller';
+import { RSSService } from './application/rss.service';
+import { RSSRepository } from './infrastructure/adapters/rss.repository';
+import { RSS_REPOSITORY } from './constants/injection-tokens';
+
+@Module({
+ imports: [
+ HttpModule.register({
+ timeout: 15000,
+ maxRedirects: 5,
+ }),
+ ],
+ controllers: [RSSController],
+ providers: [
+ RSSService,
+ {
+ provide: RSS_REPOSITORY,
+ useClass: RSSRepository,
+ },
+ ],
+ exports: [RSSService],
+})
+export class RSSModule {}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dashboard.controller.ts b/server/src/modules/dashboard/dashboard.controller.ts
new file mode 100644
index 000000000..8f0d97d39
--- /dev/null
+++ b/server/src/modules/dashboard/dashboard.controller.ts
@@ -0,0 +1,88 @@
+import {
+ Controller,
+ Get,
+ Post,
+ Body,
+ Patch,
+ Param,
+ Delete,
+ Request,
+ Logger,
+} from '@nestjs/common';
+import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
+import { DashboardService } from './dashboard.service';
+import { CreateDashboardDto, UpdateDashboardDto, DashboardWidgetDto } from './dto';
+
+@ApiTags('dashboards')
+@Controller('dashboards')
+@ApiBearerAuth()
+export class DashboardController {
+ private readonly logger = new Logger(DashboardController.name);
+
+ constructor(private readonly dashboardService: DashboardService) {}
+
+ @Post()
+ @ApiOperation({ summary: 'Create a new dashboard' })
+ async create(@Body() createDashboardDto: CreateDashboardDto) {
+ return this.dashboardService.create(createDashboardDto);
+ }
+
+ @Get()
+ @ApiOperation({ summary: 'Get all dashboards' })
+ async findAll() {
+ return this.dashboardService.findAll();
+ }
+
+ @Get('current')
+ @ApiOperation({ summary: 'Get current dashboard' })
+ async getCurrentDashboard() {
+ return this.dashboardService.getCurrentDashboard();
+ }
+
+ @Get(':id')
+ @ApiOperation({ summary: 'Get dashboard by ID' })
+ async findOne(@Param('id') id: string) {
+ return this.dashboardService.findOne(id);
+ }
+
+ @Patch(':id')
+ @ApiOperation({ summary: 'Update dashboard' })
+ async update(
+ @Param('id') id: string,
+ @Body() updateDashboardDto: UpdateDashboardDto,
+ @Request() req: any,
+ ) {
+ // Add who modified the dashboard
+ updateDashboardDto.lastModifiedBy = req.user.email;
+ return this.dashboardService.update(id, updateDashboardDto);
+ }
+
+ @Delete(':id')
+ @ApiOperation({ summary: 'Delete dashboard' })
+ async remove(@Param('id') id: string) {
+ await this.dashboardService.remove(id);
+ return { message: 'Dashboard deleted successfully' };
+ }
+
+ @Patch(':dashboardId/pages/:pageId/widgets')
+ @ApiOperation({ summary: 'Update widgets for a specific page' })
+ async updateWidgets(
+ @Param('dashboardId') dashboardId: string,
+ @Param('pageId') pageId: string,
+ @Body() widgets: DashboardWidgetDto[],
+ ) {
+ // Debug log the received widgets
+ this.logger.debug('=== Received widgets for update ===');
+ this.logger.debug(`Total widgets: ${widgets.length}`);
+ widgets.forEach((widget, index) => {
+ this.logger.debug(`Widget ${index} (${widget.id}):`, {
+ widgetType: widget.widgetType,
+ hasSettings: !!widget.settings,
+ settings: widget.settings ? JSON.stringify(widget.settings) : 'null'
+ });
+ });
+ this.logger.debug('=== End widgets debug ===');
+
+ return this.dashboardService.updateWidgets(dashboardId, pageId, widgets);
+ }
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dashboard.module.ts b/server/src/modules/dashboard/dashboard.module.ts
new file mode 100644
index 000000000..d0e0770e7
--- /dev/null
+++ b/server/src/modules/dashboard/dashboard.module.ts
@@ -0,0 +1,18 @@
+import { Module } from '@nestjs/common';
+import { MongooseModule } from '@nestjs/mongoose';
+import { DashboardService } from './dashboard.service';
+import { DashboardController } from './dashboard.controller';
+import { DashboardRepository } from './repository/dashboard.repository';
+import { Dashboard, DashboardSchema } from './entities/dashboard.entity';
+
+@Module({
+ imports: [
+ MongooseModule.forFeature([
+ { name: Dashboard.name, schema: DashboardSchema },
+ ]),
+ ],
+ controllers: [DashboardController],
+ providers: [DashboardService, DashboardRepository],
+ exports: [DashboardService],
+})
+export class DashboardModule {}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dashboard.service.ts b/server/src/modules/dashboard/dashboard.service.ts
new file mode 100644
index 000000000..7652e1eb8
--- /dev/null
+++ b/server/src/modules/dashboard/dashboard.service.ts
@@ -0,0 +1,95 @@
+import { Injectable, NotFoundException, ConflictException, Logger } from '@nestjs/common';
+import { DashboardRepository } from './repository/dashboard.repository';
+import { CreateDashboardDto, UpdateDashboardDto, DashboardWidgetDto } from './dto';
+import { Dashboard } from './entities/dashboard.entity';
+
+@Injectable()
+export class DashboardService {
+ private readonly logger = new Logger(DashboardService.name);
+
+ constructor(private readonly dashboardRepository: DashboardRepository) {}
+
+ async create(createDashboardDto: CreateDashboardDto): Promise {
+ // Check if dashboard with same name exists
+ const existing = await this.dashboardRepository.findByName(createDashboardDto.name);
+ if (existing) {
+ throw new ConflictException(`Dashboard with name "${createDashboardDto.name}" already exists`);
+ }
+
+ return this.dashboardRepository.create(createDashboardDto);
+ }
+
+ async findAll(): Promise {
+ return this.dashboardRepository.findAll();
+ }
+
+ async findOne(id: string): Promise {
+ const dashboard = await this.dashboardRepository.findOne(id);
+ if (!dashboard) {
+ throw new NotFoundException(`Dashboard with ID "${id}" not found`);
+ }
+ return dashboard;
+ }
+
+ async update(id: string, updateDashboardDto: UpdateDashboardDto): Promise {
+ const existing = await this.findOne(id);
+
+ // Check for name conflicts if updating name
+ if (updateDashboardDto.name && updateDashboardDto.name !== existing.name) {
+ const nameExists = await this.dashboardRepository.findByName(updateDashboardDto.name);
+ if (nameExists) {
+ throw new ConflictException(`Dashboard with name "${updateDashboardDto.name}" already exists`);
+ }
+ }
+
+ const updated = await this.dashboardRepository.update(id, updateDashboardDto);
+ if (!updated) {
+ throw new NotFoundException(`Dashboard with ID "${id}" not found`);
+ }
+
+ return updated;
+ }
+
+ async remove(id: string): Promise {
+ const dashboard = await this.findOne(id);
+
+ const result = await this.dashboardRepository.remove(id);
+ if (!result) {
+ throw new NotFoundException(`Dashboard with ID "${id}" not found`);
+ }
+ }
+
+ async getCurrentDashboard(): Promise {
+ // For now, return the default dashboard or create one if it doesn't exist
+ let dashboard = await this.dashboardRepository.findDefaultDashboard();
+
+ if (!dashboard) {
+ this.logger.log('Creating default dashboard');
+ dashboard = await this.dashboardRepository.createDefaultDashboard();
+ }
+
+ return dashboard;
+ }
+
+ async updateWidgets(
+ dashboardId: string,
+ pageId: string,
+ widgets: DashboardWidgetDto[]
+ ): Promise {
+ const dashboard = await this.findOne(dashboardId);
+
+ const pageIndex = dashboard.pages.findIndex(page => page.id === pageId);
+ if (pageIndex === -1) {
+ throw new NotFoundException(`Page with ID "${pageId}" not found in dashboard`);
+ }
+
+ // Update the widgets for the specific page
+ dashboard.pages[pageIndex].widgets = widgets;
+
+ const updated = await this.dashboardRepository.update(dashboardId, dashboard);
+ if (!updated) {
+ throw new NotFoundException(`Dashboard with ID "${dashboardId}" not found`);
+ }
+ return updated;
+ }
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/create-dashboard.dto.ts b/server/src/modules/dashboard/dto/create-dashboard.dto.ts
new file mode 100644
index 000000000..c0784a32d
--- /dev/null
+++ b/server/src/modules/dashboard/dto/create-dashboard.dto.ts
@@ -0,0 +1,31 @@
+import { IsString, IsArray, IsOptional, IsBoolean, ValidateNested } from 'class-validator';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { DashboardPageDto } from './dashboard-page.dto';
+
+export class CreateDashboardDto {
+ @ApiProperty()
+ @IsString()
+ name!: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ description?: string;
+
+ @ApiProperty({ type: [DashboardPageDto] })
+ @IsArray()
+ @ValidateNested({ each: true })
+ @Type(() => DashboardPageDto)
+ pages!: DashboardPageDto[];
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsBoolean()
+ isActive?: boolean;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsBoolean()
+ isSystem?: boolean;
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/dashboard-page.dto.ts b/server/src/modules/dashboard/dto/dashboard-page.dto.ts
new file mode 100644
index 000000000..d1b3ad80c
--- /dev/null
+++ b/server/src/modules/dashboard/dto/dashboard-page.dto.ts
@@ -0,0 +1,29 @@
+import { IsString, IsNumber, IsArray, IsOptional, IsBoolean, ValidateNested } from 'class-validator';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { DashboardWidgetDto } from './dashboard-widget.dto';
+
+export class DashboardPageDto {
+ @ApiProperty()
+ @IsString()
+ id!: string;
+
+ @ApiProperty()
+ @IsString()
+ name!: string;
+
+ @ApiProperty()
+ @IsNumber()
+ order!: number;
+
+ @ApiProperty({ type: [DashboardWidgetDto] })
+ @IsArray()
+ @ValidateNested({ each: true })
+ @Type(() => DashboardWidgetDto)
+ widgets!: DashboardWidgetDto[];
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsBoolean()
+ isDefault?: boolean;
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/dashboard-widget.dto.ts b/server/src/modules/dashboard/dto/dashboard-widget.dto.ts
new file mode 100644
index 000000000..7e175bc9c
--- /dev/null
+++ b/server/src/modules/dashboard/dto/dashboard-widget.dto.ts
@@ -0,0 +1,32 @@
+import { IsString, IsNumber, IsOptional, ValidateNested } from 'class-validator';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { WidgetSettingsDto } from './widget-settings.dto';
+
+export class DashboardWidgetDto {
+ @ApiProperty()
+ @IsString()
+ id!: string;
+
+ @ApiProperty()
+ @IsString()
+ widgetType!: string;
+
+ @ApiProperty()
+ @IsString()
+ title!: string;
+
+ @ApiProperty()
+ @IsString()
+ size!: string;
+
+ @ApiProperty()
+ @IsNumber()
+ position!: number;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @ValidateNested()
+ @Type(() => WidgetSettingsDto)
+ settings?: WidgetSettingsDto;
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/index.ts b/server/src/modules/dashboard/dto/index.ts
new file mode 100644
index 000000000..f4d101b73
--- /dev/null
+++ b/server/src/modules/dashboard/dto/index.ts
@@ -0,0 +1,5 @@
+export * from './create-dashboard.dto';
+export * from './update-dashboard.dto';
+export * from './dashboard-page.dto';
+export * from './dashboard-widget.dto';
+export * from './widget-settings.dto';
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/update-dashboard.dto.ts b/server/src/modules/dashboard/dto/update-dashboard.dto.ts
new file mode 100644
index 000000000..efe69e5ab
--- /dev/null
+++ b/server/src/modules/dashboard/dto/update-dashboard.dto.ts
@@ -0,0 +1,10 @@
+import { PartialType, ApiPropertyOptional } from '@nestjs/swagger';
+import { CreateDashboardDto } from './create-dashboard.dto';
+import { IsOptional, IsString } from 'class-validator';
+
+export class UpdateDashboardDto extends PartialType(CreateDashboardDto) {
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ lastModifiedBy?: string;
+}
\ No newline at end of file
diff --git a/server/src/modules/dashboard/dto/widget-settings.dto.ts b/server/src/modules/dashboard/dto/widget-settings.dto.ts
new file mode 100644
index 000000000..5e16b41f7
--- /dev/null
+++ b/server/src/modules/dashboard/dto/widget-settings.dto.ts
@@ -0,0 +1,66 @@
+import { IsString, IsArray, IsOptional, IsObject } from 'class-validator';
+import { ApiPropertyOptional } from '@nestjs/swagger';
+
+export class WidgetSettingsDto {
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ statistics_type?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsArray()
+ @IsString({ each: true })
+ statistics_source?: string[];
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ statistics_metric?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ icon?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ backgroundColor?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ title?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ customText?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ dateRangePreset?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsObject()
+ customDateRange?: any;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsString()
+ colorPalette?: string;
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsArray()
+ @IsString({ each: true })
+ customColors?: string[];
+
+ @ApiPropertyOptional()
+ @IsOptional()
+ @IsObject()
+ customSettings?: Record