From 46611a8172207a75ea8463631d4647c2e594fb58 Mon Sep 17 00:00:00 2001 From: KevIsDev Date: Tue, 1 Jul 2025 14:26:42 +0100 Subject: [PATCH 1/5] refactor: remove developer mode and related components add glowing effect component for tab tiles improve tab tile appearance with new glow effect add 'none' log level and simplify log level handling simplify tab configuration store by removing developer tabs remove useDebugStatus hook and related debug functionality remove system info endpoints no longer needed --- .../@settings/core/AvatarDropdown.tsx | 22 +- .../@settings/core/ControlPanel.tsx | 323 +-- app/components/@settings/core/constants.ts | 34 +- app/components/@settings/core/types.ts | 9 +- app/components/@settings/index.ts | 2 - .../shared/components/TabManagement.tsx | 380 --- .../@settings/shared/components/TabTile.tsx | 186 +- .../@settings/tabs/debug/DebugTab.tsx | 2110 ----------------- .../tabs/task-manager/TaskManagerTab.tsx | 1602 ------------- app/components/@settings/utils/animations.ts | 41 - app/components/@settings/utils/tab-helpers.ts | 39 +- app/components/ui/GlowingEffect.tsx | 192 ++ app/lib/hooks/index.ts | 1 - app/lib/hooks/useDebugStatus.ts | 89 - app/lib/hooks/useSettings.ts | 5 +- app/lib/stores/settings.ts | 58 +- app/lib/stores/tabConfigurationStore.ts | 7 +- app/routes/api.system.app-info.ts | 135 -- app/routes/api.system.memory-info.ts | 280 --- app/routes/api.system.process-info.ts | 424 ---- app/utils/cn.ts | 6 + app/utils/logger.ts | 11 +- vite.config.ts | 15 - 23 files changed, 369 insertions(+), 5602 deletions(-) delete mode 100644 app/components/@settings/shared/components/TabManagement.tsx delete mode 100644 app/components/@settings/tabs/debug/DebugTab.tsx delete mode 100644 app/components/@settings/tabs/task-manager/TaskManagerTab.tsx delete mode 100644 app/components/@settings/utils/animations.ts create mode 100644 app/components/ui/GlowingEffect.tsx delete mode 100644 app/lib/hooks/useDebugStatus.ts delete mode 100644 app/routes/api.system.app-info.ts delete mode 100644 app/routes/api.system.memory-info.ts delete mode 100644 app/routes/api.system.process-info.ts create mode 100644 app/utils/cn.ts diff --git a/app/components/@settings/core/AvatarDropdown.tsx b/app/components/@settings/core/AvatarDropdown.tsx index 6adfd31..c6c1845 100644 --- a/app/components/@settings/core/AvatarDropdown.tsx +++ b/app/components/@settings/core/AvatarDropdown.tsx @@ -36,7 +36,7 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => { /> ) : (
-
+
)} @@ -72,7 +72,7 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => { /> ) : (
- ? +
)}
@@ -117,24 +117,6 @@ export const AvatarDropdown = ({ onSelectTab }: AvatarDropdownProps) => {
- - onSelectTab('task-manager')} - > -
- Task Manager - - - void; } -interface TabWithDevType extends TabVisibilityConfig { - isExtraDevTab?: boolean; -} - -interface ExtendedTabConfig extends TabVisibilityConfig { - isExtraDevTab?: boolean; -} - -interface BaseTabConfig { - id: TabType; - visible: boolean; - window: 'user' | 'developer'; - order: number; -} - -interface AnimatedSwitchProps { - checked: boolean; - onCheckedChange: (checked: boolean) => void; - id: string; - label: string; -} - -const TAB_DESCRIPTIONS: Record = { - profile: 'Manage your profile and account settings', - settings: 'Configure application preferences', - notifications: 'View and manage your notifications', - features: 'Explore new and upcoming features', - data: 'Manage your data and storage', - 'cloud-providers': 'Configure cloud AI providers and models', - 'local-providers': 'Configure local AI providers and models', - 'service-status': 'Monitor cloud LLM service status', - connection: 'Check connection status and settings', - debug: 'Debug tools and system information', - 'event-logs': 'View system events and logs', - update: 'Check for updates and release notes', - 'task-manager': 'Monitor system resources and processes', - 'tab-management': 'Configure visible tabs and their order', -}; - // Beta status for experimental features -const BETA_TABS = new Set(['task-manager', 'service-status', 'update', 'local-providers']); +const BETA_TABS = new Set(['service-status', 'update', 'local-providers']); const BetaLabel = () => (
@@ -92,66 +42,6 @@ const BetaLabel = () => (
); -const AnimatedSwitch = ({ checked, onCheckedChange, id, label }: AnimatedSwitchProps) => { - return ( -
- - - - - Toggle {label} - -
- -
-
- ); -}; - export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { // State const [activeTab, setActiveTab] = useState(null); @@ -160,7 +50,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { // Store values const tabConfiguration = useStore(tabConfigurationStore); - const developerMode = useStore(developerModeStore); const profile = useStore(profileStore) as Profile; // Status hooks @@ -168,7 +57,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { const { hasNewFeatures, unviewedFeatures, acknowledgeAllFeatures } = useFeatures(); const { hasUnreadNotifications, unreadNotifications, markAllAsRead } = useNotifications(); const { hasConnectionIssues, currentIssue, acknowledgeIssue } = useConnectionStatus(); - const { hasActiveWarnings, activeIssues, acknowledgeAllIssues } = useDebugStatus(); // Memoize the base tab configurations to avoid recalculation const baseTabConfig = useMemo(() => { @@ -186,41 +74,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { const notificationsDisabled = profile?.preferences?.notifications === false; - // In developer mode, show ALL tabs without restrictions - if (developerMode) { - const seenTabs = new Set(); - const devTabs: ExtendedTabConfig[] = []; - - // Process tabs in order of priority: developer, user, default - const processTab = (tab: BaseTabConfig) => { - if (!seenTabs.has(tab.id)) { - seenTabs.add(tab.id); - devTabs.push({ - id: tab.id, - visible: true, - window: 'developer', - order: tab.order || devTabs.length, - }); - } - }; - - // Process tabs in priority order - tabConfiguration.developerTabs?.forEach((tab) => processTab(tab as BaseTabConfig)); - tabConfiguration.userTabs.forEach((tab) => processTab(tab as BaseTabConfig)); - DEFAULT_TAB_CONFIG.forEach((tab) => processTab(tab as BaseTabConfig)); - - // Add Tab Management tile - devTabs.push({ - id: 'tab-management' as TabType, - visible: true, - window: 'developer', - order: devTabs.length, - isExtraDevTab: true, - }); - - return devTabs.sort((a, b) => a.order - b.order); - } - // Optimize user mode tab filtering return tabConfiguration.userTabs .filter((tab) => { @@ -235,33 +88,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { return tab.visible && tab.window === 'user'; }) .sort((a, b) => a.order - b.order); - }, [tabConfiguration, developerMode, profile?.preferences?.notifications, baseTabConfig]); - - // Optimize animation performance with layout animations - const gridLayoutVariants = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { - staggerChildren: 0.05, - delayChildren: 0.1, - }, - }, - }; - - const itemVariants = { - hidden: { opacity: 0, scale: 0.8 }, - visible: { - opacity: 1, - scale: 1, - transition: { - type: 'spring', - stiffness: 200, - damping: 20, - mass: 0.6, - }, - }, - }; + }, [tabConfiguration, profile?.preferences?.notifications, baseTabConfig]); // Reset to default view when modal opens/closes useEffect(() => { @@ -293,21 +120,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { } }; - const handleDeveloperModeChange = (checked: boolean) => { - console.log('Developer mode changed:', checked); - setDeveloperMode(checked); - }; - - // Add effect to log developer mode changes - useEffect(() => { - console.log('Current developer mode:', developerMode); - }, [developerMode]); - - const getTabComponent = (tabId: TabType | 'tab-management') => { - if (tabId === 'tab-management') { - return ; - } - + const getTabComponent = (tabId: TabType) => { switch (tabId) { case 'profile': return ; @@ -325,14 +138,10 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { return ; case 'connection': return ; - case 'debug': - return ; case 'event-logs': return ; case 'update': return ; - case 'task-manager': - return ; case 'service-status': return ; default: @@ -350,8 +159,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { return hasUnreadNotifications; case 'connection': return hasConnectionIssues; - case 'debug': - return hasActiveWarnings; default: return false; } @@ -371,12 +178,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { : currentIssue === 'high-latency' ? 'High latency detected' : 'Connection issues detected'; - case 'debug': { - const warnings = activeIssues.filter((i) => i.type === 'warning').length; - const errors = activeIssues.filter((i) => i.type === 'error').length; - - return `${warnings} warning${warnings === 1 ? '' : 's'}, ${errors} error${errors === 1 ? '' : 's'}`; - } default: return ''; } @@ -401,9 +202,6 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { case 'connection': acknowledgeIssue(); break; - case 'debug': - acknowledgeAllIssues(); - break; } // Clear loading state after a delay @@ -414,15 +212,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
- - - + { onPointerDownOutside={handleClose} className="relative z-[101]" > -
@@ -454,7 +242,7 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { {(activeTab || showTabManagement) && ( @@ -465,18 +253,8 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => {
- {/* Mode Toggle */} -
- -
- {/* Avatar and Dropdown */} -
+
@@ -504,49 +282,48 @@ export const ControlPanel = ({ open, onClose }: ControlPanelProps) => { 'touch-auto', )} > - - {showTabManagement ? ( - - ) : activeTab ? ( + {activeTab ? ( getTabComponent(activeTab) ) : ( - - - {(visibleTabs as TabWithDevType[]).map((tab: TabWithDevType) => ( - - handleTabClick(tab.id as TabType)} - isActive={activeTab === tab.id} - hasUpdate={getTabUpdateStatus(tab.id)} - statusMessage={getStatusMessage(tab.id)} - description={TAB_DESCRIPTIONS[tab.id]} - isLoading={loadingTab === tab.id} - className="h-full relative" - > - {BETA_TABS.has(tab.id) && } - - - ))} - - +
+ {visibleTabs.map((tab, index) => ( +
+ handleTabClick(tab.id as TabType)} + isActive={activeTab === tab.id} + hasUpdate={getTabUpdateStatus(tab.id)} + statusMessage={getStatusMessage(tab.id)} + description={TAB_DESCRIPTIONS[tab.id]} + isLoading={loadingTab === tab.id} + className="h-full relative" + > + {BETA_TABS.has(tab.id) && } + +
+ ))} +
)} -
+
- +
diff --git a/app/components/@settings/core/constants.ts b/app/components/@settings/core/constants.ts index ff72a27..f4d1cfa 100644 --- a/app/components/@settings/core/constants.ts +++ b/app/components/@settings/core/constants.ts @@ -10,11 +10,8 @@ export const TAB_ICONS: Record = { 'local-providers': 'i-ph:desktop-fill', 'service-status': 'i-ph:activity-bold', connection: 'i-ph:wifi-high-fill', - debug: 'i-ph:bug-fill', 'event-logs': 'i-ph:list-bullets-fill', update: 'i-ph:arrow-clockwise-fill', - 'task-manager': 'i-ph:chart-line-fill', - 'tab-management': 'i-ph:squares-four-fill', }; export const TAB_LABELS: Record = { @@ -27,11 +24,8 @@ export const TAB_LABELS: Record = { 'local-providers': 'Local Providers', 'service-status': 'Service Status', connection: 'Connection', - debug: 'Debug', 'event-logs': 'Event Logs', update: 'Updates', - 'task-manager': 'Task Manager', - 'tab-management': 'Tab Management', }; export const TAB_DESCRIPTIONS: Record = { @@ -44,11 +38,8 @@ export const TAB_DESCRIPTIONS: Record = { 'local-providers': 'Configure local AI providers and models', 'service-status': 'Monitor cloud LLM service status', connection: 'Check connection status and settings', - debug: 'Debug tools and system information', 'event-logs': 'View system events and logs', update: 'Check for updates and release notes', - 'task-manager': 'Monitor system resources and processes', - 'tab-management': 'Configure visible tabs and their order', }; export const DEFAULT_TAB_CONFIG = [ @@ -62,27 +53,10 @@ export const DEFAULT_TAB_CONFIG = [ { id: 'event-logs', visible: true, window: 'user' as const, order: 6 }, // User Window Tabs (In dropdown, initially hidden) - { id: 'profile', visible: false, window: 'user' as const, order: 7 }, - { id: 'settings', visible: false, window: 'user' as const, order: 8 }, - { id: 'task-manager', visible: false, window: 'user' as const, order: 9 }, - { id: 'service-status', visible: false, window: 'user' as const, order: 10 }, + { id: 'profile', visible: true, window: 'user' as const, order: 7 }, + { id: 'settings', visible: true, window: 'user' as const, order: 8 }, + { id: 'service-status', visible: true, window: 'user' as const, order: 9 }, // User Window Tabs (Hidden, controlled by TaskManagerTab) - { id: 'debug', visible: false, window: 'user' as const, order: 11 }, - { id: 'update', visible: false, window: 'user' as const, order: 12 }, - - // Developer Window Tabs (All visible by default) - { id: 'features', visible: true, window: 'developer' as const, order: 0 }, - { id: 'data', visible: true, window: 'developer' as const, order: 1 }, - { id: 'cloud-providers', visible: true, window: 'developer' as const, order: 2 }, - { id: 'local-providers', visible: true, window: 'developer' as const, order: 3 }, - { id: 'connection', visible: true, window: 'developer' as const, order: 4 }, - { id: 'notifications', visible: true, window: 'developer' as const, order: 5 }, - { id: 'event-logs', visible: true, window: 'developer' as const, order: 6 }, - { id: 'profile', visible: true, window: 'developer' as const, order: 7 }, - { id: 'settings', visible: true, window: 'developer' as const, order: 8 }, - { id: 'task-manager', visible: true, window: 'developer' as const, order: 9 }, - { id: 'service-status', visible: true, window: 'developer' as const, order: 10 }, - { id: 'debug', visible: true, window: 'developer' as const, order: 11 }, - { id: 'update', visible: true, window: 'developer' as const, order: 12 }, + { id: 'update', visible: true, window: 'user' as const, order: 10 }, ]; diff --git a/app/components/@settings/core/types.ts b/app/components/@settings/core/types.ts index 97d4d36..3d65dae 100644 --- a/app/components/@settings/core/types.ts +++ b/app/components/@settings/core/types.ts @@ -12,11 +12,8 @@ export type TabType = | 'local-providers' | 'service-status' | 'connection' - | 'debug' | 'event-logs' - | 'update' - | 'task-manager' - | 'tab-management'; + | 'update'; export type WindowType = 'user' | 'developer'; @@ -63,7 +60,6 @@ export interface UserTabConfig extends TabVisibilityConfig { export interface TabWindowConfig { userTabs: UserTabConfig[]; - developerTabs: DevTabConfig[]; } export const TAB_LABELS: Record = { @@ -76,11 +72,8 @@ export const TAB_LABELS: Record = { 'local-providers': 'Local Providers', 'service-status': 'Service Status', connection: 'Connections', - debug: 'Debug', 'event-logs': 'Event Logs', update: 'Updates', - 'task-manager': 'Task Manager', - 'tab-management': 'Tab Management', }; export const categoryLabels: Record = { diff --git a/app/components/@settings/index.ts b/app/components/@settings/index.ts index 862c33e..94b3de9 100644 --- a/app/components/@settings/index.ts +++ b/app/components/@settings/index.ts @@ -7,8 +7,6 @@ export { TAB_LABELS, TAB_DESCRIPTIONS, DEFAULT_TAB_CONFIG } from './core/constan // Shared components export { TabTile } from './shared/components/TabTile'; -export { TabManagement } from './shared/components/TabManagement'; // Utils export { getVisibleTabs, reorderTabs, resetToDefaultConfig } from './utils/tab-helpers'; -export * from './utils/animations'; diff --git a/app/components/@settings/shared/components/TabManagement.tsx b/app/components/@settings/shared/components/TabManagement.tsx deleted file mode 100644 index 9ae1601..0000000 --- a/app/components/@settings/shared/components/TabManagement.tsx +++ /dev/null @@ -1,380 +0,0 @@ -import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import { useStore } from '@nanostores/react'; -import { Switch } from '~/components/ui/Switch'; -import { classNames } from '~/utils/classNames'; -import { tabConfigurationStore } from '~/lib/stores/settings'; -import { TAB_LABELS } from '~/components/@settings/core/constants'; -import type { TabType } from '~/components/@settings/core/types'; -import { toast } from 'react-toastify'; -import { TbLayoutGrid } from 'react-icons/tb'; -import { useSettingsStore } from '~/lib/stores/settings'; - -// Define tab icons mapping -const TAB_ICONS: Record = { - profile: 'i-ph:user-circle-fill', - settings: 'i-ph:gear-six-fill', - notifications: 'i-ph:bell-fill', - features: 'i-ph:star-fill', - data: 'i-ph:database-fill', - 'cloud-providers': 'i-ph:cloud-fill', - 'local-providers': 'i-ph:desktop-fill', - 'service-status': 'i-ph:activity-fill', - connection: 'i-ph:wifi-high-fill', - debug: 'i-ph:bug-fill', - 'event-logs': 'i-ph:list-bullets-fill', - update: 'i-ph:arrow-clockwise-fill', - 'task-manager': 'i-ph:chart-line-fill', - 'tab-management': 'i-ph:squares-four-fill', -}; - -// Define which tabs are default in user mode -const DEFAULT_USER_TABS: TabType[] = [ - 'features', - 'data', - 'cloud-providers', - 'local-providers', - 'connection', - 'notifications', - 'event-logs', -]; - -// Define which tabs can be added to user mode -const OPTIONAL_USER_TABS: TabType[] = ['profile', 'settings', 'task-manager', 'service-status', 'debug', 'update']; - -// All available tabs for user mode -const ALL_USER_TABS = [...DEFAULT_USER_TABS, ...OPTIONAL_USER_TABS]; - -// Define which tabs are beta -const BETA_TABS = new Set(['task-manager', 'service-status', 'update', 'local-providers']); - -// Beta label component -const BetaLabel = () => ( - BETA -); - -export const TabManagement = () => { - const [searchQuery, setSearchQuery] = useState(''); - const tabConfiguration = useStore(tabConfigurationStore); - const { setSelectedTab } = useSettingsStore(); - - const handleTabVisibilityChange = (tabId: TabType, checked: boolean) => { - // Get current tab configuration - const currentTab = tabConfiguration.userTabs.find((tab) => tab.id === tabId); - - // If tab doesn't exist in configuration, create it - if (!currentTab) { - const newTab = { - id: tabId, - visible: checked, - window: 'user' as const, - order: tabConfiguration.userTabs.length, - }; - - const updatedTabs = [...tabConfiguration.userTabs, newTab]; - - tabConfigurationStore.set({ - ...tabConfiguration, - userTabs: updatedTabs, - }); - - toast.success(`Tab ${checked ? 'enabled' : 'disabled'} successfully`); - - return; - } - - // Check if tab can be enabled in user mode - const canBeEnabled = DEFAULT_USER_TABS.includes(tabId) || OPTIONAL_USER_TABS.includes(tabId); - - if (!canBeEnabled && checked) { - toast.error('This tab cannot be enabled in user mode'); - return; - } - - // Update tab visibility - const updatedTabs = tabConfiguration.userTabs.map((tab) => { - if (tab.id === tabId) { - return { ...tab, visible: checked }; - } - - return tab; - }); - - // Update store - tabConfigurationStore.set({ - ...tabConfiguration, - userTabs: updatedTabs, - }); - - // Show success message - toast.success(`Tab ${checked ? 'enabled' : 'disabled'} successfully`); - }; - - // Create a map of existing tab configurations - const tabConfigMap = new Map(tabConfiguration.userTabs.map((tab) => [tab.id, tab])); - - // Generate the complete list of tabs, including those not in the configuration - const allTabs = ALL_USER_TABS.map((tabId) => { - return ( - tabConfigMap.get(tabId) || { - id: tabId, - visible: false, - window: 'user' as const, - order: -1, - } - ); - }); - - // Filter tabs based on search query - const filteredTabs = allTabs.filter((tab) => TAB_LABELS[tab.id].toLowerCase().includes(searchQuery.toLowerCase())); - - useEffect(() => { - // Reset to first tab when component unmounts - return () => { - setSelectedTab('user'); // Reset to user tab when unmounting - }; - }, [setSelectedTab]); - - return ( -
- - {/* Header */} -
-
-
- -
-
-

Tab Management

-

Configure visible tabs and their order

-
-
- - {/* Search */} -
-
-
-
- setSearchQuery(e.target.value)} - placeholder="Search tabs..." - className={classNames( - 'w-full pl-10 pr-4 py-2 rounded-lg', - 'bg-bolt-elements-background-depth-2', - 'border border-bolt-elements-borderColor', - 'text-bolt-elements-textPrimary', - 'placeholder-bolt-elements-textTertiary', - 'focus:outline-none focus:ring-2 focus:ring-purple-500/30', - 'transition-all duration-200', - )} - /> -
-
- - {/* Tab Grid */} -
- {/* Default Section Header */} - {filteredTabs.some((tab) => DEFAULT_USER_TABS.includes(tab.id)) && ( -
-
- Default Tabs -
- )} - - {/* Default Tabs */} - {filteredTabs - .filter((tab) => DEFAULT_USER_TABS.includes(tab.id)) - .map((tab, index) => ( - - {/* Status Badges */} -
- - Default - -
- -
- -
-
-
- - -
-
-
-
-

- {TAB_LABELS[tab.id]} -

- {BETA_TABS.has(tab.id) && } -
-

- {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} -

-
- { - const isDisabled = - !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); - - if (!isDisabled) { - handleTabVisibilityChange(tab.id, checked); - } - }} - className={classNames('data-[state=checked]:bg-purple-500 ml-4', { - 'opacity-50 pointer-events-none': - !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), - })} - /> -
-
-
- - - - ))} - - {/* Optional Section Header */} - {filteredTabs.some((tab) => OPTIONAL_USER_TABS.includes(tab.id)) && ( -
-
- Optional Tabs -
- )} - - {/* Optional Tabs */} - {filteredTabs - .filter((tab) => OPTIONAL_USER_TABS.includes(tab.id)) - .map((tab, index) => ( - - {/* Status Badges */} -
- - Optional - -
- -
- -
-
-
- - -
-
-
-
-

- {TAB_LABELS[tab.id]} -

- {BETA_TABS.has(tab.id) && } -
-

- {tab.visible ? 'Visible in user mode' : 'Hidden in user mode'} -

-
- { - const isDisabled = - !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id); - - if (!isDisabled) { - handleTabVisibilityChange(tab.id, checked); - } - }} - className={classNames('data-[state=checked]:bg-purple-500 ml-4', { - 'opacity-50 pointer-events-none': - !DEFAULT_USER_TABS.includes(tab.id) && !OPTIONAL_USER_TABS.includes(tab.id), - })} - /> -
-
-
- - - - ))} -
-
-
- ); -}; diff --git a/app/components/@settings/shared/components/TabTile.tsx b/app/components/@settings/shared/components/TabTile.tsx index ea409d6..ebc0b19 100644 --- a/app/components/@settings/shared/components/TabTile.tsx +++ b/app/components/@settings/shared/components/TabTile.tsx @@ -1,8 +1,8 @@ -import { motion } from 'framer-motion'; import * as Tooltip from '@radix-ui/react-tooltip'; import { classNames } from '~/utils/classNames'; import type { TabVisibilityConfig } from '~/components/@settings/core/types'; import { TAB_LABELS, TAB_ICONS } from '~/components/@settings/core/constants'; +import { GlowingEffect } from '~/components/ui/GlowingEffect'; interface TabTileProps { tab: TabVisibilityConfig; @@ -28,106 +28,118 @@ export const TabTile: React.FC = ({ children, }: TabTileProps) => { return ( - + - - {/* Main Content */} -
- {/* Icon */} - +
+ +
- - - - {/* Label and Description */} -
-

- {TAB_LABELS[tab.id]} -

- {description && ( -

+

+ + {/* Label and Description */} +
+

- {description} -

+ {TAB_LABELS[tab.id]} +

+ {description && ( +

+ {description} +

+ )} +
+ + {/* Update Indicator with Tooltip */} + {hasUpdate && ( + <> +
+ + + {statusMessage} + + + + )} + + {/* Children (e.g. Beta Label) */} + {children}
- - {/* Update Indicator with Tooltip */} - {hasUpdate && ( - <> -
- - - {statusMessage} - - - - - )} - - {/* Children (e.g. Beta Label) */} - {children} - +
diff --git a/app/components/@settings/tabs/debug/DebugTab.tsx b/app/components/@settings/tabs/debug/DebugTab.tsx deleted file mode 100644 index 24931aa..0000000 --- a/app/components/@settings/tabs/debug/DebugTab.tsx +++ /dev/null @@ -1,2110 +0,0 @@ -import React, { useEffect, useState, useMemo, useCallback } from 'react'; -import { toast } from 'react-toastify'; -import { classNames } from '~/utils/classNames'; -import { logStore, type LogEntry } from '~/lib/stores/logs'; -import { useStore } from '@nanostores/react'; -import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/Collapsible'; -import { Progress } from '~/components/ui/Progress'; -import { ScrollArea } from '~/components/ui/ScrollArea'; -import { Badge } from '~/components/ui/Badge'; -import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; -import { jsPDF } from 'jspdf'; -import { useSettings } from '~/lib/hooks/useSettings'; - -interface SystemInfo { - os: string; - arch: string; - platform: string; - cpus: string; - memory: { - total: string; - free: string; - used: string; - percentage: number; - }; - node: string; - browser: { - name: string; - version: string; - language: string; - userAgent: string; - cookiesEnabled: boolean; - online: boolean; - platform: string; - cores: number; - }; - screen: { - width: number; - height: number; - colorDepth: number; - pixelRatio: number; - }; - time: { - timezone: string; - offset: number; - locale: string; - }; - performance: { - memory: { - jsHeapSizeLimit: number; - totalJSHeapSize: number; - usedJSHeapSize: number; - usagePercentage: number; - }; - timing: { - loadTime: number; - domReadyTime: number; - readyStart: number; - redirectTime: number; - appcacheTime: number; - unloadEventTime: number; - lookupDomainTime: number; - connectTime: number; - requestTime: number; - initDomTreeTime: number; - loadEventTime: number; - }; - navigation: { - type: number; - redirectCount: number; - }; - }; - network: { - downlink: number; - effectiveType: string; - rtt: number; - saveData: boolean; - type: string; - }; - battery?: { - charging: boolean; - chargingTime: number; - dischargingTime: number; - level: number; - }; - storage: { - quota: number; - usage: number; - persistent: boolean; - temporary: boolean; - }; -} - -interface GitHubRepoInfo { - fullName: string; - defaultBranch: string; - stars: number; - forks: number; - openIssues?: number; -} - -interface GitInfo { - local: { - commitHash: string; - branch: string; - commitTime: string; - author: string; - email: string; - remoteUrl: string; - repoName: string; - }; - github?: { - currentRepo: GitHubRepoInfo; - upstream?: GitHubRepoInfo; - }; - isForked?: boolean; -} - -interface WebAppInfo { - name: string; - version: string; - description: string; - license: string; - environment: string; - timestamp: string; - runtimeInfo: { - nodeVersion: string; - }; - dependencies: { - production: Array<{ name: string; version: string; type: string }>; - development: Array<{ name: string; version: string; type: string }>; - peer: Array<{ name: string; version: string; type: string }>; - optional: Array<{ name: string; version: string; type: string }>; - }; - gitInfo: GitInfo; -} - -// Add Ollama service status interface -interface OllamaServiceStatus { - isRunning: boolean; - lastChecked: Date; - error?: string; - models?: Array<{ - name: string; - size: string; - quantization: string; - }>; -} - -interface ExportFormat { - id: string; - label: string; - icon: string; - handler: () => void; -} - -const DependencySection = ({ - title, - deps, -}: { - title: string; - deps: Array<{ name: string; version: string; type: string }>; -}) => { - const [isOpen, setIsOpen] = useState(false); - - if (deps.length === 0) { - return null; - } - - return ( - - -
-
- - {title} Dependencies ({deps.length}) - -
-
- {isOpen ? 'Hide' : 'Show'} -
-
- - - -
- {deps.map((dep) => ( -
- {dep.name} - {dep.version} -
- ))} -
-
-
- - ); -}; - -export default function DebugTab() { - const [systemInfo, setSystemInfo] = useState(null); - const [webAppInfo, setWebAppInfo] = useState(null); - const [ollamaStatus, setOllamaStatus] = useState({ - isRunning: false, - lastChecked: new Date(), - }); - const [loading, setLoading] = useState({ - systemInfo: false, - webAppInfo: false, - errors: false, - performance: false, - }); - const [openSections, setOpenSections] = useState({ - system: false, - webapp: false, - errors: false, - performance: false, - }); - - const { providers } = useSettings(); - - // Subscribe to logStore updates - const logs = useStore(logStore.logs); - const errorLogs = useMemo(() => { - return Object.values(logs).filter( - (log): log is LogEntry => typeof log === 'object' && log !== null && 'level' in log && log.level === 'error', - ); - }, [logs]); - - // Set up error listeners when component mounts - useEffect(() => { - const handleError = (event: ErrorEvent) => { - logStore.logError(event.message, event.error, { - filename: event.filename, - lineNumber: event.lineno, - columnNumber: event.colno, - }); - }; - - const handleRejection = (event: PromiseRejectionEvent) => { - logStore.logError('Unhandled Promise Rejection', event.reason); - }; - - window.addEventListener('error', handleError); - window.addEventListener('unhandledrejection', handleRejection); - - return () => { - window.removeEventListener('error', handleError); - window.removeEventListener('unhandledrejection', handleRejection); - }; - }, []); - - // Check for errors when the errors section is opened - useEffect(() => { - if (openSections.errors) { - checkErrors(); - } - }, [openSections.errors]); - - // Load initial data when component mounts - useEffect(() => { - const loadInitialData = async () => { - await Promise.all([getSystemInfo(), getWebAppInfo()]); - }; - - loadInitialData(); - }, []); - - // Refresh data when sections are opened - useEffect(() => { - if (openSections.system) { - getSystemInfo(); - } - - if (openSections.webapp) { - getWebAppInfo(); - } - }, [openSections.system, openSections.webapp]); - - // Add periodic refresh of git info - useEffect(() => { - if (!openSections.webapp) { - return undefined; - } - - // Initial fetch - const fetchGitInfo = async () => { - try { - const response = await fetch('/api/system/git-info'); - const updatedGitInfo = (await response.json()) as GitInfo; - - setWebAppInfo((prev) => { - if (!prev) { - return null; - } - - // Only update if the data has changed - if (JSON.stringify(prev.gitInfo) === JSON.stringify(updatedGitInfo)) { - return prev; - } - - return { - ...prev, - gitInfo: updatedGitInfo, - }; - }); - } catch (error) { - console.error('Failed to fetch git info:', error); - } - }; - - fetchGitInfo(); - - // Refresh every 5 minutes instead of every second - const interval = setInterval(fetchGitInfo, 5 * 60 * 1000); - - return () => clearInterval(interval); - }, [openSections.webapp]); - - const getSystemInfo = async () => { - try { - setLoading((prev) => ({ ...prev, systemInfo: true })); - - // Get better OS detection - const userAgent = navigator.userAgent; - let detectedOS = 'Unknown'; - let detectedArch = 'unknown'; - - // Improved OS detection - if (userAgent.indexOf('Win') !== -1) { - detectedOS = 'Windows'; - } else if (userAgent.indexOf('Mac') !== -1) { - detectedOS = 'macOS'; - } else if (userAgent.indexOf('Linux') !== -1) { - detectedOS = 'Linux'; - } else if (userAgent.indexOf('Android') !== -1) { - detectedOS = 'Android'; - } else if (/iPhone|iPad|iPod/.test(userAgent)) { - detectedOS = 'iOS'; - } - - // Better architecture detection - if (userAgent.indexOf('x86_64') !== -1 || userAgent.indexOf('x64') !== -1 || userAgent.indexOf('WOW64') !== -1) { - detectedArch = 'x64'; - } else if (userAgent.indexOf('x86') !== -1 || userAgent.indexOf('i686') !== -1) { - detectedArch = 'x86'; - } else if (userAgent.indexOf('arm64') !== -1 || userAgent.indexOf('aarch64') !== -1) { - detectedArch = 'arm64'; - } else if (userAgent.indexOf('arm') !== -1) { - detectedArch = 'arm'; - } - - // Get browser info with improved detection - const browserName = (() => { - if (userAgent.indexOf('Edge') !== -1 || userAgent.indexOf('Edg/') !== -1) { - return 'Edge'; - } - - if (userAgent.indexOf('Chrome') !== -1) { - return 'Chrome'; - } - - if (userAgent.indexOf('Firefox') !== -1) { - return 'Firefox'; - } - - if (userAgent.indexOf('Safari') !== -1) { - return 'Safari'; - } - - return 'Unknown'; - })(); - - const browserVersionMatch = userAgent.match(/(Edge|Edg|Chrome|Firefox|Safari)[\s/](\d+(\.\d+)*)/); - const browserVersion = browserVersionMatch ? browserVersionMatch[2] : 'Unknown'; - - // Get performance metrics - const memory = (performance as any).memory || {}; - const timing = performance.timing; - const navigation = performance.navigation; - const connection = (navigator as any).connection || {}; - - // Try to use Navigation Timing API Level 2 when available - let loadTime = 0; - let domReadyTime = 0; - - try { - const navEntries = performance.getEntriesByType('navigation'); - - if (navEntries.length > 0) { - const navTiming = navEntries[0] as PerformanceNavigationTiming; - loadTime = navTiming.loadEventEnd - navTiming.startTime; - domReadyTime = navTiming.domContentLoadedEventEnd - navTiming.startTime; - } else { - // Fall back to older API - loadTime = timing.loadEventEnd - timing.navigationStart; - domReadyTime = timing.domContentLoadedEventEnd - timing.navigationStart; - } - } catch { - // Fall back to older API if Navigation Timing API Level 2 is not available - loadTime = timing.loadEventEnd - timing.navigationStart; - domReadyTime = timing.domContentLoadedEventEnd - timing.navigationStart; - } - - // Get battery info - let batteryInfo; - - try { - const battery = await (navigator as any).getBattery(); - batteryInfo = { - charging: battery.charging, - chargingTime: battery.chargingTime, - dischargingTime: battery.dischargingTime, - level: battery.level * 100, - }; - } catch { - console.log('Battery API not supported'); - } - - // Get storage info - let storageInfo = { - quota: 0, - usage: 0, - persistent: false, - temporary: false, - }; - - try { - const storage = await navigator.storage.estimate(); - const persistent = await navigator.storage.persist(); - storageInfo = { - quota: storage.quota || 0, - usage: storage.usage || 0, - persistent, - temporary: !persistent, - }; - } catch { - console.log('Storage API not supported'); - } - - // Get memory info from browser performance API - const performanceMemory = (performance as any).memory || {}; - const totalMemory = performanceMemory.jsHeapSizeLimit || 0; - const usedMemory = performanceMemory.usedJSHeapSize || 0; - const freeMemory = totalMemory - usedMemory; - const memoryPercentage = totalMemory ? (usedMemory / totalMemory) * 100 : 0; - - const systemInfo: SystemInfo = { - os: detectedOS, - arch: detectedArch, - platform: navigator.platform || 'unknown', - cpus: navigator.hardwareConcurrency + ' cores', - memory: { - total: formatBytes(totalMemory), - free: formatBytes(freeMemory), - used: formatBytes(usedMemory), - percentage: Math.round(memoryPercentage), - }, - node: 'browser', - browser: { - name: browserName, - version: browserVersion, - language: navigator.language, - userAgent: navigator.userAgent, - cookiesEnabled: navigator.cookieEnabled, - online: navigator.onLine, - platform: navigator.platform || 'unknown', - cores: navigator.hardwareConcurrency, - }, - screen: { - width: window.screen.width, - height: window.screen.height, - colorDepth: window.screen.colorDepth, - pixelRatio: window.devicePixelRatio, - }, - time: { - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - offset: new Date().getTimezoneOffset(), - locale: navigator.language, - }, - performance: { - memory: { - jsHeapSizeLimit: memory.jsHeapSizeLimit || 0, - totalJSHeapSize: memory.totalJSHeapSize || 0, - usedJSHeapSize: memory.usedJSHeapSize || 0, - usagePercentage: memory.totalJSHeapSize ? (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100 : 0, - }, - timing: { - loadTime, - domReadyTime, - readyStart: timing.fetchStart - timing.navigationStart, - redirectTime: timing.redirectEnd - timing.redirectStart, - appcacheTime: timing.domainLookupStart - timing.fetchStart, - unloadEventTime: timing.unloadEventEnd - timing.unloadEventStart, - lookupDomainTime: timing.domainLookupEnd - timing.domainLookupStart, - connectTime: timing.connectEnd - timing.connectStart, - requestTime: timing.responseEnd - timing.requestStart, - initDomTreeTime: timing.domInteractive - timing.responseEnd, - loadEventTime: timing.loadEventEnd - timing.loadEventStart, - }, - navigation: { - type: navigation.type, - redirectCount: navigation.redirectCount, - }, - }, - network: { - downlink: connection?.downlink || 0, - effectiveType: connection?.effectiveType || 'unknown', - rtt: connection?.rtt || 0, - saveData: connection?.saveData || false, - type: connection?.type || 'unknown', - }, - battery: batteryInfo, - storage: storageInfo, - }; - - setSystemInfo(systemInfo); - toast.success('System information updated'); - } catch (error) { - toast.error('Failed to get system information'); - console.error('Failed to get system information:', error); - } finally { - setLoading((prev) => ({ ...prev, systemInfo: false })); - } - }; - - // Helper function to format bytes to human readable format with better precision - const formatBytes = (bytes: number) => { - if (bytes === 0) { - return '0 B'; - } - - const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - - // Return with proper precision based on unit size - if (i === 0) { - return `${bytes} ${units[i]}`; - } - - return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`; - }; - - const getWebAppInfo = async () => { - try { - setLoading((prev) => ({ ...prev, webAppInfo: true })); - - const [appResponse, gitResponse] = await Promise.all([ - fetch('/api/system/app-info'), - fetch('/api/system/git-info'), - ]); - - if (!appResponse.ok || !gitResponse.ok) { - throw new Error('Failed to fetch webapp info'); - } - - const appData = (await appResponse.json()) as Omit; - const gitData = (await gitResponse.json()) as GitInfo; - - console.log('Git Info Response:', gitData); // Add logging to debug - - setWebAppInfo({ - ...appData, - gitInfo: gitData, - }); - - toast.success('WebApp information updated'); - - return true; - } catch (error) { - console.error('Failed to fetch webapp info:', error); - toast.error('Failed to fetch webapp information'); - setWebAppInfo(null); - - return false; - } finally { - setLoading((prev) => ({ ...prev, webAppInfo: false })); - } - }; - - const handleLogPerformance = () => { - try { - setLoading((prev) => ({ ...prev, performance: true })); - - // Get performance metrics using modern Performance API - const performanceEntries = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; - const memory = (performance as any).memory; - - // Calculate timing metrics - const timingMetrics = { - loadTime: performanceEntries.loadEventEnd - performanceEntries.startTime, - domReadyTime: performanceEntries.domContentLoadedEventEnd - performanceEntries.startTime, - fetchTime: performanceEntries.responseEnd - performanceEntries.fetchStart, - redirectTime: performanceEntries.redirectEnd - performanceEntries.redirectStart, - dnsTime: performanceEntries.domainLookupEnd - performanceEntries.domainLookupStart, - tcpTime: performanceEntries.connectEnd - performanceEntries.connectStart, - ttfb: performanceEntries.responseStart - performanceEntries.requestStart, - processingTime: performanceEntries.loadEventEnd - performanceEntries.responseEnd, - }; - - // Get resource timing data - const resourceEntries = performance.getEntriesByType('resource'); - const resourceStats = { - totalResources: resourceEntries.length, - totalSize: resourceEntries.reduce((total, entry) => total + ((entry as any).transferSize || 0), 0), - totalTime: Math.max(...resourceEntries.map((entry) => entry.duration)), - }; - - // Get memory metrics - const memoryMetrics = memory - ? { - jsHeapSizeLimit: memory.jsHeapSizeLimit, - totalJSHeapSize: memory.totalJSHeapSize, - usedJSHeapSize: memory.usedJSHeapSize, - heapUtilization: (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100, - } - : null; - - // Get frame rate metrics - let fps = 0; - - if ('requestAnimationFrame' in window) { - const times: number[] = []; - - function calculateFPS(now: number) { - times.push(now); - - if (times.length > 10) { - const fps = Math.round((1000 * 10) / (now - times[0])); - times.shift(); - - return fps; - } - - requestAnimationFrame(calculateFPS); - - return 0; - } - - fps = calculateFPS(performance.now()); - } - - // Log all performance metrics - logStore.logSystem('Performance Metrics', { - timing: timingMetrics, - resources: resourceStats, - memory: memoryMetrics, - fps, - timestamp: new Date().toISOString(), - navigationEntry: { - type: performanceEntries.type, - redirectCount: performanceEntries.redirectCount, - }, - }); - - toast.success('Performance metrics logged'); - } catch (error) { - toast.error('Failed to log performance metrics'); - console.error('Failed to log performance metrics:', error); - } finally { - setLoading((prev) => ({ ...prev, performance: false })); - } - }; - - const checkErrors = async () => { - try { - setLoading((prev) => ({ ...prev, errors: true })); - - // Get errors from log store - const storedErrors = errorLogs; - - if (storedErrors.length === 0) { - toast.success('No errors found'); - } else { - toast.warning(`Found ${storedErrors.length} error(s)`); - } - } catch (error) { - toast.error('Failed to check errors'); - console.error('Failed to check errors:', error); - } finally { - setLoading((prev) => ({ ...prev, errors: false })); - } - }; - - const exportDebugInfo = () => { - try { - const debugData = { - timestamp: new Date().toISOString(), - system: systemInfo, - webApp: webAppInfo, - errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), - performance: { - memory: (performance as any).memory || {}, - timing: performance.timing, - navigation: performance.navigation, - }, - }; - - const blob = new Blob([JSON.stringify(debugData, null, 2)], { type: 'application/json' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `bolt-debug-info-${new Date().toISOString()}.json`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Debug information exported successfully'); - } catch (error) { - console.error('Failed to export debug info:', error); - toast.error('Failed to export debug information'); - } - }; - - const exportAsCSV = () => { - try { - const debugData = { - system: systemInfo, - webApp: webAppInfo, - errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), - performance: { - memory: (performance as any).memory || {}, - timing: performance.timing, - navigation: performance.navigation, - }, - }; - - // Convert the data to CSV format - const csvData = [ - ['Category', 'Key', 'Value'], - ...Object.entries(debugData).flatMap(([category, data]) => - Object.entries(data || {}).map(([key, value]) => [ - category, - key, - typeof value === 'object' ? JSON.stringify(value) : String(value), - ]), - ), - ]; - - // Create CSV content - const csvContent = csvData.map((row) => row.join(',')).join('\n'); - const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `bolt-debug-info-${new Date().toISOString()}.csv`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Debug information exported as CSV'); - } catch (error) { - console.error('Failed to export CSV:', error); - toast.error('Failed to export debug information as CSV'); - } - }; - - const exportAsPDF = () => { - try { - const debugData = { - system: systemInfo, - webApp: webAppInfo, - errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), - performance: { - memory: (performance as any).memory || {}, - timing: performance.timing, - navigation: performance.navigation, - }, - }; - - // Create new PDF document - const doc = new jsPDF(); - const lineHeight = 7; - let yPos = 20; - const margin = 20; - const pageWidth = doc.internal.pageSize.getWidth(); - const maxLineWidth = pageWidth - 2 * margin; - - // Add key-value pair with better formatting - const addKeyValue = (key: string, value: any, indent = 0) => { - // Check if we need a new page - if (yPos > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - } - - doc.setFontSize(10); - doc.setTextColor('#374151'); - doc.setFont('helvetica', 'bold'); - - // Format the key with proper spacing - const formattedKey = key.replace(/([A-Z])/g, ' $1').trim(); - doc.text(formattedKey + ':', margin + indent, yPos); - doc.setFont('helvetica', 'normal'); - doc.setTextColor('#6B7280'); - - let valueText; - - if (typeof value === 'object' && value !== null) { - // Skip rendering if value is empty object - if (Object.keys(value).length === 0) { - return; - } - - yPos += lineHeight; - Object.entries(value).forEach(([subKey, subValue]) => { - // Check for page break before each sub-item - if (yPos > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - } - - const formattedSubKey = subKey.replace(/([A-Z])/g, ' $1').trim(); - addKeyValue(formattedSubKey, subValue, indent + 10); - }); - - return; - } else { - valueText = String(value); - } - - const valueX = margin + indent + doc.getTextWidth(formattedKey + ': '); - const maxValueWidth = maxLineWidth - indent - doc.getTextWidth(formattedKey + ': '); - const lines = doc.splitTextToSize(valueText, maxValueWidth); - - // Check if we need a new page for the value - if (yPos + lines.length * lineHeight > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - } - - doc.text(lines, valueX, yPos); - yPos += lines.length * lineHeight; - }; - - // Add section header with page break check - const addSectionHeader = (title: string) => { - // Check if we need a new page - if (yPos + 20 > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - } - - yPos += lineHeight; - doc.setFillColor('#F3F4F6'); - doc.rect(margin - 2, yPos - 5, pageWidth - 2 * (margin - 2), lineHeight + 6, 'F'); - doc.setFont('helvetica', 'bold'); - doc.setTextColor('#111827'); - doc.setFontSize(12); - doc.text(title.toUpperCase(), margin, yPos); - doc.setFont('helvetica', 'normal'); - yPos += lineHeight * 1.5; - }; - - // Add horizontal line with page break check - const addHorizontalLine = () => { - // Check if we need a new page - if (yPos + 10 > doc.internal.pageSize.getHeight() - 20) { - doc.addPage(); - yPos = margin; - - return; // Skip drawing line if we just started a new page - } - - doc.setDrawColor('#E5E5E5'); - doc.line(margin, yPos, pageWidth - margin, yPos); - yPos += lineHeight; - }; - - // Helper function to add footer to all pages - const addFooters = () => { - const totalPages = doc.internal.pages.length - 1; - - for (let i = 1; i <= totalPages; i++) { - doc.setPage(i); - doc.setFontSize(8); - doc.setTextColor('#9CA3AF'); - doc.text(`Page ${i} of ${totalPages}`, pageWidth / 2, doc.internal.pageSize.getHeight() - 10, { - align: 'center', - }); - } - }; - - // Title and Header (first page only) - doc.setFillColor('#6366F1'); - doc.rect(0, 0, pageWidth, 40, 'F'); - doc.setTextColor('#FFFFFF'); - doc.setFontSize(24); - doc.setFont('helvetica', 'bold'); - doc.text('Debug Information Report', margin, 25); - yPos = 50; - - // Timestamp and metadata - doc.setTextColor('#6B7280'); - doc.setFontSize(10); - doc.setFont('helvetica', 'normal'); - - const timestamp = new Date().toLocaleString(undefined, { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }); - doc.text(`Generated: ${timestamp}`, margin, yPos); - yPos += lineHeight * 2; - - // System Information Section - if (debugData.system) { - addSectionHeader('System Information'); - - // OS and Architecture - addKeyValue('Operating System', debugData.system.os); - addKeyValue('Architecture', debugData.system.arch); - addKeyValue('Platform', debugData.system.platform); - addKeyValue('CPU Cores', debugData.system.cpus); - - // Memory - const memory = debugData.system.memory; - addKeyValue('Memory', { - 'Total Memory': memory.total, - 'Used Memory': memory.used, - 'Free Memory': memory.free, - Usage: memory.percentage + '%', - }); - - // Browser Information - const browser = debugData.system.browser; - addKeyValue('Browser', { - Name: browser.name, - Version: browser.version, - Language: browser.language, - Platform: browser.platform, - 'Cookies Enabled': browser.cookiesEnabled ? 'Yes' : 'No', - 'Online Status': browser.online ? 'Online' : 'Offline', - }); - - // Screen Information - const screen = debugData.system.screen; - addKeyValue('Screen', { - Resolution: `${screen.width}x${screen.height}`, - 'Color Depth': screen.colorDepth + ' bit', - 'Pixel Ratio': screen.pixelRatio + 'x', - }); - - // Time Information - const time = debugData.system.time; - addKeyValue('Time Settings', { - Timezone: time.timezone, - 'UTC Offset': time.offset / 60 + ' hours', - Locale: time.locale, - }); - - addHorizontalLine(); - } - - // Web App Information Section - if (debugData.webApp) { - addSectionHeader('Web App Information'); - - // Basic Info - addKeyValue('Application', { - Name: debugData.webApp.name, - Version: debugData.webApp.version, - Environment: debugData.webApp.environment, - 'Node Version': debugData.webApp.runtimeInfo.nodeVersion, - }); - - // Git Information - if (debugData.webApp.gitInfo) { - const gitInfo = debugData.webApp.gitInfo.local; - addKeyValue('Git Information', { - Branch: gitInfo.branch, - Commit: gitInfo.commitHash, - Author: gitInfo.author, - 'Commit Time': gitInfo.commitTime, - Repository: gitInfo.repoName, - }); - - if (debugData.webApp.gitInfo.github) { - const githubInfo = debugData.webApp.gitInfo.github.currentRepo; - addKeyValue('GitHub Information', { - Repository: githubInfo.fullName, - 'Default Branch': githubInfo.defaultBranch, - Stars: githubInfo.stars, - Forks: githubInfo.forks, - 'Open Issues': githubInfo.openIssues || 0, - }); - } - } - - addHorizontalLine(); - } - - // Performance Section - if (debugData.performance) { - addSectionHeader('Performance Metrics'); - - // Memory Usage - const memory = debugData.performance.memory || {}; - const totalHeap = memory.totalJSHeapSize || 0; - const usedHeap = memory.usedJSHeapSize || 0; - const usagePercentage = memory.usagePercentage || 0; - - addKeyValue('Memory Usage', { - 'Total Heap Size': formatBytes(totalHeap), - 'Used Heap Size': formatBytes(usedHeap), - Usage: usagePercentage.toFixed(1) + '%', - }); - - // Timing Metrics - const timing = debugData.performance.timing || {}; - const navigationStart = timing.navigationStart || 0; - const loadEventEnd = timing.loadEventEnd || 0; - const domContentLoadedEventEnd = timing.domContentLoadedEventEnd || 0; - const responseEnd = timing.responseEnd || 0; - const requestStart = timing.requestStart || 0; - - const loadTime = loadEventEnd > navigationStart ? loadEventEnd - navigationStart : 0; - const domReadyTime = - domContentLoadedEventEnd > navigationStart ? domContentLoadedEventEnd - navigationStart : 0; - const requestTime = responseEnd > requestStart ? responseEnd - requestStart : 0; - - addKeyValue('Page Load Metrics', { - 'Total Load Time': (loadTime / 1000).toFixed(2) + ' seconds', - 'DOM Ready Time': (domReadyTime / 1000).toFixed(2) + ' seconds', - 'Request Time': (requestTime / 1000).toFixed(2) + ' seconds', - }); - - // Network Information - if (debugData.system?.network) { - const network = debugData.system.network; - addKeyValue('Network Information', { - 'Connection Type': network.type || 'Unknown', - 'Effective Type': network.effectiveType || 'Unknown', - 'Download Speed': (network.downlink || 0) + ' Mbps', - 'Latency (RTT)': (network.rtt || 0) + ' ms', - 'Data Saver': network.saveData ? 'Enabled' : 'Disabled', - }); - } - - addHorizontalLine(); - } - - // Errors Section - if (debugData.errors && debugData.errors.length > 0) { - addSectionHeader('Error Log'); - - debugData.errors.forEach((error: LogEntry, index: number) => { - doc.setTextColor('#DC2626'); - doc.setFontSize(10); - doc.setFont('helvetica', 'bold'); - doc.text(`Error ${index + 1}:`, margin, yPos); - yPos += lineHeight; - - doc.setFont('helvetica', 'normal'); - doc.setTextColor('#6B7280'); - addKeyValue('Message', error.message, 10); - - if (error.stack) { - addKeyValue('Stack', error.stack, 10); - } - - if (error.source) { - addKeyValue('Source', error.source, 10); - } - - yPos += lineHeight; - }); - } - - // Add footers to all pages at the end - addFooters(); - - // Save the PDF - doc.save(`bolt-debug-info-${new Date().toISOString()}.pdf`); - toast.success('Debug information exported as PDF'); - } catch (error) { - console.error('Failed to export PDF:', error); - toast.error('Failed to export debug information as PDF'); - } - }; - - const exportAsText = () => { - try { - const debugData = { - system: systemInfo, - webApp: webAppInfo, - errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), - performance: { - memory: (performance as any).memory || {}, - timing: performance.timing, - navigation: performance.navigation, - }, - }; - - const textContent = Object.entries(debugData) - .map(([category, data]) => { - return `${category.toUpperCase()}\n${'-'.repeat(30)}\n${JSON.stringify(data, null, 2)}\n\n`; - }) - .join('\n'); - - const blob = new Blob([textContent], { type: 'text/plain' }); - const url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `bolt-debug-info-${new Date().toISOString()}.txt`; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); - toast.success('Debug information exported as text file'); - } catch (error) { - console.error('Failed to export text file:', error); - toast.error('Failed to export debug information as text file'); - } - }; - - const exportFormats: ExportFormat[] = [ - { - id: 'json', - label: 'Export as JSON', - icon: 'i-ph:file-js', - handler: exportDebugInfo, - }, - { - id: 'csv', - label: 'Export as CSV', - icon: 'i-ph:file-csv', - handler: exportAsCSV, - }, - { - id: 'pdf', - label: 'Export as PDF', - icon: 'i-ph:file-pdf', - handler: exportAsPDF, - }, - { - id: 'txt', - label: 'Export as Text', - icon: 'i-ph:file-text', - handler: exportAsText, - }, - ]; - - // Add Ollama health check function - const checkOllamaStatus = useCallback(async () => { - try { - const ollamaProvider = providers?.Ollama; - const baseUrl = ollamaProvider?.settings?.baseUrl || 'http://127.0.0.1:11434'; - - // First check if service is running - const versionResponse = await fetch(`${baseUrl}/api/version`); - - if (!versionResponse.ok) { - throw new Error('Service not running'); - } - - // Then fetch installed models - const modelsResponse = await fetch(`${baseUrl}/api/tags`); - - const modelsData = (await modelsResponse.json()) as { - models: Array<{ name: string; size: string; quantization: string }>; - }; - - setOllamaStatus({ - isRunning: true, - lastChecked: new Date(), - models: modelsData.models, - }); - } catch { - setOllamaStatus({ - isRunning: false, - error: 'Connection failed', - lastChecked: new Date(), - models: undefined, - }); - } - }, [providers]); - - // Monitor Ollama provider status and check periodically - useEffect(() => { - const ollamaProvider = providers?.Ollama; - - if (ollamaProvider?.settings?.enabled) { - // Check immediately when provider is enabled - checkOllamaStatus(); - - // Set up periodic checks every 10 seconds - const intervalId = setInterval(checkOllamaStatus, 10000); - - return () => clearInterval(intervalId); - } - - return undefined; - }, [providers, checkOllamaStatus]); - - // Replace the existing export button with this new component - const ExportButton = () => { - const [isOpen, setIsOpen] = useState(false); - - const handleOpenChange = useCallback((open: boolean) => { - setIsOpen(open); - }, []); - - const handleFormatClick = useCallback((handler: () => void) => { - handler(); - setIsOpen(false); - }, []); - - return ( - - - - -
- -
- Export Debug Information - - -
- {exportFormats.map((format) => ( - - ))} -
-
-
-
- ); - }; - - // Add helper function to get Ollama status text and color - const getOllamaStatus = () => { - const ollamaProvider = providers?.Ollama; - const isOllamaEnabled = ollamaProvider?.settings?.enabled; - - if (!isOllamaEnabled) { - return { - status: 'Disabled', - color: 'text-red-500', - bgColor: 'bg-red-500', - message: 'Ollama provider is disabled in settings', - }; - } - - if (!ollamaStatus.isRunning) { - return { - status: 'Not Running', - color: 'text-red-500', - bgColor: 'bg-red-500', - message: ollamaStatus.error || 'Ollama service is not running', - }; - } - - const modelCount = ollamaStatus.models?.length ?? 0; - - return { - status: 'Running', - color: 'text-green-500', - bgColor: 'bg-green-500', - message: `Ollama service is running with ${modelCount} installed models (Provider: Enabled)`, - }; - }; - - // Add type for status result - type StatusResult = { - status: string; - color: string; - bgColor: string; - message: string; - }; - - const status = getOllamaStatus() as StatusResult; - - return ( -
- {/* Quick Stats Banner */} -
- {/* Errors Card */} -
-
-
-
Errors
-
-
- 0 ? 'text-red-500' : 'text-green-500')} - > - {errorLogs.length} - -
-
-
0 ? 'i-ph:warning text-red-500' : 'i-ph:check-circle text-green-500', - )} - /> - {errorLogs.length > 0 ? 'Errors detected' : 'No errors detected'} -
-
- - {/* Memory Usage Card */} -
-
-
-
Memory Usage
-
-
- 80 - ? 'text-red-500' - : (systemInfo?.memory?.percentage ?? 0) > 60 - ? 'text-yellow-500' - : 'text-green-500', - )} - > - {systemInfo?.memory?.percentage ?? 0}% - -
- 80 - ? '[&>div]:bg-red-500' - : (systemInfo?.memory?.percentage ?? 0) > 60 - ? '[&>div]:bg-yellow-500' - : '[&>div]:bg-green-500', - )} - /> -
-
- Used: {systemInfo?.memory.used ?? '0 GB'} / {systemInfo?.memory.total ?? '0 GB'} -
-
- - {/* Page Load Time Card */} -
-
-
-
Page Load Time
-
-
- 2000 - ? 'text-red-500' - : (systemInfo?.performance.timing.loadTime ?? 0) > 1000 - ? 'text-yellow-500' - : 'text-green-500', - )} - > - {systemInfo ? (systemInfo.performance.timing.loadTime / 1000).toFixed(2) : '-'}s - -
-
-
- DOM Ready: {systemInfo ? (systemInfo.performance.timing.domReadyTime / 1000).toFixed(2) : '-'}s -
-
- - {/* Network Speed Card */} -
-
-
-
Network Speed
-
-
- - {systemInfo?.network.downlink ?? '-'} Mbps - -
-
-
- RTT: {systemInfo?.network.rtt ?? '-'} ms -
-
- - {/* Ollama Service Card - Now spans all 4 columns */} -
-
-
-
-
-
Ollama Service
-
{status.message}
-
-
-
-
-
- - {status.status} - -
-
-
- {ollamaStatus.lastChecked.toLocaleTimeString()} -
-
-
- -
- {status.status === 'Running' && ollamaStatus.models && ollamaStatus.models.length > 0 ? ( - <> -
-
-
- Installed Models - - {ollamaStatus.models.length} - -
-
-
-
- {ollamaStatus.models.map((model) => ( -
-
-
- {model.name} -
- - {Math.round(parseInt(model.size) / 1024 / 1024)}MB - -
- ))} -
-
- - ) : ( -
-
-
- {status.message} -
-
- )} -
-
-
- - {/* Action Buttons */} -
- - - - - - - - - -
- - {/* System Information */} - setOpenSections((prev) => ({ ...prev, system: open }))} - className="w-full" - > - -
-
-
-

