appclean 1.9.0 → 2.0.2
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/.github/workflows/npm-publish.yml +61 -0
- package/DEVELOPMENT.md +84 -0
- package/GUI_IMPLEMENTATION_STATUS.md +143 -0
- package/MD_Files/INDEX.md +51 -0
- package/PHASE2_COMPLETION.md +281 -0
- package/PHASE3_COMPLETION.md +364 -0
- package/README.md +411 -91
- package/RELEASE_GUIDE.md +236 -0
- package/assets/logo.svg +34 -0
- package/dist/core/appUpdateChecker.js +12 -16
- package/dist/core/appUpdateChecker.js.map +1 -1
- package/dist/core/detector.js +14 -18
- package/dist/core/detector.js.map +1 -1
- package/dist/core/duplicateFileFinder.js +12 -19
- package/dist/core/duplicateFileFinder.js.map +1 -1
- package/dist/core/orphanedDependencyDetector.js +19 -26
- package/dist/core/orphanedDependencyDetector.js.map +1 -1
- package/dist/core/performanceOptimizer.js +6 -10
- package/dist/core/performanceOptimizer.js.map +1 -1
- package/dist/core/permissionHandler.js +21 -25
- package/dist/core/permissionHandler.js.map +1 -1
- package/dist/core/pluginSystem.js +9 -13
- package/dist/core/pluginSystem.js.map +1 -1
- package/dist/core/removalRecorder.js +12 -19
- package/dist/core/removalRecorder.js.map +1 -1
- package/dist/core/remover.js +59 -66
- package/dist/core/remover.js.map +1 -1
- package/dist/core/reportGenerator.d.ts +1 -1
- package/dist/core/reportGenerator.d.ts.map +1 -1
- package/dist/core/reportGenerator.js +27 -34
- package/dist/core/reportGenerator.js.map +1 -1
- package/dist/core/scheduledCleanup.js +23 -30
- package/dist/core/scheduledCleanup.js.map +1 -1
- package/dist/core/serviceFileDetector.js +24 -31
- package/dist/core/serviceFileDetector.js.map +1 -1
- package/dist/core/verificationModule.js +10 -14
- package/dist/core/verificationModule.js.map +1 -1
- package/dist/index.js +118 -156
- package/dist/index.js.map +1 -1
- package/dist/managers/brewManager.js +30 -37
- package/dist/managers/brewManager.js.map +1 -1
- package/dist/managers/customManager.js +23 -30
- package/dist/managers/customManager.js.map +1 -1
- package/dist/managers/linuxManager.js +29 -36
- package/dist/managers/linuxManager.js.map +1 -1
- package/dist/managers/npmManager.js +27 -34
- package/dist/managers/npmManager.js.map +1 -1
- package/dist/types/index.js +1 -2
- package/dist/ui/client/api/client.d.ts +24 -0
- package/dist/ui/client/api/client.d.ts.map +1 -0
- package/dist/ui/client/api/client.js +100 -0
- package/dist/ui/client/api/client.js.map +1 -0
- package/dist/ui/client/app.d.ts +7 -0
- package/dist/ui/client/app.d.ts.map +1 -0
- package/dist/ui/client/app.js +75 -0
- package/dist/ui/client/app.js.map +1 -0
- package/dist/ui/client/index.html +107 -0
- package/dist/ui/client/pages/appDetails.d.ts +8 -0
- package/dist/ui/client/pages/appDetails.d.ts.map +1 -0
- package/dist/ui/client/pages/appDetails.js +287 -0
- package/dist/ui/client/pages/appDetails.js.map +1 -0
- package/dist/ui/client/pages/appSearch.d.ts +2 -0
- package/dist/ui/client/pages/appSearch.d.ts.map +1 -0
- package/dist/ui/client/pages/appSearch.js +221 -0
- package/dist/ui/client/pages/appSearch.js.map +1 -0
- package/dist/ui/client/pages/dashboard.d.ts +2 -0
- package/dist/ui/client/pages/dashboard.d.ts.map +1 -0
- package/dist/ui/client/pages/dashboard.js +175 -0
- package/dist/ui/client/pages/dashboard.js.map +1 -0
- package/dist/ui/client/pages/settings.d.ts +7 -0
- package/dist/ui/client/pages/settings.d.ts.map +1 -0
- package/dist/ui/client/pages/settings.js +279 -0
- package/dist/ui/client/pages/settings.js.map +1 -0
- package/dist/ui/client/state/appStore.d.ts +38 -0
- package/dist/ui/client/state/appStore.d.ts.map +1 -0
- package/dist/ui/client/state/appStore.js +130 -0
- package/dist/ui/client/state/appStore.js.map +1 -0
- package/dist/ui/client/state/dashboardStore.d.ts +31 -0
- package/dist/ui/client/state/dashboardStore.d.ts.map +1 -0
- package/dist/ui/client/state/dashboardStore.js +76 -0
- package/dist/ui/client/state/dashboardStore.js.map +1 -0
- package/dist/ui/client/state/uiStore.d.ts +43 -0
- package/dist/ui/client/state/uiStore.d.ts.map +1 -0
- package/dist/ui/client/state/uiStore.js +109 -0
- package/dist/ui/client/state/uiStore.js.map +1 -0
- package/dist/ui/client/styles/animations.css +349 -0
- package/dist/ui/client/styles/base.css +214 -0
- package/dist/ui/client/styles/components.css +400 -0
- package/dist/ui/client/styles/layout.css +224 -0
- package/dist/ui/client/styles/variables.css +140 -0
- package/dist/ui/client/utils/events.d.ts +19 -0
- package/dist/ui/client/utils/events.d.ts.map +1 -0
- package/dist/ui/client/utils/events.js +54 -0
- package/dist/ui/client/utils/events.js.map +1 -0
- package/dist/ui/client/utils/formatting.d.ts +11 -0
- package/dist/ui/client/utils/formatting.d.ts.map +1 -0
- package/dist/ui/client/utils/formatting.js +104 -0
- package/dist/ui/client/utils/formatting.js.map +1 -0
- package/dist/ui/client/utils/router.d.ts +25 -0
- package/dist/ui/client/utils/router.d.ts.map +1 -0
- package/dist/ui/client/utils/router.js +90 -0
- package/dist/ui/client/utils/router.js.map +1 -0
- package/dist/ui/guiServer.d.ts +11 -5
- package/dist/ui/guiServer.d.ts.map +1 -1
- package/dist/ui/guiServer.js +180 -501
- package/dist/ui/guiServer.js.map +1 -1
- package/dist/ui/menu.js +18 -27
- package/dist/ui/menu.js.map +1 -1
- package/dist/ui/prompts.js +34 -47
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/server/middleware/errorHandler.d.ts +19 -0
- package/dist/ui/server/middleware/errorHandler.d.ts.map +1 -0
- package/dist/ui/server/middleware/errorHandler.js +100 -0
- package/dist/ui/server/middleware/errorHandler.js.map +1 -0
- package/dist/ui/server/routes/apps.d.ts +8 -0
- package/dist/ui/server/routes/apps.d.ts.map +1 -0
- package/dist/ui/server/routes/apps.js +74 -0
- package/dist/ui/server/routes/apps.js.map +1 -0
- package/dist/ui/server/routes/dashboard.d.ts +4 -0
- package/dist/ui/server/routes/dashboard.d.ts.map +1 -0
- package/dist/ui/server/routes/dashboard.js +57 -0
- package/dist/ui/server/routes/dashboard.js.map +1 -0
- package/dist/ui/server/routes/settings.d.ts +6 -0
- package/dist/ui/server/routes/settings.d.ts.map +1 -0
- package/dist/ui/server/routes/settings.js +31 -0
- package/dist/ui/server/routes/settings.js.map +1 -0
- package/dist/ui/server/services/appService.d.ts +45 -0
- package/dist/ui/server/services/appService.d.ts.map +1 -0
- package/dist/ui/server/services/appService.js +114 -0
- package/dist/ui/server/services/appService.js.map +1 -0
- package/dist/ui/server/services/removalService.d.ts +24 -0
- package/dist/ui/server/services/removalService.d.ts.map +1 -0
- package/dist/ui/server/services/removalService.js +83 -0
- package/dist/ui/server/services/removalService.js.map +1 -0
- package/dist/utils/filesystem.js +32 -49
- package/dist/utils/filesystem.js.map +1 -1
- package/dist/utils/logger.js +9 -18
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/platform.js +10 -22
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/upgrade.d.ts +2 -1
- package/dist/utils/upgrade.d.ts.map +1 -1
- package/dist/utils/upgrade.js +24 -15
- package/dist/utils/upgrade.js.map +1 -1
- package/package.json +4 -2
- package/scripts/publish-npm.sh +64 -0
- package/src/core/appUpdateChecker.ts +1 -1
- package/src/core/detector.ts +6 -6
- package/src/core/duplicateFileFinder.ts +1 -1
- package/src/core/orphanedDependencyDetector.ts +2 -2
- package/src/core/performanceOptimizer.ts +1 -1
- package/src/core/permissionHandler.ts +2 -2
- package/src/core/pluginSystem.ts +1 -1
- package/src/core/removalRecorder.ts +2 -2
- package/src/core/remover.ts +11 -11
- package/src/core/reportGenerator.ts +2 -2
- package/src/core/scheduledCleanup.ts +2 -2
- package/src/core/serviceFileDetector.ts +2 -2
- package/src/core/verificationModule.ts +2 -2
- package/src/index.ts +8 -8
- package/src/managers/brewManager.ts +3 -3
- package/src/managers/customManager.ts +2 -2
- package/src/managers/linuxManager.ts +3 -3
- package/src/managers/npmManager.ts +3 -3
- package/src/ui/client/api/client.ts +168 -0
- package/src/ui/client/app.ts +125 -0
- package/src/ui/client/index.html +107 -0
- package/src/ui/client/pages/appDetails.ts +356 -0
- package/src/ui/client/pages/appSearch.ts +283 -0
- package/src/ui/client/pages/dashboard.ts +211 -0
- package/src/ui/client/pages/settings.ts +342 -0
- package/src/ui/client/state/appStore.ts +181 -0
- package/src/ui/client/state/dashboardStore.ts +123 -0
- package/src/ui/client/state/uiStore.ts +166 -0
- package/src/ui/client/styles/animations.css +349 -0
- package/src/ui/client/styles/base.css +214 -0
- package/src/ui/client/styles/components.css +400 -0
- package/src/ui/client/styles/layout.css +224 -0
- package/src/ui/client/styles/variables.css +140 -0
- package/src/ui/client/utils/events.ts +74 -0
- package/src/ui/client/utils/formatting.ts +157 -0
- package/src/ui/client/utils/router.ts +161 -0
- package/src/ui/guiServer.ts +245 -498
- package/src/ui/prompts.ts +1 -1
- package/src/ui/server/middleware/errorHandler.ts +174 -0
- package/src/ui/server/routes/apps.ts +132 -0
- package/src/ui/server/routes/dashboard.ts +93 -0
- package/src/ui/server/routes/settings.ts +63 -0
- package/src/ui/server/services/appService.ts +184 -0
- package/src/ui/server/services/removalService.ts +138 -0
- package/src/utils/upgrade.ts +19 -2
- package/tsconfig.json +3 -2
- package/INDEX.md +0 -165
- /package/{ACTION_CHECKLIST.md → MD_Files/ACTION_CHECKLIST.md} +0 -0
- /package/{APPCLEAN_SUMMARY.md → MD_Files/APPCLEAN_SUMMARY.md} +0 -0
- /package/{CHANGELOG.md → MD_Files/CHANGELOG.md} +0 -0
- /package/{CODE_OF_CONDUCT.md → MD_Files/CODE_OF_CONDUCT.md} +0 -0
- /package/{CODE_REVIEW_REPORT.md → MD_Files/CODE_REVIEW_REPORT.md} +0 -0
- /package/{COMMUNITY_POSTS.md → MD_Files/COMMUNITY_POSTS.md} +0 -0
- /package/{DEPLOYMENT_GUIDE.md → MD_Files/DEPLOYMENT_GUIDE.md} +0 -0
- /package/{DEPLOYMENT_STATUS.md → MD_Files/DEPLOYMENT_STATUS.md} +0 -0
- /package/{EXECUTIVE_REPORT.md → MD_Files/EXECUTIVE_REPORT.md} +0 -0
- /package/{GITHUB_OPTIMIZATION.md → MD_Files/GITHUB_OPTIMIZATION.md} +0 -0
- /package/{MARKETING_SUMMARY.md → MD_Files/MARKETING_SUMMARY.md} +0 -0
- /package/{NPM_PACKAGE_OPTIMIZATION.md → MD_Files/NPM_PACKAGE_OPTIMIZATION.md} +0 -0
- /package/{NPM_PUBLISH.md → MD_Files/NPM_PUBLISH.md} +0 -0
- /package/{PROJECT_SUMMARY.txt → MD_Files/PROJECT_SUMMARY.txt} +0 -0
- /package/{PUBLICATION_SUCCESS_REPORT.md → MD_Files/PUBLICATION_SUCCESS_REPORT.md} +0 -0
- /package/{QUICKSTART.md → MD_Files/QUICKSTART.md} +0 -0
- /package/{SETUP_GITHUB.md → MD_Files/SETUP_GITHUB.md} +0 -0
- /package/{TESTING_SUMMARY.md → MD_Files/TESTING_SUMMARY.md} +0 -0
- /package/{setup-github.sh → MD_Files/setup-github.sh} +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Page - Version management, theme, and uninstall
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { apiClient } from '../api/client.js';
|
|
6
|
+
import { uiStore } from '../state/uiStore.js';
|
|
7
|
+
|
|
8
|
+
export interface VersionInfo {
|
|
9
|
+
current: string;
|
|
10
|
+
latest: string;
|
|
11
|
+
isUpdateAvailable: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Render Settings Page
|
|
16
|
+
*/
|
|
17
|
+
export function renderSettings(): void {
|
|
18
|
+
const container = document.getElementById('page-container');
|
|
19
|
+
if (!container) return;
|
|
20
|
+
|
|
21
|
+
// Show loading state
|
|
22
|
+
container.innerHTML = `
|
|
23
|
+
<div class="loading-state" style="text-align: center; padding: 60px 20px;">
|
|
24
|
+
<div class="spinner-lg spinner"></div>
|
|
25
|
+
<p class="text-center text-muted mt-4">Loading settings...</p>
|
|
26
|
+
</div>
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
// Load version info
|
|
30
|
+
loadVersionInfo().then((versionInfo) => {
|
|
31
|
+
renderSettingsContent(container, versionInfo);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load version information
|
|
37
|
+
*/
|
|
38
|
+
async function loadVersionInfo(): Promise<VersionInfo> {
|
|
39
|
+
try {
|
|
40
|
+
return await apiClient.get<VersionInfo>('/api/version');
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
current: 'unknown',
|
|
44
|
+
latest: 'unknown',
|
|
45
|
+
isUpdateAvailable: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Render settings content
|
|
52
|
+
*/
|
|
53
|
+
function renderSettingsContent(container: HTMLElement, versionInfo: VersionInfo): void {
|
|
54
|
+
const currentTheme = uiStore.getState().theme;
|
|
55
|
+
|
|
56
|
+
container.innerHTML = `
|
|
57
|
+
<div class="settings-page">
|
|
58
|
+
<!-- Header -->
|
|
59
|
+
<div class="page-header mb-8">
|
|
60
|
+
<h1 class="text-3xl font-bold">⚙️ Settings</h1>
|
|
61
|
+
<p class="text-secondary mt-2">Configure AppClean</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Version & Updates Section -->
|
|
65
|
+
<div class="card mb-6">
|
|
66
|
+
<div class="card-header">
|
|
67
|
+
<h2 class="text-xl font-bold">📦 Version & Updates</h2>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="card-body">
|
|
70
|
+
<div class="version-info p-4 bg-secondary rounded mb-4">
|
|
71
|
+
<div class="flex-between mb-3">
|
|
72
|
+
<span class="text-sm font-medium">Current Version</span>
|
|
73
|
+
<span class="text-lg font-bold text-primary">v${escapeHtml(versionInfo.current)}</span>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="flex-between">
|
|
76
|
+
<span class="text-sm font-medium">Latest Version</span>
|
|
77
|
+
<span class="text-lg font-bold ${versionInfo.isUpdateAvailable ? 'text-warning' : 'text-success'}">
|
|
78
|
+
v${escapeHtml(versionInfo.latest)}
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
${
|
|
84
|
+
versionInfo.isUpdateAvailable
|
|
85
|
+
? `
|
|
86
|
+
<div class="alert alert-warning mb-4">
|
|
87
|
+
<span>A new version of AppClean is available!</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="flex gap-2">
|
|
90
|
+
<button class="btn btn-primary flex-1" id="upgrade-btn">
|
|
91
|
+
🚀 Upgrade Now
|
|
92
|
+
</button>
|
|
93
|
+
<button class="btn btn-secondary flex-1" id="check-update-btn">
|
|
94
|
+
🔄 Check Again
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
`
|
|
98
|
+
: `
|
|
99
|
+
<div class="alert alert-success mb-4">
|
|
100
|
+
<span>✓ You're running the latest version!</span>
|
|
101
|
+
</div>
|
|
102
|
+
<button class="btn btn-secondary w-full" id="check-update-btn">
|
|
103
|
+
🔄 Check for Updates
|
|
104
|
+
</button>
|
|
105
|
+
`
|
|
106
|
+
}
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Theme Section -->
|
|
111
|
+
<div class="card mb-6">
|
|
112
|
+
<div class="card-header">
|
|
113
|
+
<h2 class="text-xl font-bold">🎨 Appearance</h2>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="card-body">
|
|
116
|
+
<div class="flex-between items-center p-3 border border-color rounded mb-4">
|
|
117
|
+
<div>
|
|
118
|
+
<p class="font-medium">Dark Mode</p>
|
|
119
|
+
<p class="text-sm text-muted">Switch between light and dark themes</p>
|
|
120
|
+
</div>
|
|
121
|
+
<button
|
|
122
|
+
class="btn btn-sm ${currentTheme === 'dark' ? 'btn-primary' : 'btn-secondary'}"
|
|
123
|
+
id="theme-toggle-btn"
|
|
124
|
+
>
|
|
125
|
+
${currentTheme === 'dark' ? '🌙 Dark' : '☀️ Light'}
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- About Section -->
|
|
132
|
+
<div class="card mb-6">
|
|
133
|
+
<div class="card-header">
|
|
134
|
+
<h2 class="text-xl font-bold">ℹ️ About</h2>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="card-body">
|
|
137
|
+
<div class="space-y-4">
|
|
138
|
+
<div>
|
|
139
|
+
<p class="text-sm font-medium mb-2">AppClean</p>
|
|
140
|
+
<p class="text-sm text-muted">
|
|
141
|
+
Intelligently find and safely remove applications with all their artifacts.
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div>
|
|
146
|
+
<p class="text-sm font-medium mb-2">Links</p>
|
|
147
|
+
<div class="flex gap-2">
|
|
148
|
+
<a
|
|
149
|
+
href="https://github.com/praveenkay/AppClean"
|
|
150
|
+
target="_blank"
|
|
151
|
+
rel="noopener"
|
|
152
|
+
class="btn btn-sm btn-ghost"
|
|
153
|
+
>
|
|
154
|
+
💻 GitHub
|
|
155
|
+
</a>
|
|
156
|
+
<a
|
|
157
|
+
href="https://github.com/praveenkay/AppClean/issues"
|
|
158
|
+
target="_blank"
|
|
159
|
+
rel="noopener"
|
|
160
|
+
class="btn btn-sm btn-ghost"
|
|
161
|
+
>
|
|
162
|
+
🐛 Report Issue
|
|
163
|
+
</a>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<!-- Danger Zone -->
|
|
171
|
+
<div class="card" style="border-color: var(--color-danger);">
|
|
172
|
+
<div class="card-header" style="background: var(--color-danger-light);">
|
|
173
|
+
<h2 class="text-xl font-bold text-danger">⚠️ Danger Zone</h2>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="card-body">
|
|
176
|
+
<p class="text-sm text-muted mb-4">
|
|
177
|
+
Uninstall AppClean from your system. This action cannot be undone.
|
|
178
|
+
</p>
|
|
179
|
+
<button class="btn btn-danger w-full" id="uninstall-btn">
|
|
180
|
+
🗑️ Uninstall AppClean
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
// Setup event listeners
|
|
188
|
+
setupSettingsListeners();
|
|
189
|
+
|
|
190
|
+
// Scroll to top
|
|
191
|
+
window.scrollTo(0, 0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Setup event listeners
|
|
196
|
+
*/
|
|
197
|
+
function setupSettingsListeners(): void {
|
|
198
|
+
const upgradeBtn = document.getElementById('upgrade-btn');
|
|
199
|
+
const checkUpdateBtn = document.getElementById('check-update-btn');
|
|
200
|
+
const themeToggleBtn = document.getElementById('theme-toggle-btn');
|
|
201
|
+
const uninstallBtn = document.getElementById('uninstall-btn');
|
|
202
|
+
|
|
203
|
+
// Upgrade button
|
|
204
|
+
if (upgradeBtn) {
|
|
205
|
+
upgradeBtn.addEventListener('click', async () => {
|
|
206
|
+
(upgradeBtn as HTMLButtonElement).disabled = true;
|
|
207
|
+
upgradeBtn.textContent = '⏳ Upgrading...';
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const result = await apiClient.post('/api/upgrade', {});
|
|
211
|
+
|
|
212
|
+
if (result.success) {
|
|
213
|
+
uiStore.showSuccess(result.message);
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
window.location.reload();
|
|
216
|
+
}, 2000);
|
|
217
|
+
} else {
|
|
218
|
+
uiStore.showError(result.message);
|
|
219
|
+
(upgradeBtn as HTMLButtonElement).disabled = false;
|
|
220
|
+
upgradeBtn.textContent = '🚀 Upgrade Now';
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
uiStore.showError(`Upgrade failed: ${(error as Error).message}`);
|
|
224
|
+
(upgradeBtn as HTMLButtonElement).disabled = false;
|
|
225
|
+
upgradeBtn.textContent = '🚀 Upgrade Now';
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check update button
|
|
231
|
+
if (checkUpdateBtn) {
|
|
232
|
+
checkUpdateBtn.addEventListener('click', async () => {
|
|
233
|
+
(checkUpdateBtn as HTMLButtonElement).disabled = true;
|
|
234
|
+
checkUpdateBtn.textContent = '⏳ Checking...';
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const versionInfo = await apiClient.get<VersionInfo>('/api/version');
|
|
238
|
+
|
|
239
|
+
if (versionInfo.isUpdateAvailable) {
|
|
240
|
+
uiStore.showWarning(
|
|
241
|
+
`Update available: v${versionInfo.latest} (current: v${versionInfo.current})`
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
uiStore.showSuccess('You are running the latest version!');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Refresh page to update UI
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
window.location.reload();
|
|
250
|
+
}, 1500);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
uiStore.showError(`Failed to check updates: ${(error as Error).message}`);
|
|
253
|
+
(checkUpdateBtn as HTMLButtonElement).disabled = false;
|
|
254
|
+
checkUpdateBtn.textContent = '🔄 Check Again';
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Theme toggle button
|
|
260
|
+
if (themeToggleBtn) {
|
|
261
|
+
themeToggleBtn.addEventListener('click', () => {
|
|
262
|
+
uiStore.toggleTheme();
|
|
263
|
+
|
|
264
|
+
// Update button appearance
|
|
265
|
+
const newTheme = uiStore.getState().theme;
|
|
266
|
+
themeToggleBtn.className = `btn btn-sm ${newTheme === 'dark' ? 'btn-primary' : 'btn-secondary'}`;
|
|
267
|
+
themeToggleBtn.textContent = newTheme === 'dark' ? '🌙 Dark' : '☀️ Light';
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Uninstall button
|
|
272
|
+
if (uninstallBtn) {
|
|
273
|
+
uninstallBtn.addEventListener('click', () => {
|
|
274
|
+
const confirmed = confirm(
|
|
275
|
+
'Are you sure you want to uninstall AppClean?\n\nThis action cannot be undone.'
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (!confirmed) return;
|
|
279
|
+
|
|
280
|
+
const doubleConfirmed = confirm(
|
|
281
|
+
'This will permanently remove AppClean from your system.\n\nType "YES" in the next dialog to confirm.'
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (!doubleConfirmed) return;
|
|
285
|
+
|
|
286
|
+
uninstall();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Execute uninstall
|
|
293
|
+
*/
|
|
294
|
+
async function uninstall(): Promise<void> {
|
|
295
|
+
const uninstallBtn = document.getElementById('uninstall-btn');
|
|
296
|
+
if (uninstallBtn) {
|
|
297
|
+
(uninstallBtn as HTMLButtonElement).disabled = true;
|
|
298
|
+
uninstallBtn.textContent = '⏳ Uninstalling...';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
uiStore.setProcessing(true);
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const result = await apiClient.post('/api/uninstall', {});
|
|
305
|
+
|
|
306
|
+
if (result.success) {
|
|
307
|
+
uiStore.showSuccess(result.message);
|
|
308
|
+
setTimeout(() => {
|
|
309
|
+
// Redirect or close window
|
|
310
|
+
window.close();
|
|
311
|
+
}, 2000);
|
|
312
|
+
} else {
|
|
313
|
+
uiStore.showError(result.message);
|
|
314
|
+
if (uninstallBtn) {
|
|
315
|
+
(uninstallBtn as HTMLButtonElement).disabled = false;
|
|
316
|
+
uninstallBtn.textContent = '🗑️ Uninstall AppClean';
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
uiStore.showError(`Uninstall failed: ${(error as Error).message}`);
|
|
321
|
+
if (uninstallBtn) {
|
|
322
|
+
(uninstallBtn as HTMLButtonElement).disabled = false;
|
|
323
|
+
uninstallBtn.textContent = '🗑️ Uninstall AppClean';
|
|
324
|
+
}
|
|
325
|
+
} finally {
|
|
326
|
+
uiStore.setProcessing(false);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Escape HTML
|
|
332
|
+
*/
|
|
333
|
+
function escapeHtml(text: string): string {
|
|
334
|
+
const map: Record<string, string> = {
|
|
335
|
+
'&': '&',
|
|
336
|
+
'<': '<',
|
|
337
|
+
'>': '>',
|
|
338
|
+
'"': '"',
|
|
339
|
+
"'": ''',
|
|
340
|
+
};
|
|
341
|
+
return String(text).replace(/[&<>"']/g, (m) => map[m]);
|
|
342
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppStore - Manages installed applications list and search/filter state
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Store } from '../utils/events.js';
|
|
6
|
+
|
|
7
|
+
export interface InstalledApp {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
installMethod: string;
|
|
11
|
+
mainPath: string;
|
|
12
|
+
installedDate?: string;
|
|
13
|
+
size?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SearchOptions {
|
|
17
|
+
query: string;
|
|
18
|
+
installMethod?: string;
|
|
19
|
+
sortBy?: 'name' | 'size' | 'date';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AppStoreState {
|
|
23
|
+
apps: InstalledApp[];
|
|
24
|
+
searchOptions: SearchOptions;
|
|
25
|
+
selectedApp: InstalledApp | null;
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
error: string | null;
|
|
28
|
+
total: number;
|
|
29
|
+
page: number;
|
|
30
|
+
pageSize: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const INITIAL_STATE: AppStoreState = {
|
|
34
|
+
apps: [],
|
|
35
|
+
searchOptions: {
|
|
36
|
+
query: '',
|
|
37
|
+
installMethod: undefined,
|
|
38
|
+
sortBy: 'name',
|
|
39
|
+
},
|
|
40
|
+
selectedApp: null,
|
|
41
|
+
isLoading: false,
|
|
42
|
+
error: null,
|
|
43
|
+
total: 0,
|
|
44
|
+
page: 1,
|
|
45
|
+
pageSize: 50,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class AppStore extends Store<AppStoreState> {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(INITIAL_STATE);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load all apps
|
|
54
|
+
async loadApps(): Promise<void> {
|
|
55
|
+
this.setState({ isLoading: true, error: null });
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch('/api/apps/list');
|
|
58
|
+
if (!response.ok) throw new Error('Failed to load apps');
|
|
59
|
+
|
|
60
|
+
const json = await response.json();
|
|
61
|
+
const data = json.data || json; // Handle wrapped { success, data } format
|
|
62
|
+
|
|
63
|
+
this.setState({
|
|
64
|
+
apps: data.apps || [],
|
|
65
|
+
total: data.total || 0,
|
|
66
|
+
page: data.page || 1,
|
|
67
|
+
isLoading: false,
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
71
|
+
console.error('Failed to load apps:', errorMsg);
|
|
72
|
+
this.setState({
|
|
73
|
+
error: errorMsg,
|
|
74
|
+
isLoading: false,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Search apps
|
|
80
|
+
async searchApps(options: Partial<SearchOptions>): Promise<void> {
|
|
81
|
+
const searchOptions = { ...this.state.searchOptions, ...options };
|
|
82
|
+
this.setState({ searchOptions, isLoading: true, error: null, page: 1 });
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const params = new URLSearchParams();
|
|
86
|
+
if (searchOptions.query) params.append('q', searchOptions.query);
|
|
87
|
+
if (searchOptions.installMethod) params.append('method', searchOptions.installMethod);
|
|
88
|
+
if (searchOptions.sortBy) params.append('sort', searchOptions.sortBy);
|
|
89
|
+
params.append('limit', String(this.state.pageSize));
|
|
90
|
+
params.append('offset', '0');
|
|
91
|
+
|
|
92
|
+
const response = await fetch(`/api/apps/search?${params}`);
|
|
93
|
+
if (!response.ok) throw new Error('Failed to search apps');
|
|
94
|
+
|
|
95
|
+
const json = await response.json();
|
|
96
|
+
const data = json.data || json; // Handle wrapped { success, data } format
|
|
97
|
+
|
|
98
|
+
this.setState({
|
|
99
|
+
apps: data.apps || [],
|
|
100
|
+
total: data.count || 0,
|
|
101
|
+
isLoading: false,
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
105
|
+
console.error('Failed to search apps:', errorMsg);
|
|
106
|
+
this.setState({
|
|
107
|
+
error: errorMsg,
|
|
108
|
+
isLoading: false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Set search query
|
|
114
|
+
setSearchQuery(query: string): void {
|
|
115
|
+
this.searchApps({ query });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Set install method filter
|
|
119
|
+
setInstallMethodFilter(method?: string): void {
|
|
120
|
+
this.searchApps({ installMethod: method });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Set sort option
|
|
124
|
+
setSortBy(sortBy: 'name' | 'size' | 'date'): void {
|
|
125
|
+
this.searchApps({ sortBy });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Select an app
|
|
129
|
+
selectApp(app: InstalledApp): void {
|
|
130
|
+
this.setState({ selectedApp: app });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Clear selection
|
|
134
|
+
clearSelection(): void {
|
|
135
|
+
this.setState({ selectedApp: null });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Load next page
|
|
139
|
+
async loadNextPage(): Promise<void> {
|
|
140
|
+
const nextPage = this.state.page + 1;
|
|
141
|
+
const offset = (nextPage - 1) * this.state.pageSize;
|
|
142
|
+
|
|
143
|
+
this.setState({ isLoading: true });
|
|
144
|
+
try {
|
|
145
|
+
const params = new URLSearchParams();
|
|
146
|
+
if (this.state.searchOptions.query) params.append('q', this.state.searchOptions.query);
|
|
147
|
+
if (this.state.searchOptions.installMethod) {
|
|
148
|
+
params.append('method', this.state.searchOptions.installMethod);
|
|
149
|
+
}
|
|
150
|
+
params.append('limit', String(this.state.pageSize));
|
|
151
|
+
params.append('offset', String(offset));
|
|
152
|
+
|
|
153
|
+
const response = await fetch(`/api/apps/search?${params}`);
|
|
154
|
+
if (!response.ok) throw new Error('Failed to load more apps');
|
|
155
|
+
|
|
156
|
+
const json = await response.json();
|
|
157
|
+
const data = json.data || json; // Handle wrapped { success, data } format
|
|
158
|
+
|
|
159
|
+
this.setState({
|
|
160
|
+
apps: [...this.state.apps, ...(data.apps || [])],
|
|
161
|
+
page: nextPage,
|
|
162
|
+
isLoading: false,
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
166
|
+
console.error('Failed to load next page:', errorMsg);
|
|
167
|
+
this.setState({
|
|
168
|
+
error: errorMsg,
|
|
169
|
+
isLoading: false,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Clear error
|
|
175
|
+
clearError(): void {
|
|
176
|
+
this.setState({ error: null });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Export singleton instance
|
|
181
|
+
export const appStore = new AppStore();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DashboardStore - Manages dashboard statistics and session activity
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Store } from '../utils/events.js';
|
|
6
|
+
|
|
7
|
+
export interface RemovalRecord {
|
|
8
|
+
appName: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
freedSpace: number;
|
|
11
|
+
filesRemoved: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DashboardStats {
|
|
15
|
+
totalApps: number;
|
|
16
|
+
totalSpaceUsed: number;
|
|
17
|
+
sessionAppsRemoved: number;
|
|
18
|
+
sessionSpaceFreed: number;
|
|
19
|
+
diskUsagePercent: number;
|
|
20
|
+
recentlyRemoved: RemovalRecord[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DashboardStoreState {
|
|
24
|
+
stats: DashboardStats | null;
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
error: string | null;
|
|
27
|
+
lastUpdated: number | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const INITIAL_STATE: DashboardStoreState = {
|
|
31
|
+
stats: null,
|
|
32
|
+
isLoading: false,
|
|
33
|
+
error: null,
|
|
34
|
+
lastUpdated: null,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class DashboardStore extends Store<DashboardStoreState> {
|
|
38
|
+
constructor() {
|
|
39
|
+
super(INITIAL_STATE);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Load dashboard stats
|
|
43
|
+
async loadStats(): Promise<void> {
|
|
44
|
+
this.setState({ isLoading: true, error: null });
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch('/api/dashboard/stats');
|
|
47
|
+
if (!response.ok) throw new Error(`Failed to load dashboard stats: ${response.status}`);
|
|
48
|
+
|
|
49
|
+
const data = await response.json();
|
|
50
|
+
|
|
51
|
+
// Extract stats from API response format { success: true, data: {...} }
|
|
52
|
+
const stats = data.data || data;
|
|
53
|
+
|
|
54
|
+
if (!stats) {
|
|
55
|
+
throw new Error('Invalid response format from server');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.setState({
|
|
59
|
+
stats,
|
|
60
|
+
isLoading: false,
|
|
61
|
+
lastUpdated: Date.now(),
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
65
|
+
console.error('Failed to load dashboard stats:', errorMsg);
|
|
66
|
+
this.setState({
|
|
67
|
+
error: errorMsg,
|
|
68
|
+
isLoading: false,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Refresh stats (with caching - only refresh if older than 5 seconds)
|
|
74
|
+
async refreshStats(force = false): Promise<void> {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const lastUpdated = this.state.lastUpdated ?? 0;
|
|
77
|
+
const timeSinceLastUpdate = now - lastUpdated;
|
|
78
|
+
|
|
79
|
+
if (!force && timeSinceLastUpdate < 5000) {
|
|
80
|
+
return; // Use cached stats if less than 5 seconds old
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await this.loadStats();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Record a removal (for UI feedback)
|
|
87
|
+
addRemovalRecord(record: RemovalRecord): void {
|
|
88
|
+
const stats = this.state.stats;
|
|
89
|
+
if (!stats) return;
|
|
90
|
+
|
|
91
|
+
const updatedStats = {
|
|
92
|
+
...stats,
|
|
93
|
+
sessionAppsRemoved: stats.sessionAppsRemoved + 1,
|
|
94
|
+
sessionSpaceFreed: stats.sessionSpaceFreed + record.freedSpace,
|
|
95
|
+
recentlyRemoved: [record, ...stats.recentlyRemoved].slice(0, 10), // Keep last 10
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
this.setState({ stats: updatedStats });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Reset session stats
|
|
102
|
+
resetSessionStats(): void {
|
|
103
|
+
const stats = this.state.stats;
|
|
104
|
+
if (!stats) return;
|
|
105
|
+
|
|
106
|
+
const resetStats = {
|
|
107
|
+
...stats,
|
|
108
|
+
sessionAppsRemoved: 0,
|
|
109
|
+
sessionSpaceFreed: 0,
|
|
110
|
+
recentlyRemoved: [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
this.setState({ stats: resetStats });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Clear error
|
|
117
|
+
clearError(): void {
|
|
118
|
+
this.setState({ error: null });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Export singleton instance
|
|
123
|
+
export const dashboardStore = new DashboardStore();
|