@moontra/moonui-pro 2.20.2 → 2.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -3
- package/plugin/index.d.ts +86 -0
- package/plugin/index.js +308 -0
- package/scripts/postinstall.js +176 -23
- package/src/components/advanced-chart/index.tsx +0 -1246
- package/src/components/advanced-forms/index.tsx +0 -585
- package/src/components/animated-button/index.tsx +0 -385
- package/src/components/calendar/event-dialog.tsx +0 -377
- package/src/components/calendar/index.tsx +0 -1220
- package/src/components/calendar-pro/index.tsx +0 -1697
- package/src/components/color-picker/index.tsx +0 -432
- package/src/components/credit-card-input/index.tsx +0 -406
- package/src/components/dashboard/dashboard-grid.tsx +0 -480
- package/src/components/dashboard/demo.tsx +0 -425
- package/src/components/dashboard/index.tsx +0 -1046
- package/src/components/dashboard/time-range-picker.tsx +0 -336
- package/src/components/dashboard/types.ts +0 -225
- package/src/components/dashboard/widgets/activity-feed.tsx +0 -349
- package/src/components/dashboard/widgets/chart-widget.tsx +0 -418
- package/src/components/dashboard/widgets/comparison-widget.tsx +0 -177
- package/src/components/dashboard/widgets/index.ts +0 -5
- package/src/components/dashboard/widgets/metric-card.tsx +0 -363
- package/src/components/dashboard/widgets/progress-widget.tsx +0 -113
- package/src/components/data-table/data-table-bulk-actions.tsx +0 -204
- package/src/components/data-table/data-table-column-toggle.tsx +0 -169
- package/src/components/data-table/data-table-export.ts +0 -156
- package/src/components/data-table/data-table-filter-drawer.tsx +0 -448
- package/src/components/data-table/index.tsx +0 -845
- package/src/components/draggable-list/index.tsx +0 -100
- package/src/components/error-boundary/index.tsx +0 -232
- package/src/components/file-upload/index.tsx +0 -1660
- package/src/components/floating-action-button/index.tsx +0 -206
- package/src/components/form-wizard/form-wizard-context.tsx +0 -335
- package/src/components/form-wizard/form-wizard-navigation.tsx +0 -118
- package/src/components/form-wizard/form-wizard-progress.tsx +0 -329
- package/src/components/form-wizard/form-wizard-step.tsx +0 -111
- package/src/components/form-wizard/index.tsx +0 -102
- package/src/components/form-wizard/types.ts +0 -77
- package/src/components/gesture-drawer/index.tsx +0 -551
- package/src/components/github-stars/github-api.ts +0 -426
- package/src/components/github-stars/hooks.ts +0 -517
- package/src/components/github-stars/index.tsx +0 -375
- package/src/components/github-stars/types.ts +0 -148
- package/src/components/github-stars/variants.tsx +0 -515
- package/src/components/health-check/index.tsx +0 -439
- package/src/components/hover-card-3d/index.tsx +0 -529
- package/src/components/index.ts +0 -130
- package/src/components/internal/index.ts +0 -78
- package/src/components/kanban/add-card-modal.tsx +0 -502
- package/src/components/kanban/card-detail-modal.tsx +0 -761
- package/src/components/kanban/index.ts +0 -13
- package/src/components/kanban/kanban.tsx +0 -1689
- package/src/components/kanban/types.ts +0 -168
- package/src/components/lazy-component/index.tsx +0 -823
- package/src/components/license-error/index.tsx +0 -31
- package/src/components/magnetic-button/index.tsx +0 -216
- package/src/components/memory-efficient-data/index.tsx +0 -1018
- package/src/components/moonui-quiz-form/index.tsx +0 -817
- package/src/components/navbar/index.tsx +0 -781
- package/src/components/optimized-image/index.tsx +0 -425
- package/src/components/performance-debugger/index.tsx +0 -613
- package/src/components/performance-monitor/index.tsx +0 -808
- package/src/components/phone-number-input/index.tsx +0 -343
- package/src/components/phone-number-input/phone-number-input-simple.tsx +0 -167
- package/src/components/pinch-zoom/index.tsx +0 -566
- package/src/components/quiz-form/index.tsx +0 -479
- package/src/components/rich-text-editor/index.tsx +0 -2322
- package/src/components/rich-text-editor/slash-commands-extension.ts +0 -230
- package/src/components/rich-text-editor/slash-commands.css +0 -35
- package/src/components/rich-text-editor/table-styles.css +0 -65
- package/src/components/sidebar/index.tsx +0 -884
- package/src/components/spotlight-card/index.tsx +0 -191
- package/src/components/swipeable-card/index.tsx +0 -100
- package/src/components/timeline/index.tsx +0 -1183
- package/src/components/ui/accordion.tsx +0 -581
- package/src/components/ui/alert-dialog.tsx +0 -141
- package/src/components/ui/alert.tsx +0 -141
- package/src/components/ui/aspect-ratio.tsx +0 -245
- package/src/components/ui/avatar.tsx +0 -155
- package/src/components/ui/badge.tsx +0 -230
- package/src/components/ui/breadcrumb.tsx +0 -216
- package/src/components/ui/button.tsx +0 -228
- package/src/components/ui/calendar.tsx +0 -387
- package/src/components/ui/card.tsx +0 -216
- package/src/components/ui/checkbox.tsx +0 -259
- package/src/components/ui/collapsible.tsx +0 -631
- package/src/components/ui/color-picker.tsx +0 -97
- package/src/components/ui/command.tsx +0 -948
- package/src/components/ui/dialog.tsx +0 -752
- package/src/components/ui/dropdown-menu.tsx +0 -706
- package/src/components/ui/gesture-drawer.tsx +0 -11
- package/src/components/ui/hover-card.tsx +0 -29
- package/src/components/ui/index.ts +0 -222
- package/src/components/ui/input.tsx +0 -224
- package/src/components/ui/label.tsx +0 -29
- package/src/components/ui/lightbox.tsx +0 -606
- package/src/components/ui/magnetic-button.tsx +0 -129
- package/src/components/ui/media-gallery.tsx +0 -611
- package/src/components/ui/navigation-menu.tsx +0 -130
- package/src/components/ui/pagination.tsx +0 -125
- package/src/components/ui/popover.tsx +0 -185
- package/src/components/ui/progress.tsx +0 -30
- package/src/components/ui/radio-group.tsx +0 -257
- package/src/components/ui/scroll-area.tsx +0 -47
- package/src/components/ui/select.tsx +0 -378
- package/src/components/ui/separator.tsx +0 -145
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/skeleton.tsx +0 -20
- package/src/components/ui/slider.tsx +0 -354
- package/src/components/ui/spotlight-card.tsx +0 -119
- package/src/components/ui/switch.tsx +0 -86
- package/src/components/ui/table.tsx +0 -331
- package/src/components/ui/tabs-pro.tsx +0 -542
- package/src/components/ui/tabs.tsx +0 -54
- package/src/components/ui/textarea.tsx +0 -28
- package/src/components/ui/toast.tsx +0 -317
- package/src/components/ui/toggle.tsx +0 -119
- package/src/components/ui/tooltip.tsx +0 -151
- package/src/components/virtual-list/index.tsx +0 -668
- package/src/hooks/use-chart.ts +0 -205
- package/src/hooks/use-data-table.ts +0 -182
- package/src/hooks/use-docs-pro-access.ts +0 -13
- package/src/hooks/use-license-check.ts +0 -65
- package/src/hooks/use-subscription.ts +0 -19
- package/src/hooks/use-toast.ts +0 -15
- package/src/index.ts +0 -22
- package/src/lib/ai-providers.ts +0 -377
- package/src/lib/component-metadata.ts +0 -18
- package/src/lib/micro-interactions.ts +0 -255
- package/src/lib/paddle.ts +0 -17
- package/src/lib/utils.ts +0 -6
- package/src/patterns/login-form/index.tsx +0 -276
- package/src/patterns/login-form/types.ts +0 -67
- package/src/setupTests.ts +0 -41
- package/src/styles/advanced-chart.css +0 -239
- package/src/styles/calendar.css +0 -35
- package/src/styles/design-system.css +0 -363
- package/src/styles/index.css +0 -681
- package/src/styles/tailwind.css +0 -7
- package/src/styles/tokens.css +0 -455
- package/src/types/next-auth.d.ts +0 -21
- package/src/use-intersection-observer.tsx +0 -154
- package/src/use-local-storage.tsx +0 -71
- package/src/use-paddle.ts +0 -138
- package/src/use-performance-optimizer.ts +0 -389
- package/src/use-pro-access.ts +0 -141
- package/src/use-scroll-animation.ts +0 -219
- package/src/use-subscription.ts +0 -37
- package/src/use-toast.ts +0 -32
- package/src/utils/chart-helpers.ts +0 -357
- package/src/utils/cn.ts +0 -6
- package/src/utils/data-processing.ts +0 -151
- package/src/utils/license-validator.tsx +0 -183
|
@@ -1,808 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React, { useState, useEffect, useRef, useCallback } from "react"
|
|
4
|
-
import { motion, AnimatePresence } from "framer-motion"
|
|
5
|
-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
|
|
6
|
-
import { Button } from "../ui/button"
|
|
7
|
-
import { MoonUIBadgePro as Badge } from "../ui/badge"
|
|
8
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
|
|
9
|
-
import { cn } from "../../lib/utils"
|
|
10
|
-
import {
|
|
11
|
-
Activity,
|
|
12
|
-
Monitor,
|
|
13
|
-
Cpu,
|
|
14
|
-
MemoryStick,
|
|
15
|
-
Network,
|
|
16
|
-
HardDrive,
|
|
17
|
-
Zap,
|
|
18
|
-
Eye,
|
|
19
|
-
EyeOff,
|
|
20
|
-
BarChart3,
|
|
21
|
-
TrendingUp,
|
|
22
|
-
TrendingDown,
|
|
23
|
-
AlertTriangle,
|
|
24
|
-
CheckCircle,
|
|
25
|
-
RefreshCw,
|
|
26
|
-
Download,
|
|
27
|
-
Lock,
|
|
28
|
-
Sparkles,
|
|
29
|
-
Timer,
|
|
30
|
-
Gauge
|
|
31
|
-
} from "lucide-react"
|
|
32
|
-
import { useSubscription } from "../../hooks/use-subscription"
|
|
33
|
-
|
|
34
|
-
interface PerformanceMetrics {
|
|
35
|
-
cpu: {
|
|
36
|
-
usage: number
|
|
37
|
-
cores: number
|
|
38
|
-
temperature?: number
|
|
39
|
-
}
|
|
40
|
-
memory: {
|
|
41
|
-
used: number
|
|
42
|
-
total: number
|
|
43
|
-
available: number
|
|
44
|
-
percentage: number
|
|
45
|
-
}
|
|
46
|
-
disk: {
|
|
47
|
-
used: number
|
|
48
|
-
total: number
|
|
49
|
-
free: number
|
|
50
|
-
percentage: number
|
|
51
|
-
}
|
|
52
|
-
network: {
|
|
53
|
-
download: number
|
|
54
|
-
upload: number
|
|
55
|
-
latency: number
|
|
56
|
-
}
|
|
57
|
-
system: {
|
|
58
|
-
uptime: number
|
|
59
|
-
loadAverage: number[]
|
|
60
|
-
processes: number
|
|
61
|
-
}
|
|
62
|
-
browser?: {
|
|
63
|
-
jsHeapUsed: number
|
|
64
|
-
jsHeapTotal: number
|
|
65
|
-
jsHeapLimit: number
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface PerformanceAlert {
|
|
70
|
-
id: string
|
|
71
|
-
type: "warning" | "critical" | "info"
|
|
72
|
-
metric: string
|
|
73
|
-
message: string
|
|
74
|
-
value: number
|
|
75
|
-
threshold: number
|
|
76
|
-
timestamp: Date
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface PerformanceMonitorProps {
|
|
80
|
-
autoRefresh?: boolean
|
|
81
|
-
refreshInterval?: number
|
|
82
|
-
showAlerts?: boolean
|
|
83
|
-
alertThresholds?: {
|
|
84
|
-
cpu: number
|
|
85
|
-
memory: number
|
|
86
|
-
disk: number
|
|
87
|
-
network: number
|
|
88
|
-
}
|
|
89
|
-
showCharts?: boolean
|
|
90
|
-
chartDuration?: number
|
|
91
|
-
maxDataPoints?: number
|
|
92
|
-
onAlert?: (alert: PerformanceAlert) => void
|
|
93
|
-
onMetricsUpdate?: (metrics: PerformanceMetrics) => void
|
|
94
|
-
className?: string
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const PerformanceMonitorInternal: React.FC<PerformanceMonitorProps> = ({
|
|
98
|
-
autoRefresh = true,
|
|
99
|
-
refreshInterval = 5000,
|
|
100
|
-
showAlerts = true,
|
|
101
|
-
alertThresholds = {
|
|
102
|
-
cpu: 80,
|
|
103
|
-
memory: 85,
|
|
104
|
-
disk: 90,
|
|
105
|
-
network: 1000
|
|
106
|
-
},
|
|
107
|
-
showCharts = true,
|
|
108
|
-
chartDuration = 300000, // 5 minutes
|
|
109
|
-
maxDataPoints = 60,
|
|
110
|
-
onAlert,
|
|
111
|
-
onMetricsUpdate,
|
|
112
|
-
className
|
|
113
|
-
}) => {
|
|
114
|
-
const [metrics, setMetrics] = useState<PerformanceMetrics | null>(null)
|
|
115
|
-
const [history, setHistory] = useState<{ timestamp: number; metrics: PerformanceMetrics }[]>([])
|
|
116
|
-
const [alerts, setAlerts] = useState<PerformanceAlert[]>([])
|
|
117
|
-
const [isVisible, setIsVisible] = useState(true)
|
|
118
|
-
const [isRefreshing, setIsRefreshing] = useState(false)
|
|
119
|
-
const intervalRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
120
|
-
|
|
121
|
-
const generateMockMetrics = useCallback((): PerformanceMetrics => {
|
|
122
|
-
const baseMetrics = metrics || {
|
|
123
|
-
cpu: { usage: 25, cores: 8 },
|
|
124
|
-
memory: { used: 4.2, total: 16, available: 11.8, percentage: 26 },
|
|
125
|
-
disk: { used: 250, total: 500, free: 250, percentage: 50 },
|
|
126
|
-
network: { download: 50, upload: 20, latency: 25 },
|
|
127
|
-
system: { uptime: 86400, loadAverage: [1.2, 1.5, 1.8], processes: 120 }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Simulate realistic variations
|
|
131
|
-
const variance = 0.1
|
|
132
|
-
return {
|
|
133
|
-
cpu: {
|
|
134
|
-
usage: Math.max(0, Math.min(100, baseMetrics.cpu.usage + (Math.random() - 0.5) * 20)),
|
|
135
|
-
cores: baseMetrics.cpu.cores,
|
|
136
|
-
temperature: 45 + Math.random() * 15
|
|
137
|
-
},
|
|
138
|
-
memory: {
|
|
139
|
-
used: Math.max(0, baseMetrics.memory.used + (Math.random() - 0.5) * 2),
|
|
140
|
-
total: baseMetrics.memory.total,
|
|
141
|
-
available: baseMetrics.memory.total - baseMetrics.memory.used,
|
|
142
|
-
percentage: Math.max(0, Math.min(100, baseMetrics.memory.percentage + (Math.random() - 0.5) * 10))
|
|
143
|
-
},
|
|
144
|
-
disk: {
|
|
145
|
-
used: baseMetrics.disk.used + (Math.random() - 0.5) * 10,
|
|
146
|
-
total: baseMetrics.disk.total,
|
|
147
|
-
free: baseMetrics.disk.total - baseMetrics.disk.used,
|
|
148
|
-
percentage: Math.max(0, Math.min(100, baseMetrics.disk.percentage + (Math.random() - 0.5) * 5))
|
|
149
|
-
},
|
|
150
|
-
network: {
|
|
151
|
-
download: Math.max(0, baseMetrics.network.download + (Math.random() - 0.5) * 30),
|
|
152
|
-
upload: Math.max(0, baseMetrics.network.upload + (Math.random() - 0.5) * 15),
|
|
153
|
-
latency: Math.max(1, baseMetrics.network.latency + (Math.random() - 0.5) * 10)
|
|
154
|
-
},
|
|
155
|
-
system: {
|
|
156
|
-
uptime: baseMetrics.system.uptime + refreshInterval / 1000,
|
|
157
|
-
loadAverage: baseMetrics.system.loadAverage.map(load =>
|
|
158
|
-
Math.max(0, load + (Math.random() - 0.5) * 0.2)
|
|
159
|
-
),
|
|
160
|
-
processes: Math.max(50, baseMetrics.system.processes + Math.floor((Math.random() - 0.5) * 10))
|
|
161
|
-
},
|
|
162
|
-
browser: typeof window !== 'undefined' && 'memory' in performance ? {
|
|
163
|
-
jsHeapUsed: (performance as any).memory.usedJSHeapSize / 1024 / 1024,
|
|
164
|
-
jsHeapTotal: (performance as any).memory.totalJSHeapSize / 1024 / 1024,
|
|
165
|
-
jsHeapLimit: (performance as any).memory.jsHeapSizeLimit / 1024 / 1024
|
|
166
|
-
} : undefined
|
|
167
|
-
}
|
|
168
|
-
}, [metrics, refreshInterval])
|
|
169
|
-
|
|
170
|
-
const checkAlerts = useCallback((newMetrics: PerformanceMetrics) => {
|
|
171
|
-
const newAlerts: PerformanceAlert[] = []
|
|
172
|
-
const now = new Date()
|
|
173
|
-
|
|
174
|
-
// CPU Alert
|
|
175
|
-
if (newMetrics.cpu.usage > alertThresholds.cpu) {
|
|
176
|
-
newAlerts.push({
|
|
177
|
-
id: `cpu-${Date.now()}`,
|
|
178
|
-
type: newMetrics.cpu.usage > 95 ? "critical" : "warning",
|
|
179
|
-
metric: "CPU",
|
|
180
|
-
message: `CPU usage is ${newMetrics.cpu.usage.toFixed(1)}%`,
|
|
181
|
-
value: newMetrics.cpu.usage,
|
|
182
|
-
threshold: alertThresholds.cpu,
|
|
183
|
-
timestamp: now
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Memory Alert
|
|
188
|
-
if (newMetrics.memory.percentage > alertThresholds.memory) {
|
|
189
|
-
newAlerts.push({
|
|
190
|
-
id: `memory-${Date.now()}`,
|
|
191
|
-
type: newMetrics.memory.percentage > 95 ? "critical" : "warning",
|
|
192
|
-
metric: "Memory",
|
|
193
|
-
message: `Memory usage is ${newMetrics.memory.percentage.toFixed(1)}%`,
|
|
194
|
-
value: newMetrics.memory.percentage,
|
|
195
|
-
threshold: alertThresholds.memory,
|
|
196
|
-
timestamp: now
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Disk Alert
|
|
201
|
-
if (newMetrics.disk.percentage > alertThresholds.disk) {
|
|
202
|
-
newAlerts.push({
|
|
203
|
-
id: `disk-${Date.now()}`,
|
|
204
|
-
type: newMetrics.disk.percentage > 98 ? "critical" : "warning",
|
|
205
|
-
metric: "Disk",
|
|
206
|
-
message: `Disk usage is ${newMetrics.disk.percentage.toFixed(1)}%`,
|
|
207
|
-
value: newMetrics.disk.percentage,
|
|
208
|
-
threshold: alertThresholds.disk,
|
|
209
|
-
timestamp: now
|
|
210
|
-
})
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Network Latency Alert
|
|
214
|
-
if (newMetrics.network.latency > alertThresholds.network) {
|
|
215
|
-
newAlerts.push({
|
|
216
|
-
id: `network-${Date.now()}`,
|
|
217
|
-
type: "warning",
|
|
218
|
-
metric: "Network",
|
|
219
|
-
message: `High network latency: ${newMetrics.network.latency.toFixed(0)}ms`,
|
|
220
|
-
value: newMetrics.network.latency,
|
|
221
|
-
threshold: alertThresholds.network,
|
|
222
|
-
timestamp: now
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (newAlerts.length > 0) {
|
|
227
|
-
setAlerts(prev => [...newAlerts, ...prev].slice(0, 20))
|
|
228
|
-
newAlerts.forEach(alert => onAlert?.(alert))
|
|
229
|
-
}
|
|
230
|
-
}, [alertThresholds, onAlert])
|
|
231
|
-
|
|
232
|
-
const updateMetrics = useCallback(async () => {
|
|
233
|
-
setIsRefreshing(true)
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
const newMetrics = generateMockMetrics()
|
|
237
|
-
setMetrics(newMetrics)
|
|
238
|
-
|
|
239
|
-
// Update history
|
|
240
|
-
setHistory(prev => {
|
|
241
|
-
const newHistory = [
|
|
242
|
-
{ timestamp: Date.now(), metrics: newMetrics },
|
|
243
|
-
...prev.slice(0, maxDataPoints - 1)
|
|
244
|
-
]
|
|
245
|
-
return newHistory
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// Check for alerts
|
|
249
|
-
if (showAlerts) {
|
|
250
|
-
checkAlerts(newMetrics)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
onMetricsUpdate?.(newMetrics)
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error("Failed to update metrics:", error)
|
|
256
|
-
} finally {
|
|
257
|
-
setIsRefreshing(false)
|
|
258
|
-
}
|
|
259
|
-
}, [generateMockMetrics, maxDataPoints, showAlerts, checkAlerts, onMetricsUpdate])
|
|
260
|
-
|
|
261
|
-
// Initial load
|
|
262
|
-
useEffect(() => {
|
|
263
|
-
updateMetrics()
|
|
264
|
-
}, [])
|
|
265
|
-
|
|
266
|
-
// Auto refresh
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
if (!autoRefresh) return
|
|
269
|
-
|
|
270
|
-
intervalRef.current = setInterval(updateMetrics, refreshInterval)
|
|
271
|
-
return () => {
|
|
272
|
-
if (intervalRef.current) {
|
|
273
|
-
clearInterval(intervalRef.current)
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}, [autoRefresh, refreshInterval, updateMetrics])
|
|
277
|
-
|
|
278
|
-
const formatBytes = (bytes: number, decimals = 1): string => {
|
|
279
|
-
if (bytes === 0) return '0 B'
|
|
280
|
-
const k = 1024
|
|
281
|
-
const dm = decimals < 0 ? 0 : decimals
|
|
282
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
283
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
284
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const formatUptime = (seconds: number): string => {
|
|
288
|
-
const days = Math.floor(seconds / 86400)
|
|
289
|
-
const hours = Math.floor((seconds % 86400) / 3600)
|
|
290
|
-
const minutes = Math.floor((seconds % 3600) / 60)
|
|
291
|
-
|
|
292
|
-
if (days > 0) return `${days}d ${hours}h ${minutes}m`
|
|
293
|
-
if (hours > 0) return `${hours}h ${minutes}m`
|
|
294
|
-
return `${minutes}m`
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const getStatusColor = (percentage: number, thresholds = [70, 85]): string => {
|
|
298
|
-
if (percentage >= thresholds[1]) return "text-red-500"
|
|
299
|
-
if (percentage >= thresholds[0]) return "text-yellow-500"
|
|
300
|
-
return "text-green-500"
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const exportMetrics = () => {
|
|
304
|
-
const data = {
|
|
305
|
-
timestamp: new Date().toISOString(),
|
|
306
|
-
currentMetrics: metrics,
|
|
307
|
-
history: history.slice(0, 20),
|
|
308
|
-
alerts: alerts.slice(0, 10)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
|
312
|
-
type: 'application/json'
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
const url = URL.createObjectURL(blob)
|
|
316
|
-
const link = document.createElement('a')
|
|
317
|
-
link.href = url
|
|
318
|
-
link.download = `performance-metrics-${Date.now()}.json`
|
|
319
|
-
document.body.appendChild(link)
|
|
320
|
-
link.click()
|
|
321
|
-
document.body.removeChild(link)
|
|
322
|
-
URL.revokeObjectURL(url)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (!isVisible) {
|
|
326
|
-
return (
|
|
327
|
-
<Button
|
|
328
|
-
variant="outline"
|
|
329
|
-
size="sm"
|
|
330
|
-
onClick={() => setIsVisible(true)}
|
|
331
|
-
className="fixed bottom-4 left-4 z-50"
|
|
332
|
-
>
|
|
333
|
-
<Monitor className="h-4 w-4 mr-2" />
|
|
334
|
-
Performance
|
|
335
|
-
</Button>
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if (!metrics) {
|
|
340
|
-
return (
|
|
341
|
-
<Card className={cn("w-full max-w-6xl", className)}>
|
|
342
|
-
<CardContent className="p-6">
|
|
343
|
-
<div className="text-center">
|
|
344
|
-
<Activity className="h-12 w-12 animate-pulse mx-auto mb-4 text-muted-foreground" />
|
|
345
|
-
<p className="text-muted-foreground">Loading performance metrics...</p>
|
|
346
|
-
</div>
|
|
347
|
-
</CardContent>
|
|
348
|
-
</Card>
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return (
|
|
353
|
-
<Card className={cn("w-full max-w-6xl", className)}>
|
|
354
|
-
<CardHeader className="pb-6">
|
|
355
|
-
<div className="flex items-center justify-between">
|
|
356
|
-
<div>
|
|
357
|
-
<CardTitle className="flex items-center gap-2">
|
|
358
|
-
<Monitor className="h-5 w-5" />
|
|
359
|
-
Performance Monitor
|
|
360
|
-
</CardTitle>
|
|
361
|
-
<CardDescription>
|
|
362
|
-
Real-time system performance monitoring and alerts
|
|
363
|
-
</CardDescription>
|
|
364
|
-
</div>
|
|
365
|
-
|
|
366
|
-
<div className="flex items-center gap-2">
|
|
367
|
-
<div className={cn(
|
|
368
|
-
"inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs font-medium",
|
|
369
|
-
autoRefresh
|
|
370
|
-
? "border border-border bg-background text-foreground"
|
|
371
|
-
: "bg-secondary text-secondary-foreground"
|
|
372
|
-
)}>
|
|
373
|
-
{autoRefresh ? (
|
|
374
|
-
<Activity className="h-3 w-3 animate-pulse" />
|
|
375
|
-
) : (
|
|
376
|
-
<Timer className="h-3 w-3" />
|
|
377
|
-
)}
|
|
378
|
-
<span>{autoRefresh ? "Live" : "Paused"}</span>
|
|
379
|
-
</div>
|
|
380
|
-
|
|
381
|
-
{showAlerts && alerts.length > 0 && (
|
|
382
|
-
<div className="inline-flex items-center gap-1 rounded-md bg-destructive px-2 py-0.5 text-xs font-medium text-destructive-foreground">
|
|
383
|
-
<AlertTriangle className="h-3 w-3" />
|
|
384
|
-
<span>{alerts.length}</span>
|
|
385
|
-
</div>
|
|
386
|
-
)}
|
|
387
|
-
|
|
388
|
-
<div className="flex items-center gap-1">
|
|
389
|
-
<Button
|
|
390
|
-
variant="outline"
|
|
391
|
-
size="icon"
|
|
392
|
-
className="h-7 w-7"
|
|
393
|
-
onClick={updateMetrics}
|
|
394
|
-
disabled={isRefreshing}
|
|
395
|
-
>
|
|
396
|
-
<RefreshCw className={cn("h-3.5 w-3.5", isRefreshing && "animate-spin")} />
|
|
397
|
-
</Button>
|
|
398
|
-
|
|
399
|
-
<Button
|
|
400
|
-
variant="outline"
|
|
401
|
-
size="icon"
|
|
402
|
-
className="h-7 w-7"
|
|
403
|
-
onClick={exportMetrics}
|
|
404
|
-
>
|
|
405
|
-
<Download className="h-3.5 w-3.5" />
|
|
406
|
-
</Button>
|
|
407
|
-
|
|
408
|
-
<Button
|
|
409
|
-
variant="ghost"
|
|
410
|
-
size="icon"
|
|
411
|
-
className="h-7 w-7"
|
|
412
|
-
onClick={() => setIsVisible(false)}
|
|
413
|
-
>
|
|
414
|
-
<EyeOff className="h-3.5 w-3.5" />
|
|
415
|
-
</Button>
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
</CardHeader>
|
|
420
|
-
|
|
421
|
-
<CardContent>
|
|
422
|
-
<Tabs defaultValue="overview" className="w-full mt-2">
|
|
423
|
-
<TabsList className="grid w-full grid-cols-4">
|
|
424
|
-
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
425
|
-
<TabsTrigger value="details">Details</TabsTrigger>
|
|
426
|
-
<TabsTrigger value="alerts">Alerts</TabsTrigger>
|
|
427
|
-
<TabsTrigger value="history">History</TabsTrigger>
|
|
428
|
-
</TabsList>
|
|
429
|
-
|
|
430
|
-
<TabsContent value="overview" className="space-y-6">
|
|
431
|
-
{/* Key Metrics Grid */}
|
|
432
|
-
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
433
|
-
<Card>
|
|
434
|
-
<CardContent className="p-4">
|
|
435
|
-
<div className="flex items-center justify-between">
|
|
436
|
-
<div className="flex items-center gap-2">
|
|
437
|
-
<Cpu className="h-4 w-4 text-blue-500" />
|
|
438
|
-
<span className="text-sm font-medium">CPU</span>
|
|
439
|
-
</div>
|
|
440
|
-
<span className={cn("font-bold", getStatusColor(metrics.cpu.usage))}>
|
|
441
|
-
{metrics.cpu.usage.toFixed(1)}%
|
|
442
|
-
</span>
|
|
443
|
-
</div>
|
|
444
|
-
<div className="h-2 mt-2 bg-secondary rounded-full overflow-hidden">
|
|
445
|
-
<div
|
|
446
|
-
className="h-full bg-primary transition-all duration-300 ease-out"
|
|
447
|
-
style={{ width: `${metrics.cpu.usage}%` }}
|
|
448
|
-
/>
|
|
449
|
-
</div>
|
|
450
|
-
<div className="text-xs text-muted-foreground mt-1">
|
|
451
|
-
{metrics.cpu.cores} cores {metrics.cpu.temperature && `• ${metrics.cpu.temperature.toFixed(0)}°C`}
|
|
452
|
-
</div>
|
|
453
|
-
</CardContent>
|
|
454
|
-
</Card>
|
|
455
|
-
|
|
456
|
-
<Card>
|
|
457
|
-
<CardContent className="p-4">
|
|
458
|
-
<div className="flex items-center justify-between">
|
|
459
|
-
<div className="flex items-center gap-2">
|
|
460
|
-
<MemoryStick className="h-4 w-4 text-green-500" />
|
|
461
|
-
<span className="text-sm font-medium">Memory</span>
|
|
462
|
-
</div>
|
|
463
|
-
<span className={cn("font-bold", getStatusColor(metrics.memory.percentage))}>
|
|
464
|
-
{metrics.memory.percentage.toFixed(1)}%
|
|
465
|
-
</span>
|
|
466
|
-
</div>
|
|
467
|
-
<div className="h-2 mt-2 bg-secondary rounded-full overflow-hidden">
|
|
468
|
-
<div
|
|
469
|
-
className="h-full bg-primary transition-all duration-300 ease-out"
|
|
470
|
-
style={{ width: `${metrics.memory.percentage}%` }}
|
|
471
|
-
/>
|
|
472
|
-
</div>
|
|
473
|
-
<div className="text-xs text-muted-foreground mt-1">
|
|
474
|
-
{formatBytes(metrics.memory.used * 1024 * 1024 * 1024)} / {formatBytes(metrics.memory.total * 1024 * 1024 * 1024)}
|
|
475
|
-
</div>
|
|
476
|
-
</CardContent>
|
|
477
|
-
</Card>
|
|
478
|
-
|
|
479
|
-
<Card>
|
|
480
|
-
<CardContent className="p-4">
|
|
481
|
-
<div className="flex items-center justify-between">
|
|
482
|
-
<div className="flex items-center gap-2">
|
|
483
|
-
<HardDrive className="h-4 w-4 text-purple-500" />
|
|
484
|
-
<span className="text-sm font-medium">Disk</span>
|
|
485
|
-
</div>
|
|
486
|
-
<span className={cn("font-bold", getStatusColor(metrics.disk.percentage, [80, 90]))}>
|
|
487
|
-
{metrics.disk.percentage.toFixed(1)}%
|
|
488
|
-
</span>
|
|
489
|
-
</div>
|
|
490
|
-
<div className="h-2 mt-2 bg-secondary rounded-full overflow-hidden">
|
|
491
|
-
<div
|
|
492
|
-
className="h-full bg-primary transition-all duration-300 ease-out"
|
|
493
|
-
style={{ width: `${metrics.disk.percentage}%` }}
|
|
494
|
-
/>
|
|
495
|
-
</div>
|
|
496
|
-
<div className="text-xs text-muted-foreground mt-1">
|
|
497
|
-
{formatBytes(metrics.disk.used * 1024 * 1024 * 1024)} / {formatBytes(metrics.disk.total * 1024 * 1024 * 1024)}
|
|
498
|
-
</div>
|
|
499
|
-
</CardContent>
|
|
500
|
-
</Card>
|
|
501
|
-
|
|
502
|
-
<Card>
|
|
503
|
-
<CardContent className="p-4">
|
|
504
|
-
<div className="flex items-center justify-between">
|
|
505
|
-
<div className="flex items-center gap-2">
|
|
506
|
-
<Network className="h-4 w-4 text-orange-500" />
|
|
507
|
-
<span className="text-sm font-medium">Network</span>
|
|
508
|
-
</div>
|
|
509
|
-
<span className={cn("font-bold", getStatusColor(metrics.network.latency, [50, 100]))}>
|
|
510
|
-
{metrics.network.latency.toFixed(0)}ms
|
|
511
|
-
</span>
|
|
512
|
-
</div>
|
|
513
|
-
<div className="text-xs text-muted-foreground mt-2">
|
|
514
|
-
↓ {metrics.network.download.toFixed(0)} MB/s • ↑ {metrics.network.upload.toFixed(0)} MB/s
|
|
515
|
-
</div>
|
|
516
|
-
</CardContent>
|
|
517
|
-
</Card>
|
|
518
|
-
</div>
|
|
519
|
-
|
|
520
|
-
{/* System Info */}
|
|
521
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
522
|
-
<Card>
|
|
523
|
-
<CardHeader>
|
|
524
|
-
<CardTitle className="text-base">System Information</CardTitle>
|
|
525
|
-
</CardHeader>
|
|
526
|
-
<CardContent className="space-y-3">
|
|
527
|
-
<div className="flex justify-between text-sm">
|
|
528
|
-
<span className="text-muted-foreground">Uptime:</span>
|
|
529
|
-
<span className="font-medium">{formatUptime(metrics.system.uptime)}</span>
|
|
530
|
-
</div>
|
|
531
|
-
<div className="flex justify-between text-sm">
|
|
532
|
-
<span className="text-muted-foreground">Processes:</span>
|
|
533
|
-
<span className="font-medium">{metrics.system.processes}</span>
|
|
534
|
-
</div>
|
|
535
|
-
<div className="flex justify-between text-sm">
|
|
536
|
-
<span className="text-muted-foreground">Load Average:</span>
|
|
537
|
-
<span className="font-medium">
|
|
538
|
-
{metrics.system.loadAverage.map(load => load.toFixed(2)).join(", ")}
|
|
539
|
-
</span>
|
|
540
|
-
</div>
|
|
541
|
-
</CardContent>
|
|
542
|
-
</Card>
|
|
543
|
-
|
|
544
|
-
{metrics.browser && (
|
|
545
|
-
<Card>
|
|
546
|
-
<CardHeader>
|
|
547
|
-
<CardTitle className="text-base">Browser Memory</CardTitle>
|
|
548
|
-
</CardHeader>
|
|
549
|
-
<CardContent className="space-y-3">
|
|
550
|
-
<div className="flex justify-between text-sm">
|
|
551
|
-
<span className="text-muted-foreground">JS Heap Used:</span>
|
|
552
|
-
<span className="font-medium">{formatBytes(metrics.browser.jsHeapUsed * 1024 * 1024)}</span>
|
|
553
|
-
</div>
|
|
554
|
-
<div className="flex justify-between text-sm">
|
|
555
|
-
<span className="text-muted-foreground">JS Heap Total:</span>
|
|
556
|
-
<span className="font-medium">{formatBytes(metrics.browser.jsHeapTotal * 1024 * 1024)}</span>
|
|
557
|
-
</div>
|
|
558
|
-
<div className="flex justify-between text-sm">
|
|
559
|
-
<span className="text-muted-foreground">JS Heap Limit:</span>
|
|
560
|
-
<span className="font-medium">{formatBytes(metrics.browser.jsHeapLimit * 1024 * 1024)}</span>
|
|
561
|
-
</div>
|
|
562
|
-
</CardContent>
|
|
563
|
-
</Card>
|
|
564
|
-
)}
|
|
565
|
-
</div>
|
|
566
|
-
</TabsContent>
|
|
567
|
-
|
|
568
|
-
<TabsContent value="details" className="space-y-4">
|
|
569
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
570
|
-
{/* Detailed CPU Info */}
|
|
571
|
-
<Card>
|
|
572
|
-
<CardHeader>
|
|
573
|
-
<CardTitle className="text-base flex items-center gap-2">
|
|
574
|
-
<Cpu className="h-4 w-4" />
|
|
575
|
-
CPU Details
|
|
576
|
-
</CardTitle>
|
|
577
|
-
</CardHeader>
|
|
578
|
-
<CardContent className="space-y-4">
|
|
579
|
-
<div>
|
|
580
|
-
<div className="flex justify-between text-sm mb-2">
|
|
581
|
-
<span>Usage</span>
|
|
582
|
-
<span className={getStatusColor(metrics.cpu.usage)}>{metrics.cpu.usage.toFixed(1)}%</span>
|
|
583
|
-
</div>
|
|
584
|
-
<div className="h-2 bg-secondary rounded-full overflow-hidden">
|
|
585
|
-
<div
|
|
586
|
-
className="h-full bg-primary transition-all duration-300 ease-out"
|
|
587
|
-
style={{ width: `${metrics.cpu.usage}%` }}
|
|
588
|
-
/>
|
|
589
|
-
</div>
|
|
590
|
-
</div>
|
|
591
|
-
<div className="grid grid-cols-2 gap-4 text-sm">
|
|
592
|
-
<div>
|
|
593
|
-
<span className="text-muted-foreground">Cores:</span>
|
|
594
|
-
<span className="ml-2 font-medium">{metrics.cpu.cores}</span>
|
|
595
|
-
</div>
|
|
596
|
-
{metrics.cpu.temperature && (
|
|
597
|
-
<div>
|
|
598
|
-
<span className="text-muted-foreground">Temperature:</span>
|
|
599
|
-
<span className="ml-2 font-medium">{metrics.cpu.temperature.toFixed(0)}°C</span>
|
|
600
|
-
</div>
|
|
601
|
-
)}
|
|
602
|
-
</div>
|
|
603
|
-
</CardContent>
|
|
604
|
-
</Card>
|
|
605
|
-
|
|
606
|
-
{/* Detailed Memory Info */}
|
|
607
|
-
<Card>
|
|
608
|
-
<CardHeader>
|
|
609
|
-
<CardTitle className="text-base flex items-center gap-2">
|
|
610
|
-
<MemoryStick className="h-4 w-4" />
|
|
611
|
-
Memory Details
|
|
612
|
-
</CardTitle>
|
|
613
|
-
</CardHeader>
|
|
614
|
-
<CardContent className="space-y-4">
|
|
615
|
-
<div>
|
|
616
|
-
<div className="flex justify-between text-sm mb-2">
|
|
617
|
-
<span>Usage</span>
|
|
618
|
-
<span className={getStatusColor(metrics.memory.percentage)}>{metrics.memory.percentage.toFixed(1)}%</span>
|
|
619
|
-
</div>
|
|
620
|
-
<div className="h-2 bg-secondary rounded-full overflow-hidden">
|
|
621
|
-
<div
|
|
622
|
-
className="h-full bg-primary transition-all duration-300 ease-out"
|
|
623
|
-
style={{ width: `${metrics.memory.percentage}%` }}
|
|
624
|
-
/>
|
|
625
|
-
</div>
|
|
626
|
-
</div>
|
|
627
|
-
<div className="space-y-2 text-sm">
|
|
628
|
-
<div className="flex justify-between">
|
|
629
|
-
<span className="text-muted-foreground">Used:</span>
|
|
630
|
-
<span className="font-medium">{formatBytes(metrics.memory.used * 1024 * 1024 * 1024)}</span>
|
|
631
|
-
</div>
|
|
632
|
-
<div className="flex justify-between">
|
|
633
|
-
<span className="text-muted-foreground">Available:</span>
|
|
634
|
-
<span className="font-medium">{formatBytes(metrics.memory.available * 1024 * 1024 * 1024)}</span>
|
|
635
|
-
</div>
|
|
636
|
-
<div className="flex justify-between">
|
|
637
|
-
<span className="text-muted-foreground">Total:</span>
|
|
638
|
-
<span className="font-medium">{formatBytes(metrics.memory.total * 1024 * 1024 * 1024)}</span>
|
|
639
|
-
</div>
|
|
640
|
-
</div>
|
|
641
|
-
</CardContent>
|
|
642
|
-
</Card>
|
|
643
|
-
</div>
|
|
644
|
-
</TabsContent>
|
|
645
|
-
|
|
646
|
-
<TabsContent value="alerts" className="space-y-4">
|
|
647
|
-
<div className="flex items-center justify-between">
|
|
648
|
-
<div className="text-sm text-muted-foreground">
|
|
649
|
-
{alerts.length} alerts
|
|
650
|
-
</div>
|
|
651
|
-
<Button
|
|
652
|
-
variant="outline"
|
|
653
|
-
size="sm"
|
|
654
|
-
onClick={() => setAlerts([])}
|
|
655
|
-
disabled={alerts.length === 0}
|
|
656
|
-
>
|
|
657
|
-
Clear All
|
|
658
|
-
</Button>
|
|
659
|
-
</div>
|
|
660
|
-
|
|
661
|
-
<div className="space-y-2 max-h-96 overflow-y-auto">
|
|
662
|
-
<AnimatePresence>
|
|
663
|
-
{alerts.map((alert, index) => (
|
|
664
|
-
<motion.div
|
|
665
|
-
key={alert.id}
|
|
666
|
-
initial={{ opacity: 0, x: -20 }}
|
|
667
|
-
animate={{ opacity: 1, x: 0 }}
|
|
668
|
-
exit={{ opacity: 0, x: 20 }}
|
|
669
|
-
transition={{ delay: index * 0.05 }}
|
|
670
|
-
>
|
|
671
|
-
<Card>
|
|
672
|
-
<CardContent className="p-4">
|
|
673
|
-
<div className="flex items-center justify-between">
|
|
674
|
-
<div className="flex items-center gap-3">
|
|
675
|
-
{alert.type === "critical" ? (
|
|
676
|
-
<AlertTriangle className="h-4 w-4 text-red-500" />
|
|
677
|
-
) : alert.type === "warning" ? (
|
|
678
|
-
<AlertTriangle className="h-4 w-4 text-yellow-500" />
|
|
679
|
-
) : (
|
|
680
|
-
<CheckCircle className="h-4 w-4 text-blue-500" />
|
|
681
|
-
)}
|
|
682
|
-
<div>
|
|
683
|
-
<div className="font-medium text-sm">{alert.metric}</div>
|
|
684
|
-
<div className="text-sm text-muted-foreground">{alert.message}</div>
|
|
685
|
-
</div>
|
|
686
|
-
</div>
|
|
687
|
-
<div className="text-right text-sm">
|
|
688
|
-
<div className="font-medium">
|
|
689
|
-
{alert.value.toFixed(1)}{alert.metric === "Network" ? "ms" : "%"}
|
|
690
|
-
</div>
|
|
691
|
-
<div className="text-xs text-muted-foreground">
|
|
692
|
-
{alert.timestamp.toLocaleTimeString()}
|
|
693
|
-
</div>
|
|
694
|
-
</div>
|
|
695
|
-
</div>
|
|
696
|
-
</CardContent>
|
|
697
|
-
</Card>
|
|
698
|
-
</motion.div>
|
|
699
|
-
))}
|
|
700
|
-
</AnimatePresence>
|
|
701
|
-
</div>
|
|
702
|
-
|
|
703
|
-
{alerts.length === 0 && (
|
|
704
|
-
<div className="text-center py-8">
|
|
705
|
-
<CheckCircle className="h-12 w-12 mx-auto mb-4 text-green-500" />
|
|
706
|
-
<p className="text-muted-foreground">No alerts - System is running smoothly</p>
|
|
707
|
-
</div>
|
|
708
|
-
)}
|
|
709
|
-
</TabsContent>
|
|
710
|
-
|
|
711
|
-
<TabsContent value="history" className="space-y-4">
|
|
712
|
-
<div className="text-sm text-muted-foreground">
|
|
713
|
-
Last {history.length} data points ({Math.round(chartDuration / 60000)} minutes)
|
|
714
|
-
</div>
|
|
715
|
-
|
|
716
|
-
{showCharts && history.length > 1 && (
|
|
717
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
718
|
-
{/* Simple CPU History Chart */}
|
|
719
|
-
<Card>
|
|
720
|
-
<CardHeader>
|
|
721
|
-
<CardTitle className="text-base">CPU Usage Trend</CardTitle>
|
|
722
|
-
</CardHeader>
|
|
723
|
-
<CardContent>
|
|
724
|
-
<div className="h-32 flex items-end justify-between gap-1">
|
|
725
|
-
{history.slice(-20).reverse().map((point, index) => (
|
|
726
|
-
<div
|
|
727
|
-
key={index}
|
|
728
|
-
className="bg-blue-500 rounded-t min-w-[4px] flex-1 transition-all"
|
|
729
|
-
style={{ height: `${(point.metrics.cpu.usage / 100) * 128}px` }}
|
|
730
|
-
/>
|
|
731
|
-
))}
|
|
732
|
-
</div>
|
|
733
|
-
<div className="text-xs text-muted-foreground mt-2 text-center">
|
|
734
|
-
Last 20 readings
|
|
735
|
-
</div>
|
|
736
|
-
</CardContent>
|
|
737
|
-
</Card>
|
|
738
|
-
|
|
739
|
-
{/* Simple Memory History Chart */}
|
|
740
|
-
<Card>
|
|
741
|
-
<CardHeader>
|
|
742
|
-
<CardTitle className="text-base">Memory Usage Trend</CardTitle>
|
|
743
|
-
</CardHeader>
|
|
744
|
-
<CardContent>
|
|
745
|
-
<div className="h-32 flex items-end justify-between gap-1">
|
|
746
|
-
{history.slice(-20).reverse().map((point, index) => (
|
|
747
|
-
<div
|
|
748
|
-
key={index}
|
|
749
|
-
className="bg-green-500 rounded-t min-w-[4px] flex-1 transition-all"
|
|
750
|
-
style={{ height: `${(point.metrics.memory.percentage / 100) * 128}px` }}
|
|
751
|
-
/>
|
|
752
|
-
))}
|
|
753
|
-
</div>
|
|
754
|
-
<div className="text-xs text-muted-foreground mt-2 text-center">
|
|
755
|
-
Last 20 readings
|
|
756
|
-
</div>
|
|
757
|
-
</CardContent>
|
|
758
|
-
</Card>
|
|
759
|
-
</div>
|
|
760
|
-
)}
|
|
761
|
-
|
|
762
|
-
{history.length === 0 && (
|
|
763
|
-
<div className="text-center py-8">
|
|
764
|
-
<BarChart3 className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
|
|
765
|
-
<p className="text-muted-foreground">No historical data available</p>
|
|
766
|
-
</div>
|
|
767
|
-
)}
|
|
768
|
-
</TabsContent>
|
|
769
|
-
</Tabs>
|
|
770
|
-
</CardContent>
|
|
771
|
-
</Card>
|
|
772
|
-
)
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
export const PerformanceMonitor: React.FC<PerformanceMonitorProps> = ({ className, ...props }) => {
|
|
776
|
-
const { hasProAccess, isLoading } = useSubscription()
|
|
777
|
-
|
|
778
|
-
// If no pro access, show upgrade prompt
|
|
779
|
-
if (!isLoading && !hasProAccess) {
|
|
780
|
-
return (
|
|
781
|
-
<Card className={cn("w-fit", className)}>
|
|
782
|
-
<CardContent className="py-6 text-center">
|
|
783
|
-
<div className="space-y-4">
|
|
784
|
-
<div className="rounded-full bg-purple-100 dark:bg-purple-900/30 p-3 w-fit mx-auto">
|
|
785
|
-
<Lock className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
|
786
|
-
</div>
|
|
787
|
-
<div>
|
|
788
|
-
<h3 className="font-semibold text-sm mb-2">Pro Feature</h3>
|
|
789
|
-
<p className="text-muted-foreground text-xs mb-4">
|
|
790
|
-
Performance Monitor is available exclusively to MoonUI Pro subscribers.
|
|
791
|
-
</p>
|
|
792
|
-
<a href="/pricing">
|
|
793
|
-
<Button size="sm">
|
|
794
|
-
<Sparkles className="mr-2 h-4 w-4" />
|
|
795
|
-
Upgrade to Pro
|
|
796
|
-
</Button>
|
|
797
|
-
</a>
|
|
798
|
-
</div>
|
|
799
|
-
</div>
|
|
800
|
-
</CardContent>
|
|
801
|
-
</Card>
|
|
802
|
-
)
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return <PerformanceMonitorInternal className={className} {...props} />
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
export type { PerformanceMetrics, PerformanceAlert, PerformanceMonitorProps }
|