System Information

-
-
-
- - - -
- {systemInfo ? ( -
-
-
-
- OS: - {systemInfo.os} -
-
-
- Platform: - {systemInfo.platform} -
-
-
- Architecture: - {systemInfo.arch} -
-
-
- CPU Cores: - {systemInfo.cpus} -
-
-
- Node Version: - {systemInfo.node} -
-
-
- Network Type: - - {systemInfo.network.type} ({systemInfo.network.effectiveType}) - -
-
-
- Network Speed: - - {systemInfo.network.downlink}Mbps (RTT: {systemInfo.network.rtt}ms) - -
- {systemInfo.battery && ( -
-
- Battery: - - {systemInfo.battery.level.toFixed(1)}% {systemInfo.battery.charging ? '(Charging)' : ''} - -
- )} -
-
- Storage: - - {(systemInfo.storage.usage / (1024 * 1024 * 1024)).toFixed(2)}GB /{' '} - {(systemInfo.storage.quota / (1024 * 1024 * 1024)).toFixed(2)}GB - -
-
-
-
-
- Memory Usage: - - {systemInfo.memory.used} / {systemInfo.memory.total} ({systemInfo.memory.percentage}%) - -
-
-
- Browser: - - {systemInfo.browser.name} {systemInfo.browser.version} - -
-
-
- Screen: - - {systemInfo.screen.width}x{systemInfo.screen.height} ({systemInfo.screen.pixelRatio}x) - -
-
-
- Timezone: - {systemInfo.time.timezone} -
-
-
- Language: - {systemInfo.browser.language} -
-
-
- JS Heap: - - {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} - {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB ( - {systemInfo.performance.memory.usagePercentage.toFixed(1)}%) - -
-
-
- Page Load: - - {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s - -
-
-
- DOM Ready: - - {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s - -
-
-
- ) : ( -
Loading system information...
- )} -
- - - - {/* Performance Metrics */} - setOpenSections((prev) => ({ ...prev, performance: open }))} - className="w-full" - > - -
-
-
-

Performance Metrics

-
-
-
- - - -
- {systemInfo && ( -
-
-
- Page Load Time: - - {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s - -
-
- DOM Ready Time: - - {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s - -
-
- Request Time: - - {(systemInfo.performance.timing.requestTime / 1000).toFixed(2)}s - -
-
- Redirect Time: - - {(systemInfo.performance.timing.redirectTime / 1000).toFixed(2)}s - -
-
-
-
- JS Heap Usage: - - {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} - {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB - -
-
- Heap Utilization: - - {systemInfo.performance.memory.usagePercentage.toFixed(1)}% - -
-
- Navigation Type: - - {systemInfo.performance.navigation.type === 0 - ? 'Navigate' - : systemInfo.performance.navigation.type === 1 - ? 'Reload' - : systemInfo.performance.navigation.type === 2 - ? 'Back/Forward' - : 'Other'} - -
-
- Redirects: - - {systemInfo.performance.navigation.redirectCount} - -
-
-
- )} -
-
- - - {/* WebApp Information */} - setOpenSections((prev) => ({ ...prev, webapp: open }))} - className="w-full" - > - -
-
-
-

WebApp Information

- {loading.webAppInfo && } -
-
-
- - - -
- {loading.webAppInfo ? ( -
- -
- ) : !webAppInfo ? ( -
-
-

Failed to load WebApp information

- -
- ) : ( -
-
-

Basic Information

-
-
-
- Name: - {webAppInfo.name} -
-
-
- Version: - {webAppInfo.version} -
-
-
- License: - {webAppInfo.license} -
-
-
- Environment: - {webAppInfo.environment} -
-
-
- Node Version: - {webAppInfo.runtimeInfo.nodeVersion} -
-
-
- -
-

Git Information

-
-
-
- Branch: - {webAppInfo.gitInfo.local.branch} -
-
-
- Commit: - {webAppInfo.gitInfo.local.commitHash} -
-
-
- Author: - {webAppInfo.gitInfo.local.author} -
-
-
- Commit Time: - {webAppInfo.gitInfo.local.commitTime} -
- - {webAppInfo.gitInfo.github && ( - <> -
-
-
- Repository: - - {webAppInfo.gitInfo.github.currentRepo.fullName} - {webAppInfo.gitInfo.isForked && ' (fork)'} - -
- -
-
-
- - {webAppInfo.gitInfo.github.currentRepo.stars} - -
-
-
- - {webAppInfo.gitInfo.github.currentRepo.forks} - -
-
-
- - {webAppInfo.gitInfo.github.currentRepo.openIssues} - -
-
-
- - {webAppInfo.gitInfo.github.upstream && ( -
-
-
- Upstream: - - {webAppInfo.gitInfo.github.upstream.fullName} - -
- -
-
-
- - {webAppInfo.gitInfo.github.upstream.stars} - -
-
-
- - {webAppInfo.gitInfo.github.upstream.forks} - -
-
-
- )} - - )} -
-
-
- )} - - {webAppInfo && ( -
-

Dependencies

-
- - - - -
-
- )} -
- - - - {/* Error Check */} - setOpenSections((prev) => ({ ...prev, errors: open }))} - className="w-full" - > - -
-
-
-

Error Check

- {errorLogs.length > 0 && ( - - {errorLogs.length} Errors - - )} -
-
-
- - - -
- -
-
- Checks for: -
    -
  • Unhandled JavaScript errors
  • -
  • Unhandled Promise rejections
  • -
  • Runtime exceptions
  • -
  • Network errors
  • -
-
-
- Status: - - {loading.errors - ? 'Checking...' - : errorLogs.length > 0 - ? `${errorLogs.length} errors found` - : 'No errors found'} - -
- {errorLogs.length > 0 && ( -
-
Recent Errors:
-
- {errorLogs.map((error) => ( -
-
{error.message}
- {error.source && ( -
- Source: {error.source} - {error.details?.lineNumber && `:${error.details.lineNumber}`} -
- )} - {error.stack && ( -
{error.stack}
- )} -
- ))} -
-
- )} -
-
-
-
- -
- ); -} diff --git a/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx b/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx deleted file mode 100644 index 9d5de52..0000000 --- a/app/components/@settings/tabs/task-manager/TaskManagerTab.tsx +++ /dev/null @@ -1,1602 +0,0 @@ -import * as React from 'react'; -import { useEffect, useState, useCallback } from 'react'; -import { classNames } from '~/utils/classNames'; -import { Line } from 'react-chartjs-2'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, - type Chart, -} from 'chart.js'; -import { toast } from 'react-toastify'; // Import toast -import { useUpdateCheck } from '~/lib/hooks/useUpdateCheck'; -import { tabConfigurationStore, type TabConfig } from '~/lib/stores/tabConfigurationStore'; -import { useStore } from 'zustand'; - -// Register ChartJS components -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend); - -interface BatteryManager extends EventTarget { - charging: boolean; - chargingTime: number; - dischargingTime: number; - level: number; -} - -interface SystemMemoryInfo { - total: number; - free: number; - used: number; - percentage: number; - swap?: { - total: number; - free: number; - used: number; - percentage: number; - }; - timestamp: string; - error?: string; -} - -interface ProcessInfo { - pid: number; - name: string; - cpu: number; - memory: number; - command?: string; - timestamp: string; - error?: string; -} - -interface DiskInfo { - filesystem: string; - size: number; - used: number; - available: number; - percentage: number; - mountpoint: string; - timestamp: string; - error?: string; -} - -interface SystemMetrics { - memory: { - used: number; - total: number; - percentage: number; - process?: { - heapUsed: number; - heapTotal: number; - external: number; - rss: number; - }; - }; - systemMemory?: SystemMemoryInfo; - processes?: ProcessInfo[]; - disks?: DiskInfo[]; - battery?: { - level: number; - charging: boolean; - timeRemaining?: number; - }; - network: { - downlink: number; - uplink?: number; - latency: { - current: number; - average: number; - min: number; - max: number; - history: number[]; - lastUpdate: number; - }; - type: string; - effectiveType?: string; - }; - performance: { - pageLoad: number; - domReady: number; - resources: { - total: number; - size: number; - loadTime: number; - }; - timing: { - ttfb: number; - fcp: number; - lcp: number; - }; - }; -} - -type SortField = 'name' | 'pid' | 'cpu' | 'memory'; -type SortDirection = 'asc' | 'desc'; - -interface MetricsHistory { - timestamps: string[]; - memory: number[]; - battery: number[]; - network: number[]; - cpu: number[]; - disk: number[]; -} - -interface PerformanceAlert { - type: 'warning' | 'error' | 'info'; - message: string; - timestamp: number; - metric: string; - threshold: number; - value: number; -} - -declare global { - interface Navigator { - getBattery(): Promise; - } - interface Performance { - memory?: { - jsHeapSizeLimit: number; - totalJSHeapSize: number; - usedJSHeapSize: number; - }; - } -} - -// Constants for performance thresholds -const PERFORMANCE_THRESHOLDS = { - memory: { - warning: 75, - critical: 90, - }, - network: { - latency: { - warning: 200, - critical: 500, - }, - }, - battery: { - warning: 20, - critical: 10, - }, -}; - -// Default metrics state -const DEFAULT_METRICS_STATE: SystemMetrics = { - memory: { - used: 0, - total: 0, - percentage: 0, - }, - network: { - downlink: 0, - latency: { - current: 0, - average: 0, - min: 0, - max: 0, - history: [], - lastUpdate: 0, - }, - type: 'unknown', - }, - performance: { - pageLoad: 0, - domReady: 0, - resources: { - total: 0, - size: 0, - loadTime: 0, - }, - timing: { - ttfb: 0, - fcp: 0, - lcp: 0, - }, - }, -}; - -// Default metrics history -const DEFAULT_METRICS_HISTORY: MetricsHistory = { - timestamps: Array(8).fill(new Date().toLocaleTimeString()), - memory: Array(8).fill(0), - battery: Array(8).fill(0), - network: Array(8).fill(0), - cpu: Array(8).fill(0), - disk: Array(8).fill(0), -}; - -// Maximum number of history points to keep -const MAX_HISTORY_POINTS = 8; - -// Used for environment detection in updateMetrics function -const isLocalDevelopment = - typeof window !== 'undefined' && - window.location && - (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); - -// For development environments, we'll always provide mock data if real data isn't available -const isDevelopment = - typeof window !== 'undefined' && - (window.location.hostname === 'localhost' || - window.location.hostname === '127.0.0.1' || - window.location.hostname.includes('192.168.') || - window.location.hostname.includes('.local')); - -// Function to detect Cloudflare and similar serverless environments where TaskManager is not useful -const isServerlessHosting = (): boolean => { - if (typeof window === 'undefined') { - return false; - } - - // For testing: Allow forcing serverless mode via URL param for easy testing - if (typeof window !== 'undefined' && window.location.search.includes('simulate-serverless=true')) { - console.log('Simulating serverless environment for testing'); - return true; - } - - // Check for common serverless hosting domains - const hostname = window.location.hostname; - - return ( - hostname.includes('.cloudflare.') || - hostname.includes('.netlify.app') || - hostname.includes('.vercel.app') || - hostname.endsWith('.workers.dev') - ); -}; - -const TaskManagerTab: React.FC = () => { - const [metrics, setMetrics] = useState(() => DEFAULT_METRICS_STATE); - const [metricsHistory, setMetricsHistory] = useState(() => DEFAULT_METRICS_HISTORY); - const [alerts, setAlerts] = useState([]); - const [lastAlertState, setLastAlertState] = useState('normal'); - const [sortField, setSortField] = useState('memory'); - const [sortDirection, setSortDirection] = useState('desc'); - const [isNotSupported, setIsNotSupported] = useState(false); - - // Chart refs for cleanup - const memoryChartRef = React.useRef | null>(null); - const batteryChartRef = React.useRef | null>(null); - const networkChartRef = React.useRef | null>(null); - const cpuChartRef = React.useRef | null>(null); - const diskChartRef = React.useRef | null>(null); - - // Cleanup chart instances on unmount - React.useEffect(() => { - const cleanupCharts = () => { - if (memoryChartRef.current) { - memoryChartRef.current.destroy(); - } - - if (batteryChartRef.current) { - batteryChartRef.current.destroy(); - } - - if (networkChartRef.current) { - networkChartRef.current.destroy(); - } - - if (cpuChartRef.current) { - cpuChartRef.current.destroy(); - } - - if (diskChartRef.current) { - diskChartRef.current.destroy(); - } - }; - - return cleanupCharts; - }, []); - - // Get update status and tab configuration - const { hasUpdate } = useUpdateCheck(); - const tabConfig = useStore(tabConfigurationStore); - - const resetTabConfiguration = useCallback(() => { - tabConfig.reset(); - return tabConfig.get(); - }, [tabConfig]); - - // Effect to handle tab visibility - useEffect(() => { - const handleTabVisibility = () => { - const currentConfig = tabConfig.get(); - const controlledTabs = ['debug', 'update']; - - // Update visibility based on conditions - const updatedTabs = currentConfig.userTabs.map((tab: TabConfig) => { - if (controlledTabs.includes(tab.id)) { - return { - ...tab, - visible: tab.id === 'debug' ? metrics.memory.percentage > 80 : hasUpdate, - }; - } - - return tab; - }); - - tabConfig.set({ - ...currentConfig, - userTabs: updatedTabs, - }); - }; - - const checkInterval = setInterval(handleTabVisibility, 5000); - - return () => { - clearInterval(checkInterval); - }; - }, [metrics.memory.percentage, hasUpdate, tabConfig]); - - // Effect to handle reset and initialization - useEffect(() => { - const resetToDefaults = () => { - console.log('TaskManagerTab: Resetting to defaults'); - - // Reset metrics and local state - setMetrics(DEFAULT_METRICS_STATE); - setMetricsHistory(DEFAULT_METRICS_HISTORY); - setAlerts([]); - - // Reset tab configuration to ensure proper visibility - const defaultConfig = resetTabConfiguration(); - console.log('TaskManagerTab: Reset tab configuration:', defaultConfig); - }; - - // Listen for both storage changes and custom reset event - const handleReset = (event: Event | StorageEvent) => { - if (event instanceof StorageEvent) { - if (event.key === 'tabConfiguration' && event.newValue === null) { - resetToDefaults(); - } - } else if (event instanceof CustomEvent && event.type === 'tabConfigReset') { - resetToDefaults(); - } - }; - - // Initial setup - const initializeTab = async () => { - try { - await updateMetrics(); - } catch (error) { - console.error('Failed to initialize TaskManagerTab:', error); - resetToDefaults(); - } - }; - - window.addEventListener('storage', handleReset); - window.addEventListener('tabConfigReset', handleReset); - initializeTab(); - - return () => { - window.removeEventListener('storage', handleReset); - window.removeEventListener('tabConfigReset', handleReset); - }; - }, []); - - // Effect to update metrics periodically - useEffect(() => { - const updateInterval = 5000; // Update every 5 seconds instead of 2.5 seconds - let metricsInterval: NodeJS.Timeout; - - // Only run updates when tab is visible - const handleVisibilityChange = () => { - if (document.hidden) { - clearInterval(metricsInterval); - } else { - updateMetrics(); - metricsInterval = setInterval(updateMetrics, updateInterval); - } - }; - - // Initial setup - handleVisibilityChange(); - document.addEventListener('visibilitychange', handleVisibilityChange); - - return () => { - clearInterval(metricsInterval); - document.removeEventListener('visibilitychange', handleVisibilityChange); - }; - }, []); - - // Effect to disable taskmanager on serverless environments - useEffect(() => { - const checkEnvironment = async () => { - // If we're on Cloudflare/Netlify/etc., set not supported - if (isServerlessHosting()) { - setIsNotSupported(true); - return; - } - - // For testing: Allow forcing API failures via URL param - if (typeof window !== 'undefined' && window.location.search.includes('simulate-api-failure=true')) { - console.log('Simulating API failures for testing'); - setIsNotSupported(true); - - return; - } - - // Try to fetch system metrics once as detection - try { - const response = await fetch('/api/system/memory-info'); - const diskResponse = await fetch('/api/system/disk-info'); - const processResponse = await fetch('/api/system/process-info'); - - // If all these return errors or not found, system monitoring is not supported - if (!response.ok && !diskResponse.ok && !processResponse.ok) { - setIsNotSupported(true); - } - } catch (error) { - console.warn('Failed to fetch system metrics. TaskManager features may be limited:', error); - - // Don't automatically disable - we'll show partial data based on what's available - } - }; - - checkEnvironment(); - }, []); - - // Get detailed performance metrics - const getPerformanceMetrics = async (): Promise> => { - try { - // Get page load metrics - const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; - const pageLoad = navigation.loadEventEnd - navigation.startTime; - const domReady = navigation.domContentLoadedEventEnd - navigation.startTime; - - // Get resource metrics - const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[]; - const resourceMetrics = { - total: resources.length, - size: resources.reduce((total, r) => total + (r.transferSize || 0), 0), - loadTime: Math.max(0, ...resources.map((r) => r.duration)), - }; - - // Get Web Vitals - const ttfb = navigation.responseStart - navigation.requestStart; - const paintEntries = performance.getEntriesByType('paint'); - const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; - - // Get LCP using PerformanceObserver - const lcp = await new Promise((resolve) => { - new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1]; - resolve(lastEntry?.startTime || 0); - }).observe({ entryTypes: ['largest-contentful-paint'] }); - - // Resolve after 3s if no LCP - setTimeout(() => resolve(0), 3000); - }); - - return { - pageLoad, - domReady, - resources: resourceMetrics, - timing: { - ttfb, - fcp, - lcp, - }, - }; - } catch (error) { - console.error('Failed to get performance metrics:', error); - return {}; - } - }; - - // Function to measure endpoint latency - const measureLatency = async (): Promise => { - try { - const headers = new Headers(); - headers.append('Cache-Control', 'no-cache, no-store, must-revalidate'); - headers.append('Pragma', 'no-cache'); - headers.append('Expires', '0'); - - const attemptMeasurement = async (): Promise => { - const start = performance.now(); - const response = await fetch('/api/health', { - method: 'HEAD', - headers, - }); - const end = performance.now(); - - if (!response.ok) { - throw new Error(`Health check failed with status: ${response.status}`); - } - - return Math.round(end - start); - }; - - try { - const latency = await attemptMeasurement(); - console.log(`Measured latency: ${latency}ms`); - - return latency; - } catch (error) { - console.warn(`Latency measurement failed, retrying: ${error}`); - - try { - // Retry once - const latency = await attemptMeasurement(); - console.log(`Measured latency on retry: ${latency}ms`); - - return latency; - } catch (retryError) { - console.error(`Latency measurement failed after retry: ${retryError}`); - - // Return a realistic random latency value for development - const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms - console.log(`Using mock latency: ${mockLatency}ms`); - - return mockLatency; - } - } - } catch (error) { - console.error(`Error in latency measurement: ${error}`); - - // Return a realistic random latency value - const mockLatency = 30 + Math.floor(Math.random() * 120); // 30-150ms - console.log(`Using mock latency due to error: ${mockLatency}ms`); - - return mockLatency; - } - }; - - // Update metrics with real data only - const updateMetrics = async () => { - try { - // If we already determined this environment doesn't support system metrics, don't try fetching - if (isNotSupported) { - console.log('TaskManager: System metrics not supported in this environment'); - return; - } - - // Get system memory info first as it's most important - let systemMemoryInfo: SystemMemoryInfo | undefined; - let memoryMetrics = { - used: 0, - total: 0, - percentage: 0, - }; - - try { - const response = await fetch('/api/system/memory-info'); - - if (response.ok) { - systemMemoryInfo = await response.json(); - console.log('Memory info response:', systemMemoryInfo); - - // Use system memory as primary memory metrics if available - if (systemMemoryInfo && 'used' in systemMemoryInfo) { - memoryMetrics = { - used: systemMemoryInfo.used || 0, - total: systemMemoryInfo.total || 1, - percentage: systemMemoryInfo.percentage || 0, - }; - } - } - } catch (error) { - console.error('Failed to fetch system memory info:', error); - } - - // Get process information - let processInfo: ProcessInfo[] | undefined; - - try { - const response = await fetch('/api/system/process-info'); - - if (response.ok) { - processInfo = await response.json(); - console.log('Process info response:', processInfo); - } - } catch (error) { - console.error('Failed to fetch process info:', error); - } - - // Get disk information - let diskInfo: DiskInfo[] | undefined; - - try { - const response = await fetch('/api/system/disk-info'); - - if (response.ok) { - diskInfo = await response.json(); - console.log('Disk info response:', diskInfo); - } - } catch (error) { - console.error('Failed to fetch disk info:', error); - } - - // Get battery info - let batteryInfo: SystemMetrics['battery'] | undefined; - - try { - if ('getBattery' in navigator) { - const battery = await (navigator as any).getBattery(); - batteryInfo = { - level: battery.level * 100, - charging: battery.charging, - timeRemaining: battery.charging ? battery.chargingTime : battery.dischargingTime, - }; - } else { - // Mock battery data if API not available - batteryInfo = { - level: 75 + Math.floor(Math.random() * 20), - charging: Math.random() > 0.3, - timeRemaining: 7200 + Math.floor(Math.random() * 3600), - }; - console.log('Battery API not available, using mock data'); - } - } catch (error) { - console.log('Battery API error, using mock data:', error); - batteryInfo = { - level: 75 + Math.floor(Math.random() * 20), - charging: Math.random() > 0.3, - timeRemaining: 7200 + Math.floor(Math.random() * 3600), - }; - } - - // Enhanced network metrics - const connection = - (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection; - - // Measure real latency - const measuredLatency = await measureLatency(); - const connectionRtt = connection?.rtt || 0; - - // Use measured latency if available, fall back to connection.rtt - const currentLatency = measuredLatency || connectionRtt || Math.floor(Math.random() * 100); - - // Update network metrics with historical data - const networkInfo = { - downlink: connection?.downlink || 1.5 + Math.random(), - uplink: connection?.uplink || 0.5 + Math.random(), - latency: { - current: currentLatency, - average: - metrics.network.latency.history.length > 0 - ? [...metrics.network.latency.history, currentLatency].reduce((a, b) => a + b, 0) / - (metrics.network.latency.history.length + 1) - : currentLatency, - min: - metrics.network.latency.history.length > 0 - ? Math.min(...metrics.network.latency.history, currentLatency) - : currentLatency, - max: - metrics.network.latency.history.length > 0 - ? Math.max(...metrics.network.latency.history, currentLatency) - : currentLatency, - history: [...metrics.network.latency.history, currentLatency].slice(-30), // Keep last 30 measurements - lastUpdate: Date.now(), - }, - type: connection?.type || 'unknown', - effectiveType: connection?.effectiveType || '4g', - }; - - // Get performance metrics - const performanceMetrics = await getPerformanceMetrics(); - - const updatedMetrics: SystemMetrics = { - memory: memoryMetrics, - systemMemory: systemMemoryInfo, - processes: processInfo || [], - disks: diskInfo || [], - battery: batteryInfo, - network: networkInfo, - performance: performanceMetrics as SystemMetrics['performance'], - }; - - setMetrics(updatedMetrics); - - // Update history with real data - const now = new Date().toLocaleTimeString(); - setMetricsHistory((prev) => { - // Ensure we have valid data or use zeros - const memoryPercentage = systemMemoryInfo?.percentage || 0; - const batteryLevel = batteryInfo?.level || 0; - const networkDownlink = networkInfo.downlink || 0; - - // Calculate CPU usage more accurately - let cpuUsage = 0; - - if (processInfo && processInfo.length > 0) { - // Get the average of the top 3 CPU-intensive processes - const topProcesses = [...processInfo].sort((a, b) => b.cpu - a.cpu).slice(0, 3); - const topCpuUsage = topProcesses.reduce((total, proc) => total + proc.cpu, 0); - - // Get the sum of all processes - const totalCpuUsage = processInfo.reduce((total, proc) => total + proc.cpu, 0); - - // Use the higher of the two values, but cap at 100% - cpuUsage = Math.min(Math.max(topCpuUsage, (totalCpuUsage / processInfo.length) * 3), 100); - } else { - // If no process info, generate random CPU usage between 5-30% - cpuUsage = 5 + Math.floor(Math.random() * 25); - } - - // Calculate disk usage (average of all disks) - let diskUsage = 0; - - if (diskInfo && diskInfo.length > 0) { - diskUsage = diskInfo.reduce((total, disk) => total + disk.percentage, 0) / diskInfo.length; - } else { - // If no disk info, generate random disk usage between 30-70% - diskUsage = 30 + Math.floor(Math.random() * 40); - } - - // Create new arrays with the latest data - const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS); - const memory = [...prev.memory, memoryPercentage].slice(-MAX_HISTORY_POINTS); - const battery = [...prev.battery, batteryLevel].slice(-MAX_HISTORY_POINTS); - const network = [...prev.network, networkDownlink].slice(-MAX_HISTORY_POINTS); - const cpu = [...prev.cpu, cpuUsage].slice(-MAX_HISTORY_POINTS); - const disk = [...prev.disk, diskUsage].slice(-MAX_HISTORY_POINTS); - - console.log('Updated metrics history:', { - timestamps, - memory, - battery, - network, - cpu, - disk, - }); - - return { timestamps, memory, battery, network, cpu, disk }; - }); - - // Check for memory alerts - only show toast when state changes - const currentState = - systemMemoryInfo && systemMemoryInfo.percentage > PERFORMANCE_THRESHOLDS.memory.critical - ? 'critical-memory' - : networkInfo.latency.current > PERFORMANCE_THRESHOLDS.network.latency.critical - ? 'critical-network' - : batteryInfo && !batteryInfo.charging && batteryInfo.level < PERFORMANCE_THRESHOLDS.battery.critical - ? 'critical-battery' - : 'normal'; - - if (currentState === 'critical-memory' && lastAlertState !== 'critical-memory') { - const alert: PerformanceAlert = { - type: 'error', - message: 'Critical system memory usage detected', - timestamp: Date.now(), - metric: 'memory', - threshold: PERFORMANCE_THRESHOLDS.memory.critical, - value: systemMemoryInfo?.percentage || 0, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.warning(alert.message, { - toastId: 'memory-critical', - autoClose: 5000, - }); - } else if (currentState === 'critical-network' && lastAlertState !== 'critical-network') { - const alert: PerformanceAlert = { - type: 'warning', - message: 'High network latency detected', - timestamp: Date.now(), - metric: 'network', - threshold: PERFORMANCE_THRESHOLDS.network.latency.critical, - value: networkInfo.latency.current, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.warning(alert.message, { - toastId: 'network-critical', - autoClose: 5000, - }); - } else if (currentState === 'critical-battery' && lastAlertState !== 'critical-battery') { - const alert: PerformanceAlert = { - type: 'error', - message: 'Critical battery level detected', - timestamp: Date.now(), - metric: 'battery', - threshold: PERFORMANCE_THRESHOLDS.battery.critical, - value: batteryInfo?.level || 0, - }; - setAlerts((prev) => { - const newAlerts = [...prev, alert]; - return newAlerts.slice(-10); - }); - toast.error(alert.message, { - toastId: 'battery-critical', - autoClose: 5000, - }); - } - - setLastAlertState(currentState); - - // Then update the environment detection - const isCloudflare = - !isDevelopment && // Not in development mode - ((systemMemoryInfo?.error && systemMemoryInfo.error.includes('not available')) || - (processInfo?.[0]?.error && processInfo[0].error.includes('not available')) || - (diskInfo?.[0]?.error && diskInfo[0].error.includes('not available'))); - - // If we detect that we're in a serverless environment, set the flag - if (isCloudflare || isServerlessHosting()) { - setIsNotSupported(true); - } - - if (isCloudflare) { - console.log('Running in Cloudflare environment. System metrics not available.'); - } else if (isLocalDevelopment) { - console.log('Running in local development environment. Using real or mock system metrics as available.'); - } else if (isDevelopment) { - console.log('Running in development environment. Using real or mock system metrics as available.'); - } else { - console.log('Running in production environment. Using real system metrics.'); - } - } catch (error) { - console.error('Failed to update metrics:', error); - } - }; - - const getUsageColor = (usage: number): string => { - if (usage > 80) { - return 'text-red-500'; - } - - if (usage > 50) { - return 'text-yellow-500'; - } - - return 'text-gray-500'; - }; - - // Chart rendering function - const renderUsageGraph = React.useMemo( - () => - (data: number[], label: string, color: string, chartRef: React.RefObject>) => { - // Ensure we have valid data - const validData = data.map((value) => (isNaN(value) ? 0 : value)); - - // Ensure we have at least 2 data points - if (validData.length < 2) { - // Add a second point if we only have one - if (validData.length === 1) { - validData.push(validData[0]); - } else { - // Add two points if we have none - validData.push(0, 0); - } - } - - const chartData = { - labels: - metricsHistory.timestamps.length > 0 - ? metricsHistory.timestamps - : Array(validData.length) - .fill('') - .map((_, _i) => new Date().toLocaleTimeString()), - datasets: [ - { - label, - data: validData.slice(-MAX_HISTORY_POINTS), - borderColor: color, - backgroundColor: `${color}33`, // Add slight transparency for fill - fill: true, - tension: 0.4, - pointRadius: 2, // Small points for better UX - borderWidth: 2, - }, - ], - }; - - const options = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true, - max: label === 'Network' ? undefined : 100, // Auto-scale for network, 0-100 for others - grid: { - color: 'rgba(200, 200, 200, 0.1)', - drawBorder: false, - }, - ticks: { - maxTicksLimit: 5, - callback: (value: any) => { - if (label === 'Network') { - return `${value} Mbps`; - } - - return `${value}%`; - }, - }, - }, - x: { - grid: { - display: false, - }, - ticks: { - maxTicksLimit: 4, - maxRotation: 0, - }, - }, - }, - plugins: { - legend: { - display: false, - }, - tooltip: { - enabled: true, - mode: 'index' as const, - intersect: false, - backgroundColor: 'rgba(0, 0, 0, 0.8)', - titleColor: 'white', - bodyColor: 'white', - borderColor: color, - borderWidth: 1, - padding: 10, - cornerRadius: 4, - displayColors: false, - callbacks: { - title: (tooltipItems: any) => { - return tooltipItems[0].label; // Show timestamp - }, - label: (context: any) => { - const value = context.raw; - - if (label === 'Memory') { - return `Memory: ${value.toFixed(1)}%`; - } else if (label === 'CPU') { - return `CPU: ${value.toFixed(1)}%`; - } else if (label === 'Battery') { - return `Battery: ${value.toFixed(1)}%`; - } else if (label === 'Network') { - return `Network: ${value.toFixed(1)} Mbps`; - } else if (label === 'Disk') { - return `Disk: ${value.toFixed(1)}%`; - } - - return `${label}: ${value.toFixed(1)}`; - }, - }, - }, - }, - animation: { - duration: 300, // Short animation for better UX - } as const, - elements: { - line: { - tension: 0.3, - }, - }, - }; - - return ( -
- -
- ); - }, - [metricsHistory.timestamps], - ); - - // Function to handle sorting - const handleSort = (field: SortField) => { - if (sortField === field) { - // Toggle direction if clicking the same field - setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); - } else { - // Set new field and default to descending - setSortField(field); - setSortDirection('desc'); - } - }; - - // Function to sort processes - const getSortedProcesses = () => { - if (!metrics.processes) { - return []; - } - - return [...metrics.processes].sort((a, b) => { - let comparison = 0; - - switch (sortField) { - case 'name': - comparison = a.name.localeCompare(b.name); - break; - case 'pid': - comparison = a.pid - b.pid; - break; - case 'cpu': - comparison = a.cpu - b.cpu; - break; - case 'memory': - comparison = a.memory - b.memory; - break; - } - - return sortDirection === 'asc' ? comparison : -comparison; - }); - }; - - // If we're in an environment where the task manager won't work, show a message - if (isNotSupported) { - return ( -
-
-

