feat: add local search filter to My Apps view

Adds a search input to the Apps page that filters installed apps by title,
description, or app ID. Styled consistently with the Marketplace search bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-05 08:06:07 +00:00
parent f6cce7c82e
commit a49dd83c5c
2 changed files with 30 additions and 2 deletions

View File

@ -79,7 +79,7 @@ After getting Claude Max OAuth working on the live server, hardening the deploy
- **Change**: Compare `get_app_config()` port mappings with nginx proxies. Add missing nginx proxies for: Grafana (3000), Jellyfin (8096), Uptime Kuma (3001), Portainer (9000), OnlyOffice (9980). Add to both HTTP and HTTPS blocks. Verify `extract_lan_address()` correctness.
- **Verify**: Each app launches correctly from Apps page
### Task 14: Local search within Apps view
### Task 14: Local search within Apps view [DONE]
- **Files**: `neode-ui/src/views/Apps.vue`
- **Change**: Add search input with `ref('')`. Filter `sortedPackageEntries` by query against `manifest.title` and `manifest.description.short`. Style like Marketplace search.
- **Verify**: Type in search — only matching apps shown

View File

@ -5,6 +5,16 @@
<p class="text-white/70">Manage your installed applications</p>
</div>
<!-- Search Bar -->
<div class="mb-4">
<input
v-model="searchQuery"
type="text"
placeholder="Search installed apps..."
class="w-full px-4 py-3 md:py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
/>
</div>
<!-- Empty State - This should never show since we always show dummy apps -->
<div v-if="false" class="text-center py-16 pb-6">
<div class="glass-card p-12 max-w-md mx-auto">
@ -22,10 +32,15 @@
</div>
</div>
<!-- No Results -->
<div v-if="filteredPackageEntries.length === 0 && searchQuery" class="text-center py-12">
<p class="text-white/70">No apps matching "{{ searchQuery }}"</p>
</div>
<!-- Apps Grid (alphabetically by title, stable across run state) -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-6">
<div
v-for="[id, pkg] in sortedPackageEntries"
v-for="[id, pkg] in filteredPackageEntries"
:key="id"
data-controller-container
:data-controller-launch="canLaunch(pkg) ? '' : undefined"
@ -181,6 +196,9 @@ import { useModalKeyboard } from '@/composables/useModalKeyboard'
const router = useRouter()
const store = useAppStore()
// Search
const searchQuery = ref('')
// Track loading states for each app action
const loadingActions = ref<Record<string, boolean>>({})
@ -199,6 +217,16 @@ const sortedPackageEntries = computed(() => {
)
})
const filteredPackageEntries = computed(() => {
if (!searchQuery.value) return sortedPackageEntries.value
const q = searchQuery.value.toLowerCase()
return sortedPackageEntries.value.filter(([id, pkg]) =>
(pkg.manifest?.title ?? '').toLowerCase().includes(q) ||
(pkg.manifest?.description?.short ?? '').toLowerCase().includes(q) ||
id.toLowerCase().includes(q)
)
})
const uninstallModal = ref({
show: false,
appId: '',