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,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Search Page - Search, filter, and browse installed applications
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { appStore, InstalledApp, SearchOptions } from '../state/appStore.js';
|
|
6
|
+
import { uiStore } from '../state/uiStore.js';
|
|
7
|
+
import { formatBytes, getMethodBadgeColor, formatInstallMethod, debounce } from '../utils/formatting.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render App Search Page
|
|
11
|
+
*/
|
|
12
|
+
export function renderAppSearch(): void {
|
|
13
|
+
const container = document.getElementById('page-container');
|
|
14
|
+
if (!container) return;
|
|
15
|
+
|
|
16
|
+
// Show loading state
|
|
17
|
+
container.innerHTML = `
|
|
18
|
+
<div class="loading-state" style="text-align: center; padding: 60px 20px;">
|
|
19
|
+
<div class="spinner-lg spinner"></div>
|
|
20
|
+
<p class="text-center text-muted mt-4">Loading apps...</p>
|
|
21
|
+
</div>
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
// Load apps
|
|
25
|
+
appStore.loadApps().then(() => {
|
|
26
|
+
renderAppSearchContent(container);
|
|
27
|
+
|
|
28
|
+
// Subscribe to store updates
|
|
29
|
+
appStore.subscribe((state) => {
|
|
30
|
+
if (!state.isLoading) {
|
|
31
|
+
renderAppSearchContent(container);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Render app search page content
|
|
39
|
+
*/
|
|
40
|
+
function renderAppSearchContent(container: HTMLElement): void {
|
|
41
|
+
const state = appStore.getState();
|
|
42
|
+
|
|
43
|
+
// Ensure state has apps array
|
|
44
|
+
if (!state || !state.apps) {
|
|
45
|
+
console.error('AppStore state is invalid:', state);
|
|
46
|
+
container.innerHTML = `
|
|
47
|
+
<div class="alert alert-danger">
|
|
48
|
+
<h3>Error Loading Apps</h3>
|
|
49
|
+
<p>Failed to load application state. Please refresh the page.</p>
|
|
50
|
+
<button class="btn btn-primary btn-sm mt-4" onclick="window.location.reload()">Reload</button>
|
|
51
|
+
</div>
|
|
52
|
+
`;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
container.innerHTML = `
|
|
57
|
+
<div class="app-search-page">
|
|
58
|
+
<!-- Header -->
|
|
59
|
+
<div class="page-header mb-8">
|
|
60
|
+
<h1 class="text-3xl font-bold">📦 Applications</h1>
|
|
61
|
+
<p class="text-secondary mt-2">Search and manage installed apps</p>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Search and Filters -->
|
|
65
|
+
<div class="card mb-6">
|
|
66
|
+
<div class="card-body">
|
|
67
|
+
<!-- Search Input -->
|
|
68
|
+
<div class="form-group">
|
|
69
|
+
<input
|
|
70
|
+
type="text"
|
|
71
|
+
id="search-input"
|
|
72
|
+
class="form-input"
|
|
73
|
+
placeholder="Search apps by name..."
|
|
74
|
+
value="${escapeHtml(state.searchOptions.query)}"
|
|
75
|
+
autocomplete="off"
|
|
76
|
+
>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Filters Row -->
|
|
80
|
+
<div class="flex flex-wrap gap-2 items-center">
|
|
81
|
+
<span class="text-sm font-medium text-muted">Method:</span>
|
|
82
|
+
${renderMethodFilter(state.searchOptions.installMethod || '')}
|
|
83
|
+
|
|
84
|
+
<span class="text-sm font-medium text-muted ml-4">Sort:</span>
|
|
85
|
+
${renderSortFilter(state.searchOptions.sortBy || 'name')}
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Results Count -->
|
|
89
|
+
<p class="text-sm text-muted mt-4">
|
|
90
|
+
${state.isLoading ? 'Loading...' : `${state.total} app${state.total !== 1 ? 's' : ''} found`}
|
|
91
|
+
</p>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Apps Grid -->
|
|
96
|
+
<div class="apps-grid">
|
|
97
|
+
${
|
|
98
|
+
(state.apps && state.apps.length > 0)
|
|
99
|
+
? `
|
|
100
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
101
|
+
${state.apps.map((app) => renderAppCard(app)).join('')}
|
|
102
|
+
</div>
|
|
103
|
+
`
|
|
104
|
+
: `
|
|
105
|
+
<div class="card">
|
|
106
|
+
<div class="card-body text-center py-12">
|
|
107
|
+
<p class="text-2xl mb-2">🔍</p>
|
|
108
|
+
<p class="text-lg font-medium">No apps found</p>
|
|
109
|
+
<p class="text-muted mt-2">
|
|
110
|
+
${state.searchOptions.query ? 'Try a different search query' : 'No applications detected'}
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
`
|
|
115
|
+
}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Load More Button -->
|
|
119
|
+
${
|
|
120
|
+
state.apps.length < state.total
|
|
121
|
+
? `
|
|
122
|
+
<div class="text-center mt-8">
|
|
123
|
+
<button class="btn btn-secondary" id="load-more-btn">
|
|
124
|
+
Load More (${state.apps.length} of ${state.total})
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
`
|
|
128
|
+
: ''
|
|
129
|
+
}
|
|
130
|
+
</div>
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
// Setup event listeners
|
|
134
|
+
setupSearchListeners();
|
|
135
|
+
setupLoadMoreListener();
|
|
136
|
+
|
|
137
|
+
// Scroll to top
|
|
138
|
+
window.scrollTo(0, 0);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Render app card
|
|
143
|
+
*/
|
|
144
|
+
function renderAppCard(app: InstalledApp): string {
|
|
145
|
+
return `
|
|
146
|
+
<div class="card app-card hover-lift cursor-pointer" onclick="window.location.hash = '#/apps/${encodeURIComponent(app.name)}'">
|
|
147
|
+
<div class="card-body">
|
|
148
|
+
<div class="flex-between mb-2">
|
|
149
|
+
<h3 class="text-lg font-bold">${escapeHtml(app.name)}</h3>
|
|
150
|
+
<span class="badge ${getMethodBadgeColor(app.installMethod)}">
|
|
151
|
+
${formatInstallMethod(app.installMethod)}
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<p class="text-sm text-muted mb-3">
|
|
156
|
+
${app.version ? `v${escapeHtml(app.version)}` : 'Version unknown'}
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
<div class="flex-between pt-3 border-t border-color">
|
|
160
|
+
<span class="text-sm text-muted">
|
|
161
|
+
${app.size ? formatBytes(app.size) : 'Size unknown'}
|
|
162
|
+
</span>
|
|
163
|
+
<button
|
|
164
|
+
class="btn btn-sm btn-ghost-primary"
|
|
165
|
+
onclick="
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
window.location.hash = '#/apps/${encodeURIComponent(app.name)}';
|
|
168
|
+
"
|
|
169
|
+
>
|
|
170
|
+
Details →
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Render method filter buttons
|
|
180
|
+
*/
|
|
181
|
+
function renderMethodFilter(selected: string): string {
|
|
182
|
+
const methods = ['', 'npm', 'yarn', 'pnpm', 'brew', 'apt', 'yum', 'dnf', 'custom'];
|
|
183
|
+
const labels: Record<string, string> = {
|
|
184
|
+
'': 'All',
|
|
185
|
+
'npm': 'npm',
|
|
186
|
+
'yarn': 'Yarn',
|
|
187
|
+
'pnpm': 'pnpm',
|
|
188
|
+
'brew': 'Homebrew',
|
|
189
|
+
'apt': 'apt',
|
|
190
|
+
'yum': 'yum',
|
|
191
|
+
'dnf': 'dnf',
|
|
192
|
+
'custom': 'Custom',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return methods
|
|
196
|
+
.map(
|
|
197
|
+
(method) => `
|
|
198
|
+
<button
|
|
199
|
+
class="btn btn-sm ${method === selected ? 'btn-primary' : 'btn-ghost'} method-filter"
|
|
200
|
+
data-method="${method}"
|
|
201
|
+
>
|
|
202
|
+
${labels[method]}
|
|
203
|
+
</button>
|
|
204
|
+
`
|
|
205
|
+
)
|
|
206
|
+
.join('');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Render sort filter dropdown
|
|
211
|
+
*/
|
|
212
|
+
function renderSortFilter(selected: string): string {
|
|
213
|
+
return `
|
|
214
|
+
<select id="sort-select" class="form-input" style="width: auto;">
|
|
215
|
+
<option value="name" ${selected === 'name' ? 'selected' : ''}>Name (A-Z)</option>
|
|
216
|
+
<option value="size" ${selected === 'size' ? 'selected' : ''}>Size (Largest)</option>
|
|
217
|
+
<option value="date" ${selected === 'date' ? 'selected' : ''}>Date (Newest)</option>
|
|
218
|
+
</select>
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Setup search input listener with debounce
|
|
224
|
+
*/
|
|
225
|
+
function setupSearchListeners(): void {
|
|
226
|
+
const searchInput = document.getElementById('search-input') as HTMLInputElement;
|
|
227
|
+
const methodButtons = document.querySelectorAll('.method-filter');
|
|
228
|
+
const sortSelect = document.getElementById('sort-select') as HTMLSelectElement;
|
|
229
|
+
|
|
230
|
+
// Debounced search
|
|
231
|
+
const debouncedSearch = debounce((query: string) => {
|
|
232
|
+
appStore.setSearchQuery(query);
|
|
233
|
+
}, 300);
|
|
234
|
+
|
|
235
|
+
if (searchInput) {
|
|
236
|
+
searchInput.addEventListener('input', (e) => {
|
|
237
|
+
const query = (e.target as HTMLInputElement).value;
|
|
238
|
+
debouncedSearch(query);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Method filter buttons
|
|
243
|
+
methodButtons.forEach((btn) => {
|
|
244
|
+
btn.addEventListener('click', () => {
|
|
245
|
+
const method = btn.getAttribute('data-method');
|
|
246
|
+
appStore.setInstallMethodFilter(method || undefined);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Sort dropdown
|
|
251
|
+
if (sortSelect) {
|
|
252
|
+
sortSelect.addEventListener('change', (e) => {
|
|
253
|
+
const sort = (e.target as HTMLSelectElement).value as any;
|
|
254
|
+
appStore.setSortBy(sort);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Setup load more button
|
|
261
|
+
*/
|
|
262
|
+
function setupLoadMoreListener(): void {
|
|
263
|
+
const loadMoreBtn = document.getElementById('load-more-btn');
|
|
264
|
+
if (loadMoreBtn) {
|
|
265
|
+
loadMoreBtn.addEventListener('click', () => {
|
|
266
|
+
appStore.loadNextPage();
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Escape HTML
|
|
273
|
+
*/
|
|
274
|
+
function escapeHtml(text: string): string {
|
|
275
|
+
const map: Record<string, string> = {
|
|
276
|
+
'&': '&',
|
|
277
|
+
'<': '<',
|
|
278
|
+
'>': '>',
|
|
279
|
+
'"': '"',
|
|
280
|
+
"'": ''',
|
|
281
|
+
};
|
|
282
|
+
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
283
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Page - Overview of system health and app statistics
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { dashboardStore, DashboardStats } from '../state/dashboardStore.js';
|
|
6
|
+
import { uiStore } from '../state/uiStore.js';
|
|
7
|
+
import { formatBytes, formatPercent, formatRelativeTime } from '../utils/formatting.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Render Dashboard Page
|
|
11
|
+
*/
|
|
12
|
+
export function renderDashboard(): void {
|
|
13
|
+
const container = document.getElementById('page-container');
|
|
14
|
+
if (!container) return;
|
|
15
|
+
|
|
16
|
+
// Show loading state
|
|
17
|
+
container.innerHTML = `
|
|
18
|
+
<div class="loading-state" style="text-align: center; padding: 60px 20px;">
|
|
19
|
+
<div class="spinner-lg spinner"></div>
|
|
20
|
+
<p class="text-center text-muted mt-4">Loading dashboard...</p>
|
|
21
|
+
</div>
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
// Load stats
|
|
25
|
+
dashboardStore.refreshStats(true).then(() => {
|
|
26
|
+
const state = dashboardStore.getState();
|
|
27
|
+
const stats = state.stats;
|
|
28
|
+
const error = state.error;
|
|
29
|
+
|
|
30
|
+
if (error) {
|
|
31
|
+
container.innerHTML = `
|
|
32
|
+
<div class="alert alert-danger" style="margin: 20px;">
|
|
33
|
+
<h3 style="margin-top: 0;">⚠️ Error Loading Dashboard</h3>
|
|
34
|
+
<p>${escapeHtml(error)}</p>
|
|
35
|
+
<button class="btn btn-primary btn-sm" onclick="window.location.reload()">Retry</button>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!stats) {
|
|
42
|
+
container.innerHTML = `
|
|
43
|
+
<div class="alert alert-warning" style="margin: 20px;">
|
|
44
|
+
<h3 style="margin-top: 0;">⏳ Initializing</h3>
|
|
45
|
+
<p>Dashboard is loading. Please wait...</p>
|
|
46
|
+
</div>
|
|
47
|
+
`;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
renderDashboardContent(container, stats);
|
|
52
|
+
|
|
53
|
+
// Subscribe to updates
|
|
54
|
+
dashboardStore.subscribe((newState) => {
|
|
55
|
+
if (newState.stats) {
|
|
56
|
+
renderDashboardContent(container, newState.stats);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}).catch((error) => {
|
|
60
|
+
console.error('Dashboard error:', error);
|
|
61
|
+
container.innerHTML = `
|
|
62
|
+
<div class="alert alert-danger" style="margin: 20px;">
|
|
63
|
+
<h3 style="margin-top: 0;">❌ Failed to Load Dashboard</h3>
|
|
64
|
+
<p>An unexpected error occurred. Please check the console for details.</p>
|
|
65
|
+
<button class="btn btn-primary btn-sm" onclick="window.location.reload()">Reload Page</button>
|
|
66
|
+
</div>
|
|
67
|
+
`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render dashboard content
|
|
73
|
+
*/
|
|
74
|
+
function renderDashboardContent(container: HTMLElement, stats: DashboardStats): void {
|
|
75
|
+
container.innerHTML = `
|
|
76
|
+
<div class="dashboard-page fade-in">
|
|
77
|
+
<!-- Header -->
|
|
78
|
+
<div class="page-header mb-8">
|
|
79
|
+
<h1 class="text-3xl font-bold">📊 Dashboard</h1>
|
|
80
|
+
<p class="text-secondary mt-2">System overview and app management</p>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<!-- Stats Grid -->
|
|
84
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
|
85
|
+
${renderStatCard('📦 Installed Apps', stats.totalApps.toString(), 'Total applications detected')}
|
|
86
|
+
${renderStatCard('💾 Total Space Used', formatBytes(stats.totalSpaceUsed), 'Size of all app files')}
|
|
87
|
+
${renderStatCard('🗑️ Removed (Session)', stats.sessionAppsRemoved.toString(), 'Apps removed this session')}
|
|
88
|
+
${renderStatCard('📉 Space Freed', formatBytes(stats.sessionSpaceFreed), 'Total space recovered')}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- System Health -->
|
|
92
|
+
<div class="card mb-8">
|
|
93
|
+
<div class="card-header">
|
|
94
|
+
<h2 class="text-xl font-bold">💻 System Health</h2>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="card-body">
|
|
97
|
+
<div class="health-gauge mb-4">
|
|
98
|
+
<div class="flex-between mb-2">
|
|
99
|
+
<span class="text-sm font-medium">Disk Usage</span>
|
|
100
|
+
<span class="text-sm font-bold">${formatPercent(stats.diskUsagePercent)}</span>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="progress-bar" style="height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden;">
|
|
103
|
+
<div style="
|
|
104
|
+
height: 100%;
|
|
105
|
+
width: ${stats.diskUsagePercent}%;
|
|
106
|
+
background: ${getDiskUsageColor(stats.diskUsagePercent)};
|
|
107
|
+
transition: width 0.3s ease;
|
|
108
|
+
"></div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<p class="text-sm text-muted">
|
|
112
|
+
${stats.diskUsagePercent < 80
|
|
113
|
+
? '✅ Disk usage is healthy'
|
|
114
|
+
: stats.diskUsagePercent < 90
|
|
115
|
+
? '⚠️ Disk usage is getting high'
|
|
116
|
+
: '🔴 Disk usage is critical'}
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- Recent Activity -->
|
|
122
|
+
<div class="card">
|
|
123
|
+
<div class="card-header">
|
|
124
|
+
<h2 class="text-xl font-bold">📜 Recent Activity</h2>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="card-body">
|
|
127
|
+
${
|
|
128
|
+
stats.recentlyRemoved.length > 0
|
|
129
|
+
? `
|
|
130
|
+
<div class="activity-list">
|
|
131
|
+
${stats.recentlyRemoved
|
|
132
|
+
.slice(0, 10)
|
|
133
|
+
.map(
|
|
134
|
+
(removal) => `
|
|
135
|
+
<div class="activity-item flex-between p-3 border-b border-color last:border-0" style="padding: 12px 0;">
|
|
136
|
+
<div class="activity-info">
|
|
137
|
+
<p class="text-sm font-medium">${escapeHtml(removal.appName)}</p>
|
|
138
|
+
<p class="text-xs text-muted">Removed ${formatRelativeTime(removal.timestamp)}</p>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="activity-stats text-right">
|
|
141
|
+
<p class="text-sm font-bold text-success">${formatBytes(removal.freedSpace)}</p>
|
|
142
|
+
<p class="text-xs text-muted">${removal.filesRemoved} files</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
`
|
|
146
|
+
)
|
|
147
|
+
.join('')}
|
|
148
|
+
</div>
|
|
149
|
+
`
|
|
150
|
+
: `
|
|
151
|
+
<p class="text-muted text-center py-8">
|
|
152
|
+
No apps removed yet. Start by searching for apps to remove.
|
|
153
|
+
</p>
|
|
154
|
+
`
|
|
155
|
+
}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Quick Actions -->
|
|
160
|
+
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
161
|
+
<button class="btn btn-primary btn-lg w-full" onclick="window.location.hash = '#/apps'">
|
|
162
|
+
<span>🔍 Find & Remove Apps</span>
|
|
163
|
+
</button>
|
|
164
|
+
<button class="btn btn-secondary btn-lg w-full" onclick="window.location.hash = '#/settings'">
|
|
165
|
+
<span>⚙️ Settings</span>
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
`;
|
|
170
|
+
|
|
171
|
+
// Scroll to top
|
|
172
|
+
window.scrollTo(0, 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Render stat card
|
|
177
|
+
*/
|
|
178
|
+
function renderStatCard(label: string, value: string, description: string): string {
|
|
179
|
+
return `
|
|
180
|
+
<div class="card">
|
|
181
|
+
<div class="card-body">
|
|
182
|
+
<p class="text-muted text-sm mb-2">${escapeHtml(label)}</p>
|
|
183
|
+
<p class="text-2xl font-bold text-primary mb-1">${escapeHtml(value)}</p>
|
|
184
|
+
<p class="text-xs text-muted">${escapeHtml(description)}</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get disk usage color
|
|
192
|
+
*/
|
|
193
|
+
function getDiskUsageColor(percent: number): string {
|
|
194
|
+
if (percent < 50) return '#10b981'; // Green
|
|
195
|
+
if (percent < 80) return '#f59e0b'; // Amber
|
|
196
|
+
return '#ef4444'; // Red
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Escape HTML to prevent XSS
|
|
201
|
+
*/
|
|
202
|
+
function escapeHtml(text: string): string {
|
|
203
|
+
const map: Record<string, string> = {
|
|
204
|
+
'&': '&',
|
|
205
|
+
'<': '<',
|
|
206
|
+
'>': '>',
|
|
207
|
+
'"': '"',
|
|
208
|
+
"'": ''',
|
|
209
|
+
};
|
|
210
|
+
return text.replace(/[&<>"']/g, (m) => map[m]);
|
|
211
|
+
}
|