System Monitoring Not Available

-

- System monitoring is not available in serverless environments like Cloudflare Pages, Netlify, or Vercel. These - platforms don't provide access to the underlying system resources. -

-
-

- Why is this disabled? -
- Serverless platforms execute your code in isolated environments without access to the server's operating - system metrics like CPU, memory, and disk usage. -

-

- System monitoring features will be available when running in: -

    -
  • Local development environment
  • -
  • Virtual Machines (VMs)
  • -
  • Dedicated servers
  • -
  • Docker containers (with proper permissions)
  • -
-

-
- - {/* Testing controls - only shown in development */} - {isDevelopment && ( -
-

Testing Controls

-

- These controls are only visible in development mode -

- -
- )} -
- ); - } - - return ( -
- {/* Summary Header */} -
-
-
CPU
-
- {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% -
-
-
-
Memory
-
- {Math.round(metrics.systemMemory?.percentage || 0)}% -
-
-
-
Disk
-
0 - ? metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length - : 0, - ), - )} - > - {metrics.disks && metrics.disks.length > 0 - ? Math.round(metrics.disks.reduce((total, disk) => total + disk.percentage, 0) / metrics.disks.length) - : 0} - % -
-
-
-
Network
-
{metrics.network.downlink.toFixed(1)} Mbps
-
-
- - {/* Memory Usage */} -
-

