Compare commits
1 commit
main
...
fresh-rele
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aa491978a |
4 changed files with 184 additions and 124 deletions
|
|
@ -8,7 +8,8 @@ A modern, high-performance system optimizer for macOS, built with **Electron**,
|
||||||
- **Flash Clean**: Instantly remove system caches, logs, Xcode cache, Homebrew cache, and manage Trash with a detailed inspection view.
|
- **Flash Clean**: Instantly remove system caches, logs, Xcode cache, Homebrew cache, and manage Trash with a detailed inspection view.
|
||||||
- **App Uninstaller**: View installed applications, their sizes, and thoroughly remove them along with their associated preference files and caches.
|
- **App Uninstaller**: View installed applications, their sizes, and thoroughly remove them along with their associated preference files and caches.
|
||||||
- **Deep Clean**: Scan for large files and heavy folders.
|
- **Deep Clean**: Scan for large files and heavy folders.
|
||||||
- **Real-time Monitoring**: Track disk usage and category sizes.
|
- **Real-time Monitoring**: Track disk usage and category sizes with accurate categorical percentages.
|
||||||
|
- **Web App Support**: Access the fully functional dashboard remotely or locally via any standard web browser at `http://localhost:36969` (or your NAS host IP).
|
||||||
- **Native Menubar Integration**: Includes a responsive, monochrome template icon that adapts to macOS light/dark modes perfectly.
|
- **Native Menubar Integration**: Includes a responsive, monochrome template icon that adapts to macOS light/dark modes perfectly.
|
||||||
- **Cross-Platform**: Runs natively with compiled Go backends on Apple Silicon (M1/M2/M3), Intel Macs, and Windows.
|
- **Cross-Platform**: Runs natively with compiled Go backends on Apple Silicon (M1/M2/M3), Intel Macs, and Windows.
|
||||||
|
|
||||||
|
|
@ -39,10 +40,10 @@ To create distributable release binaries (Universal `.dmg` for macOS, Portable `
|
||||||
### 1. Build the App
|
### 1. Build the App
|
||||||
```bash
|
```bash
|
||||||
# macOS Universal DMG
|
# macOS Universal DMG
|
||||||
pnpm run build && pnpm run electron:build && npx electron-builder --mac --universal
|
pnpm run build:mac
|
||||||
|
|
||||||
# Windows Portable EXE
|
# Windows Portable EXE
|
||||||
pnpm run build && pnpm run electron:build && npx electron-builder --win portable --x64
|
pnpm run build:win
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Locate the Installer
|
### 2. Locate the Installer
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,24 @@ func main() {
|
||||||
http.HandleFunc("/api/apps/action", handleAppAction)
|
http.HandleFunc("/api/apps/action", handleAppAction)
|
||||||
http.HandleFunc("/api/apps/uninstall", handleAppUninstall)
|
http.HandleFunc("/api/apps/uninstall", handleAppUninstall)
|
||||||
|
|
||||||
// Static File Serving is handled directly by Electron.
|
// Serve Static Files for Web Mode
|
||||||
// Backend only needs to provide API routes.
|
// This serves the built Vite frontend from the `dist` folder.
|
||||||
|
distPath := filepath.Join("..", "dist")
|
||||||
|
if _, err := os.Stat("dist"); err == nil {
|
||||||
|
distPath = "dist" // If running directly from the project root
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := http.FileServer(http.Dir(distPath))
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// If the requested file exists, serve it
|
||||||
|
path := filepath.Join(distPath, r.URL.Path)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
fs.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Otherwise, fallback to index.html for SPA routing
|
||||||
|
http.ServeFile(w, r, filepath.Join(distPath, "index.html"))
|
||||||
|
})
|
||||||
|
|
||||||
fmt.Printf("🚀 Antigravity Backend running on http://localhost%s\n", Port)
|
fmt.Printf("🚀 Antigravity Backend running on http://localhost%s\n", Port)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -414,13 +414,14 @@ export function Dashboard() {
|
||||||
{isSystemDrive && segments.length > 0 ? (
|
{isSystemDrive && segments.length > 0 ? (
|
||||||
segments.map((seg, i) => {
|
segments.map((seg, i) => {
|
||||||
const width = total > 0 ? (seg.size / total) * 100 : 0;
|
const width = total > 0 ? (seg.size / total) * 100 : 0;
|
||||||
|
const segUsedPercent = used > 0 ? (seg.size / used) * 100 : 0;
|
||||||
if (width < 0.5) return null; // Hide tiny segments
|
if (width < 0.5) return null; // Hide tiny segments
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={`h-full bg-gradient-to-b ${seg.color} border-r border-white/20 last:border-0`}
|
className={`h-full bg-gradient-to-b ${seg.color} border-r border-white/20 last:border-0`}
|
||||||
style={{ width: `${width}%` }}
|
style={{ width: `${width}%` }}
|
||||||
title={`${seg.label}: ${seg.size.toFixed(2)} GB`}
|
title={`${seg.label}: ${seg.size.toFixed(2)} GB (${width.toFixed(1)}% of total, ${segUsedPercent.toFixed(1)}% of used)`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
@ -436,12 +437,18 @@ export function Dashboard() {
|
||||||
{/* Legend for System Drive */}
|
{/* Legend for System Drive */}
|
||||||
{isSystemDrive && segments.length > 0 && (
|
{isSystemDrive && segments.length > 0 && (
|
||||||
<div className="flex flex-wrap gap-x-4 gap-y-2 mt-3">
|
<div className="flex flex-wrap gap-x-4 gap-y-2 mt-3">
|
||||||
{segments.map((seg, i) => seg.size > 0.1 && (
|
{segments.map((seg, i) => {
|
||||||
<div key={i} className="flex items-center gap-1.5">
|
if (seg.size <= 0.1) return null;
|
||||||
<div className={`w-2.5 h-2.5 rounded-full ${seg.legendColor} shadow-sm`} />
|
const width = total > 0 ? (seg.size / total) * 100 : 0;
|
||||||
<span className="text-[11px] font-medium text-gray-600">{seg.label}</span>
|
return (
|
||||||
</div>
|
<div key={i} className="flex items-center gap-1.5">
|
||||||
))}
|
<div className={`w-2.5 h-2.5 rounded-full ${seg.legendColor} shadow-sm`} />
|
||||||
|
<span className="text-[11px] font-medium text-gray-600">
|
||||||
|
{seg.label} {width > 0 && `(${width.toFixed(1)}%)`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -481,118 +488,146 @@ export function Dashboard() {
|
||||||
<div className="liquid-glass-heavy rounded-[var(--radius-lg)] overflow-hidden flex flex-col min-h-0 relative border border-white/20 dark:border-white/10 shadow-lg max-h-full">
|
<div className="liquid-glass-heavy rounded-[var(--radius-lg)] overflow-hidden flex flex-col min-h-0 relative border border-white/20 dark:border-white/10 shadow-lg max-h-full">
|
||||||
<div className="overflow-y-auto flex-1 stagger-children py-3 px-3">
|
<div className="overflow-y-auto flex-1 stagger-children py-3 px-3">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||||
<CategoryRow
|
{categorySizes.apps !== 0 && (
|
||||||
icon={<Icons.Apps />}
|
<CategoryRow
|
||||||
label="Applications"
|
icon={<Icons.Apps />}
|
||||||
size={categorySizes.apps >= 0 ? formatBytes(categorySizes.apps) : "Not Scanned"}
|
label="Applications"
|
||||||
onClick={() => runScan('apps', 'Applications')}
|
size={categorySizes.apps >= 0 ? formatBytes(categorySizes.apps) : "Not Scanned"}
|
||||||
actionIcon
|
onClick={() => runScan('apps', 'Applications')}
|
||||||
description="Installed apps & support files"
|
actionIcon
|
||||||
/>
|
description="Installed apps & support files"
|
||||||
<CategoryRow
|
/>
|
||||||
icon={<Icons.Documents />}
|
)}
|
||||||
label="Documents"
|
{categorySizes.documents !== 0 && (
|
||||||
size={categorySizes.documents >= 0 ? formatBytes(categorySizes.documents) : "Not Scanned"}
|
<CategoryRow
|
||||||
onClick={() => runScan('docs', 'Documents')}
|
icon={<Icons.Documents />}
|
||||||
actionIcon
|
label="Documents"
|
||||||
description="Personal documents folder"
|
size={categorySizes.documents >= 0 ? formatBytes(categorySizes.documents) : "Not Scanned"}
|
||||||
/>
|
onClick={() => runScan('docs', 'Documents')}
|
||||||
<CategoryRow
|
actionIcon
|
||||||
icon={<Icons.Archives />}
|
description="Personal documents folder"
|
||||||
label="Archives"
|
/>
|
||||||
size={categorySizes.archives !== undefined && categorySizes.archives >= 0 ? formatBytes(categorySizes.archives) : "Not Scanned"}
|
)}
|
||||||
onClick={() => runScan('archives', 'Archives')}
|
{categorySizes.archives !== 0 && (
|
||||||
actionIcon
|
<CategoryRow
|
||||||
description="Compressed files (.zip, .rar, .7z)"
|
icon={<Icons.Archives />}
|
||||||
/>
|
label="Archives"
|
||||||
<CategoryRow
|
size={categorySizes.archives !== undefined && categorySizes.archives >= 0 ? formatBytes(categorySizes.archives) : "Not Scanned"}
|
||||||
icon={<Icons.Downloads />}
|
onClick={() => runScan('archives', 'Archives')}
|
||||||
label="Downloads"
|
actionIcon
|
||||||
size={categorySizes.downloads >= 0 ? formatBytes(categorySizes.downloads) : "Not Scanned"}
|
description="Compressed files (.zip, .rar, .7z)"
|
||||||
onClick={() => runScan('downloads', 'Downloads')}
|
/>
|
||||||
actionIcon
|
)}
|
||||||
description="Downloads folder items"
|
{categorySizes.downloads !== 0 && (
|
||||||
/>
|
<CategoryRow
|
||||||
<CategoryRow
|
icon={<Icons.Downloads />}
|
||||||
icon={<Icons.Desktop />}
|
label="Downloads"
|
||||||
label="Desktop"
|
size={categorySizes.downloads >= 0 ? formatBytes(categorySizes.downloads) : "Not Scanned"}
|
||||||
size={categorySizes.desktop >= 0 ? formatBytes(categorySizes.desktop) : "Not Scanned"}
|
onClick={() => runScan('downloads', 'Downloads')}
|
||||||
onClick={() => runScan('desktop', 'Desktop Items')}
|
actionIcon
|
||||||
actionIcon
|
description="Downloads folder items"
|
||||||
description="Files on your desktop"
|
/>
|
||||||
/>
|
)}
|
||||||
<CategoryRow
|
{categorySizes.desktop !== 0 && (
|
||||||
icon={<Icons.Music />}
|
<CategoryRow
|
||||||
label="Music"
|
icon={<Icons.Desktop />}
|
||||||
size={categorySizes.music >= 0 ? formatBytes(categorySizes.music) : "Not Scanned"}
|
label="Desktop"
|
||||||
onClick={() => runScan('music', 'Music Library')}
|
size={categorySizes.desktop >= 0 ? formatBytes(categorySizes.desktop) : "Not Scanned"}
|
||||||
actionIcon
|
onClick={() => runScan('desktop', 'Desktop Items')}
|
||||||
description="iTunes/Music library & files"
|
actionIcon
|
||||||
/>
|
description="Files on your desktop"
|
||||||
<CategoryRow
|
/>
|
||||||
icon={<Icons.Movies />}
|
)}
|
||||||
label="Movies"
|
{categorySizes.music !== 0 && (
|
||||||
size={categorySizes.movies >= 0 ? formatBytes(categorySizes.movies) : "Not Scanned"}
|
<CategoryRow
|
||||||
onClick={() => runScan('movies', 'Movies & Videos')}
|
icon={<Icons.Music />}
|
||||||
actionIcon
|
label="Music"
|
||||||
description="Movies folder & video files"
|
size={categorySizes.music >= 0 ? formatBytes(categorySizes.music) : "Not Scanned"}
|
||||||
/>
|
onClick={() => runScan('music', 'Music Library')}
|
||||||
<CategoryRow
|
actionIcon
|
||||||
icon={<Icons.VirtualMachines />}
|
description="iTunes/Music library & files"
|
||||||
label="Disk Images"
|
/>
|
||||||
size={categorySizes.virtual_machines !== undefined && categorySizes.virtual_machines >= 0 ? formatBytes(categorySizes.virtual_machines) : "Not Scanned"}
|
)}
|
||||||
onClick={() => runScan('vms', 'Disk Images')}
|
{categorySizes.movies !== 0 && (
|
||||||
actionIcon
|
<CategoryRow
|
||||||
description="ISOs, VM disks, & installers"
|
icon={<Icons.Movies />}
|
||||||
/>
|
label="Movies"
|
||||||
<CategoryRow
|
size={categorySizes.movies >= 0 ? formatBytes(categorySizes.movies) : "Not Scanned"}
|
||||||
icon={<Icons.Games />}
|
onClick={() => runScan('movies', 'Movies & Videos')}
|
||||||
label="Games"
|
actionIcon
|
||||||
size={categorySizes.games !== undefined && categorySizes.games >= 0 ? formatBytes(categorySizes.games) : "Not Scanned"}
|
description="Movies folder & video files"
|
||||||
onClick={() => runScan('games', 'Games Libraries')}
|
/>
|
||||||
actionIcon
|
)}
|
||||||
description="Steam, Epic, EA, Ubisoft"
|
{categorySizes.virtual_machines !== 0 && (
|
||||||
/>
|
<CategoryRow
|
||||||
<CategoryRow
|
icon={<Icons.VirtualMachines />}
|
||||||
icon={<Icons.AI />}
|
label="Disk Images"
|
||||||
label="AI Models"
|
size={categorySizes.virtual_machines !== undefined && categorySizes.virtual_machines >= 0 ? formatBytes(categorySizes.virtual_machines) : "Not Scanned"}
|
||||||
size={categorySizes.ai !== undefined && categorySizes.ai >= 0 ? formatBytes(categorySizes.ai) : "Not Scanned"}
|
onClick={() => runScan('vms', 'Disk Images')}
|
||||||
onClick={() => runScan('ai', 'AI Tools')}
|
actionIcon
|
||||||
actionIcon
|
description="ISOs, VM disks, & installers"
|
||||||
description="ComfyUI, WebUI, Checkpoints"
|
/>
|
||||||
/>
|
)}
|
||||||
<CategoryRow
|
{categorySizes.games !== 0 && (
|
||||||
icon={<Icons.Docker />}
|
<CategoryRow
|
||||||
label="Docker"
|
icon={<Icons.Games />}
|
||||||
size={categorySizes.docker !== undefined && categorySizes.docker >= 0 ? formatBytes(categorySizes.docker) : "Not Scanned"}
|
label="Games"
|
||||||
onClick={() => runScan('docker', 'Docker Data')}
|
size={categorySizes.games !== undefined && categorySizes.games >= 0 ? formatBytes(categorySizes.games) : "Not Scanned"}
|
||||||
actionIcon
|
onClick={() => runScan('games', 'Games Libraries')}
|
||||||
description="Docker Desktop disk usage"
|
actionIcon
|
||||||
/>
|
description="Steam, Epic, EA, Ubisoft"
|
||||||
<CategoryRow
|
/>
|
||||||
icon={<Icons.Cache />}
|
)}
|
||||||
label="System Cache"
|
{categorySizes.ai !== 0 && (
|
||||||
size={categorySizes.cache !== undefined && categorySizes.cache >= 0 ? formatBytes(categorySizes.cache) : "Not Scanned"}
|
<CategoryRow
|
||||||
onClick={() => runScan('cache', 'Cache & Temp')}
|
icon={<Icons.AI />}
|
||||||
actionIcon
|
label="AI Models"
|
||||||
description="Browser & System temporary files"
|
size={categorySizes.ai !== undefined && categorySizes.ai >= 0 ? formatBytes(categorySizes.ai) : "Not Scanned"}
|
||||||
/>
|
onClick={() => runScan('ai', 'AI Tools')}
|
||||||
<CategoryRow
|
actionIcon
|
||||||
icon={<Icons.CloudDrive />}
|
description="ComfyUI, WebUI, Checkpoints"
|
||||||
label="iCloud Drive"
|
/>
|
||||||
size={categorySizes.icloud >= 0 ? formatBytes(categorySizes.icloud) : "Not Scanned"}
|
)}
|
||||||
onClick={() => runScan('icloud', 'iCloud Drive')}
|
{categorySizes.docker !== 0 && (
|
||||||
actionIcon
|
<CategoryRow
|
||||||
description="Local iCloud file copies"
|
icon={<Icons.Docker />}
|
||||||
/>
|
label="Docker"
|
||||||
<CategoryRow
|
size={categorySizes.docker !== undefined && categorySizes.docker >= 0 ? formatBytes(categorySizes.docker) : "Not Scanned"}
|
||||||
icon={<Icons.Photos />}
|
onClick={() => runScan('docker', 'Docker Data')}
|
||||||
label="Photos"
|
actionIcon
|
||||||
size={categorySizes.photos >= 0 ? formatBytes(categorySizes.photos) : "Not Scanned"}
|
description="Docker Desktop disk usage"
|
||||||
onClick={() => runScan('photos', 'Photos')}
|
/>
|
||||||
actionIcon
|
)}
|
||||||
description="Photos library caches"
|
{categorySizes.cache !== 0 && (
|
||||||
/>
|
<CategoryRow
|
||||||
|
icon={<Icons.Cache />}
|
||||||
|
label="System Cache"
|
||||||
|
size={categorySizes.cache !== undefined && categorySizes.cache >= 0 ? formatBytes(categorySizes.cache) : "Not Scanned"}
|
||||||
|
onClick={() => runScan('cache', 'Cache & Temp')}
|
||||||
|
actionIcon
|
||||||
|
description="Browser & System temporary files"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{categorySizes.icloud !== 0 && (
|
||||||
|
<CategoryRow
|
||||||
|
icon={<Icons.CloudDrive />}
|
||||||
|
label="iCloud Drive"
|
||||||
|
size={categorySizes.icloud >= 0 ? formatBytes(categorySizes.icloud) : "Not Scanned"}
|
||||||
|
onClick={() => runScan('icloud', 'iCloud Drive')}
|
||||||
|
actionIcon
|
||||||
|
description="Local iCloud file copies"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{categorySizes.photos !== 0 && (
|
||||||
|
<CategoryRow
|
||||||
|
icon={<Icons.Photos />}
|
||||||
|
label="Photos"
|
||||||
|
size={categorySizes.photos >= 0 ? formatBytes(categorySizes.photos) : "Not Scanned"}
|
||||||
|
onClick={() => runScan('photos', 'Photos')}
|
||||||
|
actionIcon
|
||||||
|
description="Photos library caches"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<CategoryRow
|
<CategoryRow
|
||||||
icon={<Icons.Trash />}
|
icon={<Icons.Trash />}
|
||||||
label="Trash"
|
label="Trash"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,14 @@ import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
||||||
|
for (let registration of registrations) {
|
||||||
|
registration.unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue