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:
parent
f6cce7c82e
commit
a49dd83c5c
@ -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.
|
- **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
|
- **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`
|
- **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.
|
- **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
|
- **Verify**: Type in search — only matching apps shown
|
||||||
|
|||||||
@ -5,6 +5,16 @@
|
|||||||
<p class="text-white/70">Manage your installed applications</p>
|
<p class="text-white/70">Manage your installed applications</p>
|
||||||
</div>
|
</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 -->
|
<!-- Empty State - This should never show since we always show dummy apps -->
|
||||||
<div v-if="false" class="text-center py-16 pb-6">
|
<div v-if="false" class="text-center py-16 pb-6">
|
||||||
<div class="glass-card p-12 max-w-md mx-auto">
|
<div class="glass-card p-12 max-w-md mx-auto">
|
||||||
@ -22,10 +32,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</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) -->
|
<!-- 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 class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-6">
|
||||||
<div
|
<div
|
||||||
v-for="[id, pkg] in sortedPackageEntries"
|
v-for="[id, pkg] in filteredPackageEntries"
|
||||||
:key="id"
|
:key="id"
|
||||||
data-controller-container
|
data-controller-container
|
||||||
:data-controller-launch="canLaunch(pkg) ? '' : undefined"
|
:data-controller-launch="canLaunch(pkg) ? '' : undefined"
|
||||||
@ -181,6 +196,9 @@ import { useModalKeyboard } from '@/composables/useModalKeyboard'
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
|
|
||||||
|
// Search
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
// Track loading states for each app action
|
// Track loading states for each app action
|
||||||
const loadingActions = ref<Record<string, boolean>>({})
|
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({
|
const uninstallModal = ref({
|
||||||
show: false,
|
show: false,
|
||||||
appId: '',
|
appId: '',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user