Memory Usage

-
- {/* System Physical Memory */} -
-
-
- System Memory -
-
-
- Shows your system's physical memory (RAM) usage. -
-
-
- - {Math.round(metrics.systemMemory?.percentage || 0)}% - -
- {renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb', memoryChartRef)} -
- Used: {formatBytes(metrics.systemMemory?.used || 0)} / {formatBytes(metrics.systemMemory?.total || 0)} -
-
- Free: {formatBytes(metrics.systemMemory?.free || 0)} -
-
- - {/* Swap Memory */} - {metrics.systemMemory?.swap && ( -
-
-
- Swap Memory -
-
-
- Virtual memory used when physical RAM is full. -
-
-
- - {Math.round(metrics.systemMemory.swap.percentage)}% - -
-
-
-
-
- Used: {formatBytes(metrics.systemMemory.swap.used)} / {formatBytes(metrics.systemMemory.swap.total)} -
-
- Free: {formatBytes(metrics.systemMemory.swap.free)} -
-
- )} -
-
- - {/* Disk Usage */} -
-

Disk Usage

- {metrics.disks && metrics.disks.length > 0 ? ( -
-
- System Disk - - {(metricsHistory.disk[metricsHistory.disk.length - 1] || 0).toFixed(1)}% - -
- {renderUsageGraph(metricsHistory.disk, 'Disk', '#8b5cf6', diskChartRef)} - - {/* Show only the main system disk (usually the first one) */} - {metrics.disks[0] && ( - <> -
-
-
-
-
Used: {formatBytes(metrics.disks[0].used)}
-
Free: {formatBytes(metrics.disks[0].available)}
-
Total: {formatBytes(metrics.disks[0].size)}
-
- - )} -
- ) : ( -
-
-

Disk information is not available

-

- This feature may not be supported in your environment -

-
- )} -
- - {/* Process Information */} -
-
-

Process Information

- -
-
- {metrics.processes && metrics.processes.length > 0 ? ( - <> - {/* CPU Usage Summary */} - {metrics.processes[0].name !== 'Unknown' && ( -
-
- CPU Usage - - {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% Total - -
-
-
- {metrics.processes.map((process, index) => { - return ( -
- ); - })} -
-
-
-
- System:{' '} - {metrics.processes.reduce((total, proc) => total + (proc.cpu < 10 ? proc.cpu : 0), 0).toFixed(1)}% -
-
- User:{' '} - {metrics.processes.reduce((total, proc) => total + (proc.cpu >= 10 ? proc.cpu : 0), 0).toFixed(1)} - % -
-
- Idle: {(100 - (metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0)).toFixed(1)}% -
-
-
- )} - -
- - - - - - - - - - - {getSortedProcesses().map((process, index) => ( - - - - - - - ))} - -
handleSort('name')} - > - Process {sortField === 'name' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('pid')} - > - PID {sortField === 'pid' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('cpu')} - > - CPU % {sortField === 'cpu' && (sortDirection === 'asc' ? '↑' : '↓')} - handleSort('memory')} - > - Memory {sortField === 'memory' && (sortDirection === 'asc' ? '↑' : '↓')} -
- {process.name} - {process.pid} -
-
-
-
- {process.cpu.toFixed(1)}% -
-
-
-
-
-
- {/* Calculate approximate MB based on percentage and total system memory */} - {metrics.systemMemory - ? `${formatBytes(metrics.systemMemory.total * (process.memory / 100))}` - : `${process.memory.toFixed(1)}%`} -
-
-
-
- {metrics.processes[0].error ? ( - -
- Error retrieving process information: {metrics.processes[0].error} - - ) : metrics.processes[0].name === 'Browser' ? ( - -
- Showing browser process information. System process information is not available in this - environment. - - ) : ( - Showing top {metrics.processes.length} processes by memory usage - )} -
- - ) : ( -
-
-

Process information is not available

-

- This feature may not be supported in your environment -

- -
- )} -
-
- - {/* CPU Usage Graph */} -
-

