Taskmanager update
Browse filesEnhanced System Metrics:
Detailed CPU metrics including temperature and frequency
Comprehensive memory breakdown with heap usage
Advanced performance metrics (FPS, page load times, web vitals)
Detailed network statistics
Storage monitoring with visual indicators
Battery health and detailed status
Power Management:
Multiple power profiles (Performance, Balanced, Power Saver)
Enhanced energy saver mode
Automatic power management based on system state
Detailed energy savings statistics
System Health:
Overall system health score
Real-time issue detection and alerts
Performance optimization suggestions
Historical metrics tracking
UI Improvements:
Interactive graphs for all metrics
Color-coded status indicators
Detailed tooltips and explanations
Collapsible sections for better organization
Alert system for critical events
Performance Monitoring:
Frame rate monitoring
Resource usage tracking
Network performance analysis
Web vitals monitoring
Detailed timing metrics
To use the enhanced task manager:
Monitor system health in the new health score section
Choose a power profile based on your needs
Enable auto energy saver for automatic power management
Monitor real-time alerts for system issues
5. View detailed metrics in each category
Check optimization suggestions when performance issues arise
@@ -447,6 +447,8 @@ export default function DebugTab() {
|
|
447 |
const appData = (await appResponse.json()) as Omit<WebAppInfo, 'gitInfo'>;
|
448 |
const gitData = (await gitResponse.json()) as GitInfo;
|
449 |
|
|
|
|
|
450 |
setWebAppInfo({
|
451 |
...appData,
|
452 |
gitInfo: gitData,
|
@@ -1084,7 +1086,7 @@ export default function DebugTab() {
|
|
1084 |
<>
|
1085 |
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-800">
|
1086 |
<div className="text-sm flex items-center gap-2">
|
1087 |
-
<div className="i-ph:git-
|
1088 |
<span className="text-bolt-elements-textSecondary">Repository:</span>
|
1089 |
<span className="text-bolt-elements-textPrimary">
|
1090 |
{webAppInfo.gitInfo.github.currentRepo.fullName}
|
|
|
447 |
const appData = (await appResponse.json()) as Omit<WebAppInfo, 'gitInfo'>;
|
448 |
const gitData = (await gitResponse.json()) as GitInfo;
|
449 |
|
450 |
+
console.log('Git Info Response:', gitData); // Add logging to debug
|
451 |
+
|
452 |
setWebAppInfo({
|
453 |
...appData,
|
454 |
gitInfo: gitData,
|
|
|
1086 |
<>
|
1087 |
<div className="mt-4 pt-4 border-t border-gray-200 dark:border-gray-800">
|
1088 |
<div className="text-sm flex items-center gap-2">
|
1089 |
+
<div className="i-ph:git-repository text-bolt-elements-textSecondary w-4 h-4" />
|
1090 |
<span className="text-bolt-elements-textSecondary">Repository:</span>
|
1091 |
<span className="text-bolt-elements-textPrimary">
|
1092 |
{webAppInfo.gitInfo.github.currentRepo.fullName}
|
@@ -24,22 +24,66 @@ interface BatteryManager extends EventTarget {
|
|
24 |
}
|
25 |
|
26 |
interface SystemMetrics {
|
27 |
-
cpu:
|
|
|
|
|
|
|
|
|
|
|
28 |
memory: {
|
29 |
used: number;
|
30 |
total: number;
|
31 |
percentage: number;
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
};
|
33 |
uptime: number;
|
34 |
battery?: {
|
35 |
level: number;
|
36 |
charging: boolean;
|
37 |
timeRemaining?: number;
|
|
|
|
|
|
|
38 |
};
|
39 |
network: {
|
40 |
downlink: number;
|
|
|
41 |
latency: number;
|
42 |
type: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
};
|
44 |
}
|
45 |
|
@@ -57,6 +101,26 @@ interface EnergySavings {
|
|
57 |
estimatedEnergySaved: number; // in mWh (milliwatt-hours)
|
58 |
}
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
declare global {
|
61 |
interface Navigator {
|
62 |
getBattery(): Promise<BatteryManager>;
|
@@ -88,12 +152,61 @@ const ENERGY_COSTS = {
|
|
88 |
rendering: 1, // mW per render
|
89 |
};
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
export default function TaskManagerTab() {
|
92 |
const [metrics, setMetrics] = useState<SystemMetrics>({
|
93 |
-
cpu: 0,
|
94 |
-
memory: { used: 0, total: 0, percentage: 0 },
|
95 |
uptime: 0,
|
96 |
-
network: { downlink: 0, latency: 0, type: 'unknown' },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
});
|
98 |
const [metricsHistory, setMetricsHistory] = useState<MetricsHistory>({
|
99 |
timestamps: [],
|
@@ -121,6 +234,8 @@ export default function TaskManagerTab() {
|
|
121 |
});
|
122 |
|
123 |
const saverModeStartTime = useRef<number | null>(null);
|
|
|
|
|
124 |
|
125 |
// Handle energy saver mode changes
|
126 |
const handleEnergySaverChange = (checked: boolean) => {
|
@@ -181,7 +296,135 @@ export default function TaskManagerTab() {
|
|
181 |
return () => clearInterval(interval);
|
182 |
}, [updateEnergySavings]);
|
183 |
|
184 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
const updateMetrics = async () => {
|
186 |
try {
|
187 |
// Get memory info using Performance API
|
@@ -216,36 +459,62 @@ export default function TaskManagerTab() {
|
|
216 |
(navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;
|
217 |
const networkInfo = {
|
218 |
downlink: connection?.downlink || 0,
|
|
|
219 |
latency: connection?.rtt || 0,
|
220 |
type: connection?.type || 'unknown',
|
|
|
|
|
|
|
221 |
};
|
222 |
|
223 |
-
|
224 |
-
|
|
|
|
|
|
|
225 |
memory: {
|
226 |
used: Math.round(usedMem),
|
227 |
total: Math.round(totalMem),
|
228 |
percentage: Math.round(memPercentage),
|
|
|
|
|
|
|
|
|
|
|
229 |
},
|
230 |
uptime: performance.now() / 1000,
|
231 |
battery: batteryInfo,
|
232 |
network: networkInfo,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
};
|
234 |
|
235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
236 |
|
237 |
// Update metrics history
|
238 |
const now = new Date().toLocaleTimeString();
|
239 |
setMetricsHistory((prev) => {
|
240 |
const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS);
|
241 |
-
const cpu = [...prev.cpu,
|
242 |
-
const memory = [...prev.memory,
|
243 |
const battery = [...prev.battery, batteryInfo?.level || 0].slice(-MAX_HISTORY_POINTS);
|
244 |
const network = [...prev.network, networkInfo.downlink].slice(-MAX_HISTORY_POINTS);
|
245 |
|
246 |
return { timestamps, cpu, memory, battery, network };
|
247 |
});
|
248 |
-
} catch (error
|
249 |
console.error('Failed to update system metrics:', error);
|
250 |
}
|
251 |
};
|
@@ -300,6 +569,8 @@ export default function TaskManagerTab() {
|
|
300 |
downlink: connection.downlink || 0,
|
301 |
latency: connection.rtt || 0,
|
302 |
type: connection.type || 'unknown',
|
|
|
|
|
303 |
},
|
304 |
}));
|
305 |
};
|
@@ -427,12 +698,60 @@ export default function TaskManagerTab() {
|
|
427 |
return () => clearInterval(batteryCheckInterval);
|
428 |
}, [autoEnergySaver]);
|
429 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
430 |
return (
|
431 |
<div className="flex flex-col gap-6">
|
432 |
-
{/*
|
433 |
<div className="flex flex-col gap-4">
|
434 |
<div className="flex items-center justify-between">
|
435 |
-
<h3 className="text-base font-medium text-bolt-elements-textPrimary">
|
436 |
<div className="flex items-center gap-4">
|
437 |
<div className="flex items-center gap-2">
|
438 |
<input
|
@@ -463,19 +782,99 @@ export default function TaskManagerTab() {
|
|
463 |
{energySaverMode && <span className="ml-2 text-xs text-bolt-elements-textSecondary">Active</span>}
|
464 |
</label>
|
465 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
</div>
|
467 |
</div>
|
|
|
468 |
|
|
|
|
|
|
|
469 |
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
470 |
{/* CPU Usage */}
|
471 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
472 |
<div className="flex items-center justify-between">
|
473 |
<span className="text-sm text-bolt-elements-textSecondary">CPU Usage</span>
|
474 |
-
<span className={classNames('text-sm font-medium', getUsageColor(metrics.cpu))}>
|
475 |
-
{Math.round(metrics.cpu)}%
|
476 |
</span>
|
477 |
</div>
|
478 |
{renderUsageGraph(metricsHistory.cpu, 'CPU', '#9333ea')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
</div>
|
480 |
|
481 |
{/* Memory Usage */}
|
@@ -487,28 +886,42 @@ export default function TaskManagerTab() {
|
|
487 |
</span>
|
488 |
</div>
|
489 |
{renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
490 |
</div>
|
491 |
|
492 |
-
{/*
|
493 |
-
|
494 |
-
<div className="flex
|
495 |
-
<
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
<
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
|
|
|
|
|
|
|
|
510 |
</div>
|
511 |
-
|
|
|
|
|
|
|
512 |
|
513 |
{/* Network */}
|
514 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
@@ -519,9 +932,118 @@ export default function TaskManagerTab() {
|
|
519 |
</span>
|
520 |
</div>
|
521 |
{renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b')}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
522 |
</div>
|
523 |
</div>
|
524 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
525 |
{/* Energy Savings */}
|
526 |
{energySaverMode && (
|
527 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
@@ -550,3 +1072,32 @@ export default function TaskManagerTab() {
|
|
550 |
</div>
|
551 |
);
|
552 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
interface SystemMetrics {
|
27 |
+
cpu: {
|
28 |
+
usage: number;
|
29 |
+
cores: number[];
|
30 |
+
temperature?: number;
|
31 |
+
frequency?: number;
|
32 |
+
};
|
33 |
memory: {
|
34 |
used: number;
|
35 |
total: number;
|
36 |
percentage: number;
|
37 |
+
heap: {
|
38 |
+
used: number;
|
39 |
+
total: number;
|
40 |
+
limit: number;
|
41 |
+
};
|
42 |
+
cache?: number;
|
43 |
};
|
44 |
uptime: number;
|
45 |
battery?: {
|
46 |
level: number;
|
47 |
charging: boolean;
|
48 |
timeRemaining?: number;
|
49 |
+
temperature?: number;
|
50 |
+
cycles?: number;
|
51 |
+
health?: number;
|
52 |
};
|
53 |
network: {
|
54 |
downlink: number;
|
55 |
+
uplink?: number;
|
56 |
latency: number;
|
57 |
type: string;
|
58 |
+
activeConnections?: number;
|
59 |
+
bytesReceived: number;
|
60 |
+
bytesSent: number;
|
61 |
+
};
|
62 |
+
performance: {
|
63 |
+
fps: number;
|
64 |
+
pageLoad: number;
|
65 |
+
domReady: number;
|
66 |
+
resources: {
|
67 |
+
total: number;
|
68 |
+
size: number;
|
69 |
+
loadTime: number;
|
70 |
+
};
|
71 |
+
timing: {
|
72 |
+
ttfb: number;
|
73 |
+
fcp: number;
|
74 |
+
lcp: number;
|
75 |
+
};
|
76 |
+
};
|
77 |
+
storage: {
|
78 |
+
total: number;
|
79 |
+
used: number;
|
80 |
+
free: number;
|
81 |
+
type: string;
|
82 |
+
};
|
83 |
+
health: {
|
84 |
+
score: number;
|
85 |
+
issues: string[];
|
86 |
+
suggestions: string[];
|
87 |
};
|
88 |
}
|
89 |
|
|
|
101 |
estimatedEnergySaved: number; // in mWh (milliwatt-hours)
|
102 |
}
|
103 |
|
104 |
+
interface PowerProfile {
|
105 |
+
name: string;
|
106 |
+
description: string;
|
107 |
+
settings: {
|
108 |
+
updateInterval: number;
|
109 |
+
enableAnimations: boolean;
|
110 |
+
backgroundProcessing: boolean;
|
111 |
+
networkThrottling: boolean;
|
112 |
+
};
|
113 |
+
}
|
114 |
+
|
115 |
+
interface PerformanceAlert {
|
116 |
+
type: 'warning' | 'error' | 'info';
|
117 |
+
message: string;
|
118 |
+
timestamp: number;
|
119 |
+
metric: string;
|
120 |
+
threshold: number;
|
121 |
+
value: number;
|
122 |
+
}
|
123 |
+
|
124 |
declare global {
|
125 |
interface Navigator {
|
126 |
getBattery(): Promise<BatteryManager>;
|
|
|
152 |
rendering: 1, // mW per render
|
153 |
};
|
154 |
|
155 |
+
const PERFORMANCE_THRESHOLDS = {
|
156 |
+
cpu: { warning: 70, critical: 90 },
|
157 |
+
memory: { warning: 80, critical: 95 },
|
158 |
+
fps: { warning: 30, critical: 15 },
|
159 |
+
loadTime: { warning: 3000, critical: 5000 },
|
160 |
+
};
|
161 |
+
|
162 |
+
const POWER_PROFILES: PowerProfile[] = [
|
163 |
+
{
|
164 |
+
name: 'Performance',
|
165 |
+
description: 'Maximum performance, higher power consumption',
|
166 |
+
settings: {
|
167 |
+
updateInterval: 1000,
|
168 |
+
enableAnimations: true,
|
169 |
+
backgroundProcessing: true,
|
170 |
+
networkThrottling: false,
|
171 |
+
},
|
172 |
+
},
|
173 |
+
{
|
174 |
+
name: 'Balanced',
|
175 |
+
description: 'Balance between performance and power saving',
|
176 |
+
settings: {
|
177 |
+
updateInterval: 2000,
|
178 |
+
enableAnimations: true,
|
179 |
+
backgroundProcessing: true,
|
180 |
+
networkThrottling: false,
|
181 |
+
},
|
182 |
+
},
|
183 |
+
{
|
184 |
+
name: 'Power Saver',
|
185 |
+
description: 'Maximum power saving, reduced performance',
|
186 |
+
settings: {
|
187 |
+
updateInterval: 5000,
|
188 |
+
enableAnimations: false,
|
189 |
+
backgroundProcessing: false,
|
190 |
+
networkThrottling: true,
|
191 |
+
},
|
192 |
+
},
|
193 |
+
];
|
194 |
+
|
195 |
export default function TaskManagerTab() {
|
196 |
const [metrics, setMetrics] = useState<SystemMetrics>({
|
197 |
+
cpu: { usage: 0, cores: [] },
|
198 |
+
memory: { used: 0, total: 0, percentage: 0, heap: { used: 0, total: 0, limit: 0 } },
|
199 |
uptime: 0,
|
200 |
+
network: { downlink: 0, latency: 0, type: 'unknown', bytesReceived: 0, bytesSent: 0 },
|
201 |
+
performance: {
|
202 |
+
fps: 0,
|
203 |
+
pageLoad: 0,
|
204 |
+
domReady: 0,
|
205 |
+
resources: { total: 0, size: 0, loadTime: 0 },
|
206 |
+
timing: { ttfb: 0, fcp: 0, lcp: 0 },
|
207 |
+
},
|
208 |
+
storage: { total: 0, used: 0, free: 0, type: 'unknown' },
|
209 |
+
health: { score: 0, issues: [], suggestions: [] },
|
210 |
});
|
211 |
const [metricsHistory, setMetricsHistory] = useState<MetricsHistory>({
|
212 |
timestamps: [],
|
|
|
234 |
});
|
235 |
|
236 |
const saverModeStartTime = useRef<number | null>(null);
|
237 |
+
const [selectedProfile, setSelectedProfile] = useState<PowerProfile>(POWER_PROFILES[1]); // Default to Balanced
|
238 |
+
const [alerts, setAlerts] = useState<PerformanceAlert[]>([]);
|
239 |
|
240 |
// Handle energy saver mode changes
|
241 |
const handleEnergySaverChange = (checked: boolean) => {
|
|
|
296 |
return () => clearInterval(interval);
|
297 |
}, [updateEnergySavings]);
|
298 |
|
299 |
+
// Get detailed performance metrics
|
300 |
+
const getPerformanceMetrics = async (): Promise<Partial<SystemMetrics['performance']>> => {
|
301 |
+
try {
|
302 |
+
// Get FPS
|
303 |
+
const fps = await measureFrameRate();
|
304 |
+
|
305 |
+
// Get page load metrics
|
306 |
+
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
|
307 |
+
const pageLoad = navigation.loadEventEnd - navigation.startTime;
|
308 |
+
const domReady = navigation.domContentLoadedEventEnd - navigation.startTime;
|
309 |
+
|
310 |
+
// Get resource metrics
|
311 |
+
const resources = performance.getEntriesByType('resource');
|
312 |
+
const resourceMetrics = {
|
313 |
+
total: resources.length,
|
314 |
+
size: resources.reduce((total, r) => total + (r as any).transferSize || 0, 0),
|
315 |
+
loadTime: Math.max(...resources.map((r) => r.duration)),
|
316 |
+
};
|
317 |
+
|
318 |
+
// Get Web Vitals
|
319 |
+
const ttfb = navigation.responseStart - navigation.requestStart;
|
320 |
+
const paintEntries = performance.getEntriesByType('paint');
|
321 |
+
const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0;
|
322 |
+
const lcpEntry = await getLargestContentfulPaint();
|
323 |
+
|
324 |
+
return {
|
325 |
+
fps,
|
326 |
+
pageLoad,
|
327 |
+
domReady,
|
328 |
+
resources: resourceMetrics,
|
329 |
+
timing: {
|
330 |
+
ttfb,
|
331 |
+
fcp,
|
332 |
+
lcp: lcpEntry?.startTime || 0,
|
333 |
+
},
|
334 |
+
};
|
335 |
+
} catch (error) {
|
336 |
+
console.error('Failed to get performance metrics:', error);
|
337 |
+
return {};
|
338 |
+
}
|
339 |
+
};
|
340 |
+
|
341 |
+
// Measure frame rate
|
342 |
+
const measureFrameRate = async (): Promise<number> => {
|
343 |
+
return new Promise((resolve) => {
|
344 |
+
const frameCount = { value: 0 };
|
345 |
+
const startTime = performance.now();
|
346 |
+
|
347 |
+
const countFrame = (time: number) => {
|
348 |
+
frameCount.value++;
|
349 |
+
|
350 |
+
if (time - startTime >= 1000) {
|
351 |
+
resolve(Math.round((frameCount.value * 1000) / (time - startTime)));
|
352 |
+
} else {
|
353 |
+
requestAnimationFrame(countFrame);
|
354 |
+
}
|
355 |
+
};
|
356 |
+
|
357 |
+
requestAnimationFrame(countFrame);
|
358 |
+
});
|
359 |
+
};
|
360 |
+
|
361 |
+
// Get Largest Contentful Paint
|
362 |
+
const getLargestContentfulPaint = async (): Promise<PerformanceEntry | undefined> => {
|
363 |
+
return new Promise((resolve) => {
|
364 |
+
new PerformanceObserver((list) => {
|
365 |
+
const entries = list.getEntries();
|
366 |
+
resolve(entries[entries.length - 1]);
|
367 |
+
}).observe({ entryTypes: ['largest-contentful-paint'] });
|
368 |
+
|
369 |
+
// Resolve after 3 seconds if no LCP entry is found
|
370 |
+
setTimeout(() => resolve(undefined), 3000);
|
371 |
+
});
|
372 |
+
};
|
373 |
+
|
374 |
+
// Analyze system health
|
375 |
+
const analyzeSystemHealth = (currentMetrics: SystemMetrics): SystemMetrics['health'] => {
|
376 |
+
const issues: string[] = [];
|
377 |
+
const suggestions: string[] = [];
|
378 |
+
let score = 100;
|
379 |
+
|
380 |
+
// CPU analysis
|
381 |
+
if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) {
|
382 |
+
score -= 30;
|
383 |
+
issues.push('Critical CPU usage');
|
384 |
+
suggestions.push('Consider closing resource-intensive applications');
|
385 |
+
} else if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.warning) {
|
386 |
+
score -= 15;
|
387 |
+
issues.push('High CPU usage');
|
388 |
+
suggestions.push('Monitor system processes for unusual activity');
|
389 |
+
}
|
390 |
+
|
391 |
+
// Memory analysis
|
392 |
+
if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) {
|
393 |
+
score -= 30;
|
394 |
+
issues.push('Critical memory usage');
|
395 |
+
suggestions.push('Close unused applications to free up memory');
|
396 |
+
} else if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.warning) {
|
397 |
+
score -= 15;
|
398 |
+
issues.push('High memory usage');
|
399 |
+
suggestions.push('Consider freeing up memory by closing background applications');
|
400 |
+
}
|
401 |
+
|
402 |
+
// Performance analysis
|
403 |
+
if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) {
|
404 |
+
score -= 20;
|
405 |
+
issues.push('Very low frame rate');
|
406 |
+
suggestions.push('Disable animations or switch to power saver mode');
|
407 |
+
} else if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.warning) {
|
408 |
+
score -= 10;
|
409 |
+
issues.push('Low frame rate');
|
410 |
+
suggestions.push('Consider reducing visual effects');
|
411 |
+
}
|
412 |
+
|
413 |
+
// Battery analysis
|
414 |
+
if (currentMetrics.battery && !currentMetrics.battery.charging && currentMetrics.battery.level < 20) {
|
415 |
+
score -= 10;
|
416 |
+
issues.push('Low battery');
|
417 |
+
suggestions.push('Connect to power source or enable power saver mode');
|
418 |
+
}
|
419 |
+
|
420 |
+
return {
|
421 |
+
score: Math.max(0, score),
|
422 |
+
issues,
|
423 |
+
suggestions,
|
424 |
+
};
|
425 |
+
};
|
426 |
+
|
427 |
+
// Update metrics with enhanced data
|
428 |
const updateMetrics = async () => {
|
429 |
try {
|
430 |
// Get memory info using Performance API
|
|
|
459 |
(navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;
|
460 |
const networkInfo = {
|
461 |
downlink: connection?.downlink || 0,
|
462 |
+
uplink: connection?.uplink,
|
463 |
latency: connection?.rtt || 0,
|
464 |
type: connection?.type || 'unknown',
|
465 |
+
activeConnections: connection?.activeConnections,
|
466 |
+
bytesReceived: connection?.bytesReceived || 0,
|
467 |
+
bytesSent: connection?.bytesSent || 0,
|
468 |
};
|
469 |
|
470 |
+
// Get enhanced performance metrics
|
471 |
+
const performanceMetrics = await getPerformanceMetrics();
|
472 |
+
|
473 |
+
const metrics: SystemMetrics = {
|
474 |
+
cpu: { usage: cpuUsage, cores: [], temperature: undefined, frequency: undefined },
|
475 |
memory: {
|
476 |
used: Math.round(usedMem),
|
477 |
total: Math.round(totalMem),
|
478 |
percentage: Math.round(memPercentage),
|
479 |
+
heap: {
|
480 |
+
used: Math.round(usedMem),
|
481 |
+
total: Math.round(totalMem),
|
482 |
+
limit: Math.round(totalMem),
|
483 |
+
},
|
484 |
},
|
485 |
uptime: performance.now() / 1000,
|
486 |
battery: batteryInfo,
|
487 |
network: networkInfo,
|
488 |
+
performance: performanceMetrics as SystemMetrics['performance'],
|
489 |
+
storage: {
|
490 |
+
total: 0,
|
491 |
+
used: 0,
|
492 |
+
free: 0,
|
493 |
+
type: 'unknown',
|
494 |
+
},
|
495 |
+
health: { score: 0, issues: [], suggestions: [] },
|
496 |
};
|
497 |
|
498 |
+
// Analyze system health
|
499 |
+
metrics.health = analyzeSystemHealth(metrics);
|
500 |
+
|
501 |
+
// Check for alerts
|
502 |
+
checkPerformanceAlerts(metrics);
|
503 |
+
|
504 |
+
setMetrics(metrics);
|
505 |
|
506 |
// Update metrics history
|
507 |
const now = new Date().toLocaleTimeString();
|
508 |
setMetricsHistory((prev) => {
|
509 |
const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS);
|
510 |
+
const cpu = [...prev.cpu, metrics.cpu.usage].slice(-MAX_HISTORY_POINTS);
|
511 |
+
const memory = [...prev.memory, metrics.memory.percentage].slice(-MAX_HISTORY_POINTS);
|
512 |
const battery = [...prev.battery, batteryInfo?.level || 0].slice(-MAX_HISTORY_POINTS);
|
513 |
const network = [...prev.network, networkInfo.downlink].slice(-MAX_HISTORY_POINTS);
|
514 |
|
515 |
return { timestamps, cpu, memory, battery, network };
|
516 |
});
|
517 |
+
} catch (error) {
|
518 |
console.error('Failed to update system metrics:', error);
|
519 |
}
|
520 |
};
|
|
|
569 |
downlink: connection.downlink || 0,
|
570 |
latency: connection.rtt || 0,
|
571 |
type: connection.type || 'unknown',
|
572 |
+
bytesReceived: connection.bytesReceived || 0,
|
573 |
+
bytesSent: connection.bytesSent || 0,
|
574 |
},
|
575 |
}));
|
576 |
};
|
|
|
698 |
return () => clearInterval(batteryCheckInterval);
|
699 |
}, [autoEnergySaver]);
|
700 |
|
701 |
+
// Check for performance alerts
|
702 |
+
const checkPerformanceAlerts = (currentMetrics: SystemMetrics) => {
|
703 |
+
const newAlerts: PerformanceAlert[] = [];
|
704 |
+
|
705 |
+
// CPU alert
|
706 |
+
if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) {
|
707 |
+
newAlerts.push({
|
708 |
+
type: 'error',
|
709 |
+
message: 'Critical CPU usage detected',
|
710 |
+
timestamp: Date.now(),
|
711 |
+
metric: 'cpu',
|
712 |
+
threshold: PERFORMANCE_THRESHOLDS.cpu.critical,
|
713 |
+
value: currentMetrics.cpu.usage,
|
714 |
+
});
|
715 |
+
}
|
716 |
+
|
717 |
+
// Memory alert
|
718 |
+
if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) {
|
719 |
+
newAlerts.push({
|
720 |
+
type: 'error',
|
721 |
+
message: 'Critical memory usage detected',
|
722 |
+
timestamp: Date.now(),
|
723 |
+
metric: 'memory',
|
724 |
+
threshold: PERFORMANCE_THRESHOLDS.memory.critical,
|
725 |
+
value: currentMetrics.memory.percentage,
|
726 |
+
});
|
727 |
+
}
|
728 |
+
|
729 |
+
// Performance alert
|
730 |
+
if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) {
|
731 |
+
newAlerts.push({
|
732 |
+
type: 'warning',
|
733 |
+
message: 'Very low frame rate detected',
|
734 |
+
timestamp: Date.now(),
|
735 |
+
metric: 'fps',
|
736 |
+
threshold: PERFORMANCE_THRESHOLDS.fps.critical,
|
737 |
+
value: currentMetrics.performance.fps,
|
738 |
+
});
|
739 |
+
}
|
740 |
+
|
741 |
+
if (newAlerts.length > 0) {
|
742 |
+
setAlerts((prev) => [...prev, ...newAlerts]);
|
743 |
+
newAlerts.forEach((alert) => {
|
744 |
+
toast.warning(alert.message);
|
745 |
+
});
|
746 |
+
}
|
747 |
+
};
|
748 |
+
|
749 |
return (
|
750 |
<div className="flex flex-col gap-6">
|
751 |
+
{/* Power Profile Selection */}
|
752 |
<div className="flex flex-col gap-4">
|
753 |
<div className="flex items-center justify-between">
|
754 |
+
<h3 className="text-base font-medium text-bolt-elements-textPrimary">Power Management</h3>
|
755 |
<div className="flex items-center gap-4">
|
756 |
<div className="flex items-center gap-2">
|
757 |
<input
|
|
|
782 |
{energySaverMode && <span className="ml-2 text-xs text-bolt-elements-textSecondary">Active</span>}
|
783 |
</label>
|
784 |
</div>
|
785 |
+
<select
|
786 |
+
value={selectedProfile.name}
|
787 |
+
onChange={(e) => {
|
788 |
+
const profile = POWER_PROFILES.find((p) => p.name === e.target.value);
|
789 |
+
|
790 |
+
if (profile) {
|
791 |
+
setSelectedProfile(profile);
|
792 |
+
toast.success(`Switched to ${profile.name} power profile`);
|
793 |
+
}
|
794 |
+
}}
|
795 |
+
className="px-3 py-1 rounded-md bg-[#F8F8F8] dark:bg-[#141414] border border-[#E5E5E5] dark:border-[#1A1A1A] text-sm"
|
796 |
+
>
|
797 |
+
{POWER_PROFILES.map((profile) => (
|
798 |
+
<option key={profile.name} value={profile.name}>
|
799 |
+
{profile.name}
|
800 |
+
</option>
|
801 |
+
))}
|
802 |
+
</select>
|
803 |
+
</div>
|
804 |
+
</div>
|
805 |
+
<div className="text-sm text-bolt-elements-textSecondary">{selectedProfile.description}</div>
|
806 |
+
</div>
|
807 |
+
|
808 |
+
{/* System Health Score */}
|
809 |
+
<div className="flex flex-col gap-4">
|
810 |
+
<h3 className="text-base font-medium text-bolt-elements-textPrimary">System Health</h3>
|
811 |
+
<div className="grid grid-cols-1 gap-4">
|
812 |
+
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
813 |
+
<div className="flex items-center justify-between">
|
814 |
+
<span className="text-sm text-bolt-elements-textSecondary">Health Score</span>
|
815 |
+
<span
|
816 |
+
className={classNames('text-lg font-medium', {
|
817 |
+
'text-green-500': metrics.health.score >= 80,
|
818 |
+
'text-yellow-500': metrics.health.score >= 60 && metrics.health.score < 80,
|
819 |
+
'text-red-500': metrics.health.score < 60,
|
820 |
+
})}
|
821 |
+
>
|
822 |
+
{metrics.health.score}%
|
823 |
+
</span>
|
824 |
+
</div>
|
825 |
+
{metrics.health.issues.length > 0 && (
|
826 |
+
<div className="mt-2">
|
827 |
+
<div className="text-sm font-medium text-bolt-elements-textSecondary mb-1">Issues:</div>
|
828 |
+
<ul className="text-sm text-bolt-elements-textSecondary space-y-1">
|
829 |
+
{metrics.health.issues.map((issue, index) => (
|
830 |
+
<li key={index} className="flex items-center gap-2">
|
831 |
+
<div className="i-ph:warning-circle-fill text-yellow-500 w-4 h-4" />
|
832 |
+
{issue}
|
833 |
+
</li>
|
834 |
+
))}
|
835 |
+
</ul>
|
836 |
+
</div>
|
837 |
+
)}
|
838 |
+
{metrics.health.suggestions.length > 0 && (
|
839 |
+
<div className="mt-2">
|
840 |
+
<div className="text-sm font-medium text-bolt-elements-textSecondary mb-1">Suggestions:</div>
|
841 |
+
<ul className="text-sm text-bolt-elements-textSecondary space-y-1">
|
842 |
+
{metrics.health.suggestions.map((suggestion, index) => (
|
843 |
+
<li key={index} className="flex items-center gap-2">
|
844 |
+
<div className="i-ph:lightbulb-fill text-purple-500 w-4 h-4" />
|
845 |
+
{suggestion}
|
846 |
+
</li>
|
847 |
+
))}
|
848 |
+
</ul>
|
849 |
+
</div>
|
850 |
+
)}
|
851 |
</div>
|
852 |
</div>
|
853 |
+
</div>
|
854 |
|
855 |
+
{/* System Metrics */}
|
856 |
+
<div className="flex flex-col gap-4">
|
857 |
+
<h3 className="text-base font-medium text-bolt-elements-textPrimary">System Metrics</h3>
|
858 |
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
859 |
{/* CPU Usage */}
|
860 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
861 |
<div className="flex items-center justify-between">
|
862 |
<span className="text-sm text-bolt-elements-textSecondary">CPU Usage</span>
|
863 |
+
<span className={classNames('text-sm font-medium', getUsageColor(metrics.cpu.usage))}>
|
864 |
+
{Math.round(metrics.cpu.usage)}%
|
865 |
</span>
|
866 |
</div>
|
867 |
{renderUsageGraph(metricsHistory.cpu, 'CPU', '#9333ea')}
|
868 |
+
{metrics.cpu.temperature && (
|
869 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">
|
870 |
+
Temperature: {metrics.cpu.temperature}°C
|
871 |
+
</div>
|
872 |
+
)}
|
873 |
+
{metrics.cpu.frequency && (
|
874 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
875 |
+
Frequency: {(metrics.cpu.frequency / 1000).toFixed(1)} GHz
|
876 |
+
</div>
|
877 |
+
)}
|
878 |
</div>
|
879 |
|
880 |
{/* Memory Usage */}
|
|
|
886 |
</span>
|
887 |
</div>
|
888 |
{renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb')}
|
889 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">
|
890 |
+
Used: {formatBytes(metrics.memory.used)}
|
891 |
+
</div>
|
892 |
+
<div className="text-xs text-bolt-elements-textSecondary">Total: {formatBytes(metrics.memory.total)}</div>
|
893 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
894 |
+
Heap: {formatBytes(metrics.memory.heap.used)} / {formatBytes(metrics.memory.heap.total)}
|
895 |
+
</div>
|
896 |
</div>
|
897 |
|
898 |
+
{/* Performance */}
|
899 |
+
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
900 |
+
<div className="flex items-center justify-between">
|
901 |
+
<span className="text-sm text-bolt-elements-textSecondary">Performance</span>
|
902 |
+
<span
|
903 |
+
className={classNames('text-sm font-medium', {
|
904 |
+
'text-red-500': metrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical,
|
905 |
+
'text-yellow-500': metrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.warning,
|
906 |
+
'text-green-500': metrics.performance.fps >= PERFORMANCE_THRESHOLDS.fps.warning,
|
907 |
+
})}
|
908 |
+
>
|
909 |
+
{Math.round(metrics.performance.fps)} FPS
|
910 |
+
</span>
|
911 |
+
</div>
|
912 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">
|
913 |
+
Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s
|
914 |
+
</div>
|
915 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
916 |
+
DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s
|
917 |
+
</div>
|
918 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
919 |
+
TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s
|
920 |
</div>
|
921 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
922 |
+
Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)})
|
923 |
+
</div>
|
924 |
+
</div>
|
925 |
|
926 |
{/* Network */}
|
927 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
|
|
932 |
</span>
|
933 |
</div>
|
934 |
{renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b')}
|
935 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">Type: {metrics.network.type}</div>
|
936 |
+
<div className="text-xs text-bolt-elements-textSecondary">Latency: {metrics.network.latency}ms</div>
|
937 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
938 |
+
Received: {formatBytes(metrics.network.bytesReceived)}
|
939 |
+
</div>
|
940 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
941 |
+
Sent: {formatBytes(metrics.network.bytesSent)}
|
942 |
+
</div>
|
943 |
</div>
|
944 |
</div>
|
945 |
|
946 |
+
{/* Battery Section */}
|
947 |
+
{metrics.battery && (
|
948 |
+
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
949 |
+
<div className="flex items-center justify-between">
|
950 |
+
<span className="text-sm text-bolt-elements-textSecondary">Battery</span>
|
951 |
+
<div className="flex items-center gap-2">
|
952 |
+
{metrics.battery.charging && <div className="i-ph:lightning-fill w-4 h-4 text-bolt-action-primary" />}
|
953 |
+
<span
|
954 |
+
className={classNames(
|
955 |
+
'text-sm font-medium',
|
956 |
+
metrics.battery.level > 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500',
|
957 |
+
)}
|
958 |
+
>
|
959 |
+
{Math.round(metrics.battery.level)}%
|
960 |
+
</span>
|
961 |
+
</div>
|
962 |
+
</div>
|
963 |
+
{renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e')}
|
964 |
+
{metrics.battery.timeRemaining && (
|
965 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">
|
966 |
+
{metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '}
|
967 |
+
{formatTime(metrics.battery.timeRemaining)}
|
968 |
+
</div>
|
969 |
+
)}
|
970 |
+
{metrics.battery.temperature && (
|
971 |
+
<div className="text-xs text-bolt-elements-textSecondary">
|
972 |
+
Temperature: {metrics.battery.temperature}°C
|
973 |
+
</div>
|
974 |
+
)}
|
975 |
+
{metrics.battery.cycles && (
|
976 |
+
<div className="text-xs text-bolt-elements-textSecondary">Charge cycles: {metrics.battery.cycles}</div>
|
977 |
+
)}
|
978 |
+
{metrics.battery.health && (
|
979 |
+
<div className="text-xs text-bolt-elements-textSecondary">Battery health: {metrics.battery.health}%</div>
|
980 |
+
)}
|
981 |
+
</div>
|
982 |
+
)}
|
983 |
+
|
984 |
+
{/* Storage Section */}
|
985 |
+
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
986 |
+
<div className="flex items-center justify-between">
|
987 |
+
<span className="text-sm text-bolt-elements-textSecondary">Storage</span>
|
988 |
+
<span className="text-sm font-medium text-bolt-elements-textPrimary">
|
989 |
+
{formatBytes(metrics.storage.used)} / {formatBytes(metrics.storage.total)}
|
990 |
+
</span>
|
991 |
+
</div>
|
992 |
+
<div className="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
993 |
+
<div
|
994 |
+
className={classNames('h-full transition-all duration-300', {
|
995 |
+
'bg-green-500': metrics.storage.used / metrics.storage.total < 0.7,
|
996 |
+
'bg-yellow-500':
|
997 |
+
metrics.storage.used / metrics.storage.total >= 0.7 &&
|
998 |
+
metrics.storage.used / metrics.storage.total < 0.9,
|
999 |
+
'bg-red-500': metrics.storage.used / metrics.storage.total >= 0.9,
|
1000 |
+
})}
|
1001 |
+
style={{ width: `${(metrics.storage.used / metrics.storage.total) * 100}%` }}
|
1002 |
+
/>
|
1003 |
+
</div>
|
1004 |
+
<div className="text-xs text-bolt-elements-textSecondary mt-2">Free: {formatBytes(metrics.storage.free)}</div>
|
1005 |
+
<div className="text-xs text-bolt-elements-textSecondary">Type: {metrics.storage.type}</div>
|
1006 |
+
</div>
|
1007 |
+
|
1008 |
+
{/* Performance Alerts */}
|
1009 |
+
{alerts.length > 0 && (
|
1010 |
+
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
1011 |
+
<div className="flex items-center justify-between">
|
1012 |
+
<span className="text-sm font-medium text-bolt-elements-textPrimary">Recent Alerts</span>
|
1013 |
+
<button
|
1014 |
+
onClick={() => setAlerts([])}
|
1015 |
+
className="text-xs text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary"
|
1016 |
+
>
|
1017 |
+
Clear All
|
1018 |
+
</button>
|
1019 |
+
</div>
|
1020 |
+
<div className="space-y-2">
|
1021 |
+
{alerts.slice(-5).map((alert, index) => (
|
1022 |
+
<div
|
1023 |
+
key={index}
|
1024 |
+
className={classNames('flex items-center gap-2 text-sm', {
|
1025 |
+
'text-red-500': alert.type === 'error',
|
1026 |
+
'text-yellow-500': alert.type === 'warning',
|
1027 |
+
'text-blue-500': alert.type === 'info',
|
1028 |
+
})}
|
1029 |
+
>
|
1030 |
+
<div
|
1031 |
+
className={classNames('w-4 h-4', {
|
1032 |
+
'i-ph:warning-circle-fill': alert.type === 'warning',
|
1033 |
+
'i-ph:x-circle-fill': alert.type === 'error',
|
1034 |
+
'i-ph:info-fill': alert.type === 'info',
|
1035 |
+
})}
|
1036 |
+
/>
|
1037 |
+
<span>{alert.message}</span>
|
1038 |
+
<span className="text-xs text-bolt-elements-textSecondary ml-auto">
|
1039 |
+
{new Date(alert.timestamp).toLocaleTimeString()}
|
1040 |
+
</span>
|
1041 |
+
</div>
|
1042 |
+
))}
|
1043 |
+
</div>
|
1044 |
+
</div>
|
1045 |
+
)}
|
1046 |
+
|
1047 |
{/* Energy Savings */}
|
1048 |
{energySaverMode && (
|
1049 |
<div className="flex flex-col gap-2 rounded-lg bg-[#F8F8F8] dark:bg-[#141414] p-4">
|
|
|
1072 |
</div>
|
1073 |
);
|
1074 |
}
|
1075 |
+
|
1076 |
+
// Helper function to format bytes
|
1077 |
+
const formatBytes = (bytes: number): string => {
|
1078 |
+
if (bytes === 0) {
|
1079 |
+
return '0 B';
|
1080 |
+
}
|
1081 |
+
|
1082 |
+
const k = 1024;
|
1083 |
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
1084 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
1085 |
+
|
1086 |
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
1087 |
+
};
|
1088 |
+
|
1089 |
+
// Helper function to format time
|
1090 |
+
const formatTime = (seconds: number): string => {
|
1091 |
+
if (!isFinite(seconds) || seconds === 0) {
|
1092 |
+
return 'Unknown';
|
1093 |
+
}
|
1094 |
+
|
1095 |
+
const hours = Math.floor(seconds / 3600);
|
1096 |
+
const minutes = Math.floor((seconds % 3600) / 60);
|
1097 |
+
|
1098 |
+
if (hours > 0) {
|
1099 |
+
return `${hours}h ${minutes}m`;
|
1100 |
+
}
|
1101 |
+
|
1102 |
+
return `${minutes}m`;
|
1103 |
+
};
|
@@ -20,7 +20,7 @@ interface GitHubRepoInfo {
|
|
20 |
const getLocalGitInfo = () => {
|
21 |
try {
|
22 |
return {
|
23 |
-
commitHash: execSync('git rev-parse
|
24 |
branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
|
25 |
commitTime: execSync('git log -1 --format=%cd').toString().trim(),
|
26 |
author: execSync('git log -1 --format=%an').toString().trim(),
|
@@ -40,13 +40,42 @@ const getLocalGitInfo = () => {
|
|
40 |
|
41 |
const getGitHubInfo = async (repoFullName: string) => {
|
42 |
try {
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
if (!response.ok) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
throw new Error(`GitHub API error: ${response.statusText}`);
|
47 |
}
|
48 |
|
49 |
-
|
|
|
|
|
|
|
50 |
} catch (error) {
|
51 |
console.error('Failed to get GitHub info:', error);
|
52 |
return null;
|
@@ -55,6 +84,7 @@ const getGitHubInfo = async (repoFullName: string) => {
|
|
55 |
|
56 |
export const loader: LoaderFunction = async ({ request: _request }) => {
|
57 |
const localInfo = getLocalGitInfo();
|
|
|
58 |
|
59 |
// If we have local info, try to get GitHub info for both our fork and upstream
|
60 |
let githubInfo = null;
|
@@ -68,7 +98,7 @@ export const loader: LoaderFunction = async ({ request: _request }) => {
|
|
68 |
githubInfo = await getGitHubInfo('stackblitz-labs/bolt.diy');
|
69 |
}
|
70 |
|
71 |
-
|
72 |
local: localInfo || {
|
73 |
commitHash: 'unknown',
|
74 |
branch: 'unknown',
|
@@ -99,5 +129,10 @@ export const loader: LoaderFunction = async ({ request: _request }) => {
|
|
99 |
: null,
|
100 |
isForked: Boolean(githubInfo?.parent),
|
101 |
timestamp: new Date().toISOString(),
|
102 |
-
}
|
|
|
|
|
|
|
|
|
|
|
103 |
};
|
|
|
20 |
const getLocalGitInfo = () => {
|
21 |
try {
|
22 |
return {
|
23 |
+
commitHash: execSync('git rev-parse HEAD').toString().trim(),
|
24 |
branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(),
|
25 |
commitTime: execSync('git log -1 --format=%cd').toString().trim(),
|
26 |
author: execSync('git log -1 --format=%an').toString().trim(),
|
|
|
40 |
|
41 |
const getGitHubInfo = async (repoFullName: string) => {
|
42 |
try {
|
43 |
+
// Add GitHub token if available
|
44 |
+
const headers: Record<string, string> = {
|
45 |
+
Accept: 'application/vnd.github.v3+json',
|
46 |
+
};
|
47 |
+
|
48 |
+
const githubToken = process.env.GITHUB_TOKEN;
|
49 |
+
|
50 |
+
if (githubToken) {
|
51 |
+
headers.Authorization = `token ${githubToken}`;
|
52 |
+
}
|
53 |
+
|
54 |
+
console.log('Fetching GitHub info for:', repoFullName); // Debug log
|
55 |
+
|
56 |
+
const response = await fetch(`https://api.github.com/repos/${repoFullName}`, {
|
57 |
+
headers,
|
58 |
+
});
|
59 |
|
60 |
if (!response.ok) {
|
61 |
+
console.error('GitHub API error:', {
|
62 |
+
status: response.status,
|
63 |
+
statusText: response.statusText,
|
64 |
+
repoFullName,
|
65 |
+
});
|
66 |
+
|
67 |
+
// If we get a 404, try the main repo as fallback
|
68 |
+
if (response.status === 404 && repoFullName !== 'stackblitz-labs/bolt.diy') {
|
69 |
+
return getGitHubInfo('stackblitz-labs/bolt.diy');
|
70 |
+
}
|
71 |
+
|
72 |
throw new Error(`GitHub API error: ${response.statusText}`);
|
73 |
}
|
74 |
|
75 |
+
const data = await response.json();
|
76 |
+
console.log('GitHub API response:', data); // Debug log
|
77 |
+
|
78 |
+
return data as GitHubRepoInfo;
|
79 |
} catch (error) {
|
80 |
console.error('Failed to get GitHub info:', error);
|
81 |
return null;
|
|
|
84 |
|
85 |
export const loader: LoaderFunction = async ({ request: _request }) => {
|
86 |
const localInfo = getLocalGitInfo();
|
87 |
+
console.log('Local git info:', localInfo); // Debug log
|
88 |
|
89 |
// If we have local info, try to get GitHub info for both our fork and upstream
|
90 |
let githubInfo = null;
|
|
|
98 |
githubInfo = await getGitHubInfo('stackblitz-labs/bolt.diy');
|
99 |
}
|
100 |
|
101 |
+
const response = {
|
102 |
local: localInfo || {
|
103 |
commitHash: 'unknown',
|
104 |
branch: 'unknown',
|
|
|
129 |
: null,
|
130 |
isForked: Boolean(githubInfo?.parent),
|
131 |
timestamp: new Date().toISOString(),
|
132 |
+
};
|
133 |
+
|
134 |
+
console.log('Final response:', response);
|
135 |
+
|
136 |
+
// Debug log
|
137 |
+
return json(response);
|
138 |
};
|
@@ -1,38 +0,0 @@
|
|
1 |
-
import type { NextApiRequest, NextApiResponse } from 'next';
|
2 |
-
import { execSync } from 'child_process';
|
3 |
-
|
4 |
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
5 |
-
if (req.method !== 'GET') {
|
6 |
-
return res.status(405).json({ message: 'Method not allowed' });
|
7 |
-
}
|
8 |
-
|
9 |
-
try {
|
10 |
-
// Get git information using git commands
|
11 |
-
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
12 |
-
const commitHash = execSync('git rev-parse HEAD').toString().trim();
|
13 |
-
const commitTime = execSync('git log -1 --format=%cd').toString().trim();
|
14 |
-
const author = execSync('git log -1 --format=%an').toString().trim();
|
15 |
-
const email = execSync('git log -1 --format=%ae').toString().trim();
|
16 |
-
const remoteUrl = execSync('git config --get remote.origin.url').toString().trim();
|
17 |
-
|
18 |
-
// Extract repo name from remote URL
|
19 |
-
const repoName = remoteUrl.split('/').pop()?.replace('.git', '') || '';
|
20 |
-
|
21 |
-
const gitInfo = {
|
22 |
-
local: {
|
23 |
-
commitHash,
|
24 |
-
branch,
|
25 |
-
commitTime,
|
26 |
-
author,
|
27 |
-
email,
|
28 |
-
remoteUrl,
|
29 |
-
repoName,
|
30 |
-
},
|
31 |
-
};
|
32 |
-
|
33 |
-
return res.status(200).json(gitInfo);
|
34 |
-
} catch (error) {
|
35 |
-
console.error('Failed to get git information:', error);
|
36 |
-
return res.status(500).json({ message: 'Failed to get git information' });
|
37 |
-
}
|
38 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|