-
Notifications
You must be signed in to change notification settings - Fork 0
Add disk usage monitoring panel #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -228,6 +228,62 @@ async function getMemoryInfo(): Promise<{ total: number; free: number; used: num | |||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface DiskInfo { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| filesystem: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| size: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| used: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| available: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use_percent: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mounted: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function getDiskInfo(): Promise<DiskInfo[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const os = platform(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| let cmd: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (os === "darwin") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd = "df -h"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd = "df -h"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { stdout } = await execAsync(cmd); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lines = stdout.trim().split("\n"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const diskList: DiskInfo[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // skip header | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 1; i < lines.length; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const line = lines[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parts = line.trim().split(/\s+/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (parts.length >= 6) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const filesystem = parts[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // filter out virtual filesystems | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (filesystem.startsWith("/dev") || filesystem.includes("disk")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const percentStr = parts[4].replace("%", ""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const percent = parseInt(percentStr); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| diskList.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| filesystem: filesystem, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| size: parts[1], | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| used: parts[2], | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| available: parts[3], | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| use_percent: percent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mounted: parts[5], | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+262
to
+275
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return all mounted filesystems; current filter drops valid entries. The PR objective says “all mounted filesystems,” but filtering to 💡 Suggested adjustment- // filter out virtual filesystems
- if (filesystem.startsWith("/dev") || filesystem.includes("disk")) {
- const percentStr = parts[4].replace("%", "");
- const percent = parseInt(percentStr);
-
- diskList.push({
- filesystem: filesystem,
- size: parts[1],
- used: parts[2],
- available: parts[3],
- use_percent: percent,
- mounted: parts[5],
- });
- }
+ const percentStr = parts[4].replace("%", "");
+ const percent = parseInt(percentStr);
+
+ diskList.push({
+ filesystem: filesystem,
+ size: parts[1],
+ used: parts[2],
+ available: parts[3],
+ use_percent: percent,
+ mounted: parts[5],
+ });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Disk info fetched:", diskList.length, "disks found"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return diskList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Error getting disk info:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function getSystemMetrics(): Promise<SystemMetrics> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const cpuInfo = cpus(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const memInfo = await getMemoryInfo(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -287,6 +343,16 @@ const server = Bun.serve({ | |||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (url.pathname === "/api/disks") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const diskInfo = await getDiskInfo(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Response(JSON.stringify({ disks: diskInfo }), { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": "application/json", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...corsHeaders, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (url.pathname === "/api/environment") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Return filtered environment variables for system diagnostics | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only expose safe, non-sensitive variables | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -704,3 +704,110 @@ body { | |||||||||||||||||||||||||||||||||||||
| max-height: 200px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /* Disk Usage Panel */ | ||||||||||||||||||||||||||||||||||||||
| .disk-usage { | ||||||||||||||||||||||||||||||||||||||
| background: var(--bg-card); | ||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border-color); | ||||||||||||||||||||||||||||||||||||||
| border-radius: 12px; | ||||||||||||||||||||||||||||||||||||||
| padding: 16px; | ||||||||||||||||||||||||||||||||||||||
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); | ||||||||||||||||||||||||||||||||||||||
| grid-column: span 2; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+708
to
+716
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n src/App.css | sed -n '700,720p'Repository: coderabbit-demo/btop Length of output: 561 🏁 Script executed: # Search for metrics-panel and related grid definitions
rg -A 5 -B 5 "metrics-panel|disk-usage" src/App.css | head -100Repository: coderabbit-demo/btop Length of output: 725 🏁 Script executed: # Look for media queries that might affect the grid layout
rg "max-width.*1200px|@media" src/App.css | head -20Repository: coderabbit-demo/btop Length of output: 120 🏁 Script executed: # Check the full context and structure around disk-usage
rg -B 15 "\.disk-usage" src/App.css | head -50Repository: coderabbit-demo/btop Length of output: 450 🏁 Script executed: # Look for any additional disk-usage rules or media query overrides
rg "\.disk-usage" src/App.cssRepository: coderabbit-demo/btop Length of output: 97 🏁 Script executed: # Check the 800px media query context to see if disk-usage needs adjustment there too
rg -A 20 "max-width.*800px" src/App.cssRepository: coderabbit-demo/btop Length of output: 340 Use When 🎛️ Suggested fix .disk-usage {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
- grid-column: span 2;
+ grid-column: 1 / -1;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-usage.error { | ||||||||||||||||||||||||||||||||||||||
| color: var(--color-red); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-header { | ||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||
| justify-content: space-between; | ||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||
| margin-bottom: 12px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-title { | ||||||||||||||||||||||||||||||||||||||
| color: var(--color-purple); | ||||||||||||||||||||||||||||||||||||||
| font-weight: 700; | ||||||||||||||||||||||||||||||||||||||
| font-size: 13px; | ||||||||||||||||||||||||||||||||||||||
| text-transform: uppercase; | ||||||||||||||||||||||||||||||||||||||
| letter-spacing: 2px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-total { | ||||||||||||||||||||||||||||||||||||||
| font-size: 14px; | ||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-list { | ||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||
| gap: 8px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-item { | ||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||
| gap: 12px; | ||||||||||||||||||||||||||||||||||||||
| padding: 8px 12px; | ||||||||||||||||||||||||||||||||||||||
| background: rgba(0, 0, 0, 0.2); | ||||||||||||||||||||||||||||||||||||||
| border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-info { | ||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||
| min-width: 140px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-mount { | ||||||||||||||||||||||||||||||||||||||
| color: var(--color-cyan); | ||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||
| font-size: 11px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-size { | ||||||||||||||||||||||||||||||||||||||
| color: var(--color-text-dim); | ||||||||||||||||||||||||||||||||||||||
| font-size: 10px; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-bar-container { | ||||||||||||||||||||||||||||||||||||||
| flex: 1; | ||||||||||||||||||||||||||||||||||||||
| height: 6px; | ||||||||||||||||||||||||||||||||||||||
| background: rgba(0, 0, 0, 0.4); | ||||||||||||||||||||||||||||||||||||||
| border-radius: 3px; | ||||||||||||||||||||||||||||||||||||||
| overflow: hidden; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-bar { | ||||||||||||||||||||||||||||||||||||||
| height: 100%; | ||||||||||||||||||||||||||||||||||||||
| border-radius: 3px; | ||||||||||||||||||||||||||||||||||||||
| transition: width 0.3s ease; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-percent { | ||||||||||||||||||||||||||||||||||||||
| font-weight: 600; | ||||||||||||||||||||||||||||||||||||||
| font-size: 12px; | ||||||||||||||||||||||||||||||||||||||
| min-width: 40px; | ||||||||||||||||||||||||||||||||||||||
| text-align: right; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-expand-btn { | ||||||||||||||||||||||||||||||||||||||
| display: block; | ||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||
| margin-top: 10px; | ||||||||||||||||||||||||||||||||||||||
| padding: 8px; | ||||||||||||||||||||||||||||||||||||||
| background: rgba(167, 139, 250, 0.1); | ||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border-color); | ||||||||||||||||||||||||||||||||||||||
| border-radius: 6px; | ||||||||||||||||||||||||||||||||||||||
| color: var(--color-purple); | ||||||||||||||||||||||||||||||||||||||
| font-family: var(--font-mono); | ||||||||||||||||||||||||||||||||||||||
| font-size: 11px; | ||||||||||||||||||||||||||||||||||||||
| cursor: pointer; | ||||||||||||||||||||||||||||||||||||||
| transition: background 0.2s, border-color 0.2s; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| .disk-expand-btn:hover { | ||||||||||||||||||||||||||||||||||||||
| background: rgba(167, 139, 250, 0.2); | ||||||||||||||||||||||||||||||||||||||
| border-color: var(--color-purple); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| import { useState, useEffect } from 'react'; | ||
|
|
||
| interface DiskInfo { | ||
| filesystem: string; | ||
| size: string; | ||
| used: string; | ||
| available: string; | ||
| use_percent: number; | ||
| mounted: string; | ||
| } | ||
|
|
||
| interface DiskUsageProps { | ||
| refreshRate: number; | ||
| } | ||
|
|
||
| export function DiskUsage({ refreshRate }: DiskUsageProps) { | ||
| const [disks, setDisks] = useState<DiskInfo[]>([]); | ||
| const [loading, setLoading] = useState(true); | ||
| const [error, setError] = useState<string | null>(null); | ||
| const [expanded, setExpanded] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const fetchDiskInfo = async () => { | ||
| try { | ||
| const response = await fetch('http://localhost:3001/api/disks'); | ||
| if (!response.ok) { | ||
| throw new Error('Failed to fetch disk info'); | ||
| } | ||
| const data = await response.json(); | ||
| setDisks(data.disks); | ||
| setLoading(false); | ||
| setError(null); | ||
| } catch (err) { | ||
| console.log('Error fetching disk info:', err); | ||
| setError('Failed to load disk info'); | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| fetchDiskInfo(); | ||
| const interval = setInterval(fetchDiskInfo, refreshRate); | ||
| return () => clearInterval(interval); | ||
| }, [refreshRate]); | ||
|
Comment on lines
+22
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded URL and missing fetch abort.
Suggested improvements useEffect(() => {
+ const controller = new AbortController();
+
const fetchDiskInfo = async () => {
try {
- const response = await fetch('http://localhost:3001/api/disks');
+ const response = await fetch('/api/disks', { signal: controller.signal });
if (!response.ok) {
throw new Error('Failed to fetch disk info');
}
const data = await response.json();
setDisks(data.disks);
setLoading(false);
setError(null);
} catch (err) {
- console.log('Error fetching disk info:', err);
- setError('Failed to load disk info');
- setLoading(false);
+ if (err instanceof Error && err.name !== 'AbortError') {
+ console.error('Error fetching disk info:', err);
+ setError('Failed to load disk info');
+ setLoading(false);
+ }
}
};
fetchDiskInfo();
const interval = setInterval(fetchDiskInfo, refreshRate);
- return () => clearInterval(interval);
+ return () => {
+ controller.abort();
+ clearInterval(interval);
+ };
}, [refreshRate]);🤖 Prompt for AI Agents |
||
|
|
||
| // Get color based on usage percentage | ||
| const getUsageColor = (percent: number) => { | ||
| if (percent < 50) { | ||
| return '#34d399'; | ||
| } else if (percent < 75) { | ||
| return '#fbbf24'; | ||
| } else if (percent < 90) { | ||
| return '#fb923c'; | ||
| } else { | ||
| return '#f87171'; | ||
| } | ||
| }; | ||
|
|
||
| // Calculate total disk stats | ||
| const calculateTotals = () => { | ||
| let totalUsed = 0; | ||
| let totalSize = 0; | ||
|
|
||
| for (let i = 0; i < disks.length; i++) { | ||
| const disk = disks[i]; | ||
| // Parse size strings to get numeric values | ||
| const sizeNum = parseSize(disk.size); | ||
| const usedNum = parseSize(disk.used); | ||
| totalSize = totalSize + sizeNum; | ||
| totalUsed = totalUsed + usedNum; | ||
| } | ||
|
|
||
| return { totalUsed, totalSize }; | ||
| }; | ||
|
|
||
| // TODO: Move this to a utility file | ||
| const parseSize = (sizeStr: string): number => { | ||
| const match = sizeStr.match(/^([\d.]+)([KMGTP]?)i?$/i); | ||
| if (!match) return 0; | ||
|
|
||
| const value = parseFloat(match[1]); | ||
| const unit = match[2].toUpperCase(); | ||
|
|
||
| const multipliers: { [key: string]: number } = { | ||
| '': 1, | ||
| 'K': 1024, | ||
| 'M': 1024 * 1024, | ||
| 'G': 1024 * 1024 * 1024, | ||
| 'T': 1024 * 1024 * 1024 * 1024, | ||
| 'P': 1024 * 1024 * 1024 * 1024 * 1024, | ||
| }; | ||
|
|
||
| return value * (multipliers[unit] || 1); | ||
| }; | ||
|
|
||
| const formatTotalSize = (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)); | ||
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | ||
| }; | ||
|
|
||
| if (loading) { | ||
| return <div className="disk-usage">Loading disk info...</div>; | ||
| } | ||
|
|
||
| if (error) { | ||
| return <div className="disk-usage error">{error}</div>; | ||
| } | ||
|
|
||
| const totals = calculateTotals(); | ||
| const totalPercent = totals.totalSize > 0 ? Math.round((totals.totalUsed / totals.totalSize) * 100) : 0; | ||
| const displayDisks = expanded ? disks : disks.slice(0, 3); | ||
|
|
||
| return ( | ||
| <div className="disk-usage"> | ||
| <div className="disk-header"> | ||
| <span className="disk-title">DISK</span> | ||
| <span className="disk-total" style={{ color: getUsageColor(totalPercent) }}> | ||
| {formatTotalSize(totals.totalUsed)} / {formatTotalSize(totals.totalSize)} ({totalPercent}%) | ||
| </span> | ||
| </div> | ||
|
|
||
| <div className="disk-list"> | ||
| {displayDisks.map((disk, index) => ( | ||
| <div key={index} className="disk-item"> | ||
| <div className="disk-info"> | ||
| <span className="disk-mount">{disk.mounted}</span> | ||
| <span className="disk-size">{disk.used} / {disk.size}</span> | ||
| </div> | ||
| <div className="disk-bar-container"> | ||
| <div | ||
| className="disk-bar" | ||
| style={{ | ||
| width: `${disk.use_percent}%`, | ||
| backgroundColor: getUsageColor(disk.use_percent) | ||
| }} | ||
| /> | ||
| </div> | ||
| <span className="disk-percent" style={{ color: getUsageColor(disk.use_percent) }}> | ||
| {disk.use_percent}% | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
|
|
||
| {disks.length > 3 && ( | ||
| <button | ||
| className="disk-expand-btn" | ||
| onClick={() => setExpanded(!expanded)} | ||
| > | ||
| {expanded ? 'Show less' : `Show all (${disks.length})`} | ||
| </button> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve mount paths that contain spaces.
Splitting on whitespace and using
parts[5]truncates mountpoints with spaces (e.g., “/Volumes/My Drive”). Consider joining the tail for the mount path.🛠️ Suggested parsing fix
🤖 Prompt for AI Agents