CPU Usage History

-
-
- System CPU - - {(metricsHistory.cpu[metricsHistory.cpu.length - 1] || 0).toFixed(1)}% - -
- {renderUsageGraph(metricsHistory.cpu, 'CPU', '#ef4444', cpuChartRef)} -
- Average: {(metricsHistory.cpu.reduce((a, b) => a + b, 0) / metricsHistory.cpu.length || 0).toFixed(1)}% -
-
- Peak: {Math.max(...metricsHistory.cpu).toFixed(1)}% -
-
-
- - {/* Network */} -
-

Network

-
-
-
- Connection - - {metrics.network.downlink.toFixed(1)} Mbps - -
- {renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b', networkChartRef)} -
- Type: {metrics.network.type} - {metrics.network.effectiveType && ` (${metrics.network.effectiveType})`} -
-
- Latency: {Math.round(metrics.network.latency.current)}ms - - (avg: {Math.round(metrics.network.latency.average)}ms) - -
-
- Min: {Math.round(metrics.network.latency.min)}ms / Max: {Math.round(metrics.network.latency.max)}ms -
- {metrics.network.uplink && ( -
- Uplink: {metrics.network.uplink.toFixed(1)} Mbps -
- )} -
-
-
- - {/* Battery */} - {metrics.battery && ( -
-

Battery

-
-
-
- Status -
- {metrics.battery.charging &&
} - 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', - )} - > - {Math.round(metrics.battery.level)}% - -
-
- {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e', batteryChartRef)} - {metrics.battery.timeRemaining && metrics.battery.timeRemaining !== Infinity && ( -
- {metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '} - {formatTime(metrics.battery.timeRemaining)} -
- )} -
-
-
- )} - - {/* Performance */} -
-

Performance

-
-
-
- Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s -
-
- DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s -
-
- TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s -
-
- Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)}) -
-
-
-
- - {/* Alerts */} - {alerts.length > 0 && ( -
-
- Recent Alerts - -
-
- {alerts.slice(-5).map((alert, index) => ( -
-
- {alert.message} - - {new Date(alert.timestamp).toLocaleTimeString()} - -
- ))} -
-
- )} -
- ); -}; - -export default React.memo(TaskManagerTab); - -// Helper function to format bytes -const formatBytes = (bytes: number): string => { - if (bytes === 0) { - return '0 B'; - } - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - const value = bytes / Math.pow(k, i); - - // Format with 2 decimal places for MB and larger units - const formattedValue = i >= 2 ? value.toFixed(2) : value.toFixed(0); - - return `${formattedValue} ${sizes[i]}`; -}; - -// Helper function to format time -const formatTime = (seconds: number): string => { - if (!isFinite(seconds) || seconds === 0) { - return 'Unknown'; - } - - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (hours > 0) { - return `${hours}h ${minutes}m`; - } - - return `${minutes}m`; -}; diff --git a/app/components/@settings/utils/animations.ts b/app/components/@settings/utils/animations.ts deleted file mode 100644 index 48d27e8..0000000 --- a/app/components/@settings/utils/animations.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Variants } from 'framer-motion'; - -export const fadeIn: Variants = { - initial: { opacity: 0 }, - animate: { opacity: 1 }, - exit: { opacity: 0 }, -}; - -export const slideIn: Variants = { - initial: { opacity: 0, y: 20 }, - animate: { opacity: 1, y: 0 }, - exit: { opacity: 0, y: -20 }, -}; - -export const scaleIn: Variants = { - initial: { opacity: 0, scale: 0.8 }, - animate: { opacity: 1, scale: 1 }, - exit: { opacity: 0, scale: 0.8 }, -}; - -export const tabAnimation: Variants = { - initial: { opacity: 0, scale: 0.8, y: 20 }, - animate: { opacity: 1, scale: 1, y: 0 }, - exit: { opacity: 0, scale: 0.8, y: -20 }, -}; - -export const overlayAnimation: Variants = { - initial: { opacity: 0 }, - animate: { opacity: 1 }, - exit: { opacity: 0 }, -}; - -export const modalAnimation: Variants = { - initial: { opacity: 0, scale: 0.95, y: 20 }, - animate: { opacity: 1, scale: 1, y: 0 }, - exit: { opacity: 0, scale: 0.95, y: 20 }, -}; - -export const transition = { - duration: 0.2, -}; diff --git a/app/components/@settings/utils/tab-helpers.ts b/app/components/@settings/utils/tab-helpers.ts index 7a55eca..990627c 100644 --- a/app/components/@settings/utils/tab-helpers.ts +++ b/app/components/@settings/utils/tab-helpers.ts @@ -1,9 +1,8 @@ -import type { TabType, TabVisibilityConfig } from '~/components/@settings/core/types'; +import type { TabVisibilityConfig } from '~/components/@settings/core/types'; import { DEFAULT_TAB_CONFIG } from '~/components/@settings/core/constants'; export const getVisibleTabs = ( - tabConfiguration: { userTabs: TabVisibilityConfig[]; developerTabs?: TabVisibilityConfig[] }, - isDeveloperMode: boolean, + tabConfiguration: { userTabs: TabVisibilityConfig[] }, notificationsEnabled: boolean, ): TabVisibilityConfig[] => { if (!tabConfiguration?.userTabs || !Array.isArray(tabConfiguration.userTabs)) { @@ -11,35 +10,6 @@ export const getVisibleTabs = ( return DEFAULT_TAB_CONFIG as TabVisibilityConfig[]; } - // In developer mode, show ALL tabs without restrictions - if (isDeveloperMode) { - // Combine all unique tabs from both user and developer configurations - const allTabs = new Set([ - ...DEFAULT_TAB_CONFIG.map((tab) => tab.id), - ...tabConfiguration.userTabs.map((tab) => tab.id), - ...(tabConfiguration.developerTabs || []).map((tab) => tab.id), - 'task-manager' as TabType, // Always include task-manager in developer mode - ]); - - // Create a complete tab list with all tabs visible - const devTabs = Array.from(allTabs).map((tabId) => { - // Try to find existing configuration for this tab - const existingTab = - tabConfiguration.developerTabs?.find((t) => t.id === tabId) || - tabConfiguration.userTabs?.find((t) => t.id === tabId) || - DEFAULT_TAB_CONFIG.find((t) => t.id === tabId); - - return { - id: tabId as TabType, - visible: true, - window: 'developer' as const, - order: existingTab?.order || DEFAULT_TAB_CONFIG.findIndex((t) => t.id === tabId), - } as TabVisibilityConfig; - }); - - return devTabs.sort((a, b) => a.order - b.order); - } - // In user mode, only show visible user tabs return tabConfiguration.userTabs .filter((tab) => { @@ -53,11 +23,6 @@ export const getVisibleTabs = ( return false; } - // Always show task-manager in user mode if it's configured as visible - if (tab.id === 'task-manager') { - return tab.visible; - } - // Only show tabs that are explicitly visible and assigned to the user window return tab.visible && tab.window === 'user'; }) diff --git a/app/components/ui/GlowingEffect.tsx b/app/components/ui/GlowingEffect.tsx new file mode 100644 index 0000000..5c8a3e1 --- /dev/null +++ b/app/components/ui/GlowingEffect.tsx @@ -0,0 +1,192 @@ +import { memo, useCallback, useEffect, useRef } from 'react'; +import { cn } from '~/utils/cn'; +import { animate } from 'framer-motion'; + +interface GlowingEffectProps { + blur?: number; + inactiveZone?: number; + proximity?: number; + spread?: number; + variant?: 'default' | 'white'; + glow?: boolean; + className?: string; + disabled?: boolean; + movementDuration?: number; + borderWidth?: number; +} + +const GlowingEffect = memo( + ({ + blur = 0, + inactiveZone = 0.7, + proximity = 0, + spread = 20, + variant = 'default', + glow = false, + className, + movementDuration = 2, + borderWidth = 1, + disabled = true, + }: GlowingEffectProps) => { + const containerRef = useRef(null); + const lastPosition = useRef({ x: 0, y: 0 }); + const animationFrameRef = useRef(0); + + const handleMove = useCallback( + (e?: MouseEvent | { x: number; y: number }) => { + if (!containerRef.current) { + return; + } + + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + + animationFrameRef.current = requestAnimationFrame(() => { + const element = containerRef.current; + + if (!element) { + return; + } + + const { left, top, width, height } = element.getBoundingClientRect(); + const mouseX = e?.x ?? lastPosition.current.x; + const mouseY = e?.y ?? lastPosition.current.y; + + if (e) { + lastPosition.current = { x: mouseX, y: mouseY }; + } + + const center = [left + width * 0.5, top + height * 0.5]; + const distanceFromCenter = Math.hypot(mouseX - center[0], mouseY - center[1]); + const inactiveRadius = 0.5 * Math.min(width, height) * inactiveZone; + + if (distanceFromCenter < inactiveRadius) { + element.style.setProperty('--active', '0'); + return; + } + + const isActive = + mouseX > left - proximity && + mouseX < left + width + proximity && + mouseY > top - proximity && + mouseY < top + height + proximity; + + element.style.setProperty('--active', isActive ? '1' : '0'); + + if (!isActive) { + return; + } + + const currentAngle = parseFloat(element.style.getPropertyValue('--start')) || 0; + const targetAngle = (180 * Math.atan2(mouseY - center[1], mouseX - center[0])) / Math.PI + 90; + + const angleDiff = ((targetAngle - currentAngle + 180) % 360) - 180; + const newAngle = currentAngle + angleDiff; + + animate(currentAngle, newAngle, { + duration: movementDuration, + ease: [0.16, 1, 0.3, 1], + onUpdate: (value) => { + element.style.setProperty('--start', String(value)); + }, + }); + }); + }, + [inactiveZone, proximity, movementDuration], + ); + + useEffect(() => { + if (disabled) { + return undefined; + } + + const handleScroll = () => handleMove(); + const handlePointerMove = (e: PointerEvent) => handleMove(e); + + window.addEventListener('scroll', handleScroll, { passive: true }); + document.body.addEventListener('pointermove', handlePointerMove, { + passive: true, + }); + + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + + window.removeEventListener('scroll', handleScroll); + document.body.removeEventListener('pointermove', handlePointerMove); + }; + }, [handleMove, disabled]); + + return ( + <> +