feat: add search functionality for processes and improve pagination controls

This commit is contained in:
Jonathan Atta
2026-03-11 17:28:42 +01:00
parent c2aecae867
commit 9ec6b4763c

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback, useRef } from 'react' import React, { useEffect, useState, useCallback, useRef } from 'react'
import { EventsOn, EventsOff } from '../wailsjs/runtime/runtime' import { EventsOn, EventsOff } from '../wailsjs/runtime/runtime'
import { Activity, Cpu, Database, Monitor, ChevronDown, ChevronRight, Layers, X, RefreshCw, OctagonX } from 'lucide-react' import { Activity, Cpu, Database, Monitor, ChevronDown, ChevronRight, Layers, X, RefreshCw, OctagonX, Search } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress' import { Progress } from '@/components/ui/progress'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@@ -525,6 +525,7 @@ export default function App() {
const [gpuOpen, setGpuOpen] = useState(true) const [gpuOpen, setGpuOpen] = useState(true)
const [gpuSortBy, setGpuSortBy] = useState<'vram' | 'ram'>('vram') const [gpuSortBy, setGpuSortBy] = useState<'vram' | 'ram'>('vram')
const [navStack, setNavStack] = useState<NavItem[]>([]) const [navStack, setNavStack] = useState<NavItem[]>([])
const [search, setSearch] = useState('')
const openDetail = useCallback((pid: number) => { const openDetail = useCallback((pid: number) => {
setSelectedPid(pid) setSelectedPid(pid)
@@ -554,15 +555,18 @@ export default function App() {
: 0 : 0
const gpuUtilPct = metrics?.gpu_util_percent ?? 0 const gpuUtilPct = metrics?.gpu_util_percent ?? 0
const gpuProcesses = [...(metrics?.gpu_processes ?? [])].sort((a, b) => const searchLower = search.toLowerCase()
gpuSortBy === 'vram' ? b.vram_mb - a.vram_mb : b.ram - a.ram const gpuProcesses = [...(metrics?.gpu_processes ?? [])]
) .filter(p => !search || p.name.toLowerCase().includes(searchLower) || String(p.pid).includes(search))
.sort((a, b) => gpuSortBy === 'vram' ? b.vram_mb - a.vram_mb : b.ram - a.ram)
const gpuTotalPages = Math.ceil(gpuProcesses.length / gpuPageSize) const gpuTotalPages = Math.ceil(gpuProcesses.length / gpuPageSize)
const safeGpuPage = Math.min(gpuPage, Math.max(0, gpuTotalPages - 1)) const safeGpuPage = Math.min(gpuPage, Math.max(0, gpuTotalPages - 1))
const pagedGpuProcesses = gpuProcesses.slice(safeGpuPage * gpuPageSize, (safeGpuPage + 1) * gpuPageSize) const pagedGpuProcesses = gpuProcesses.slice(safeGpuPage * gpuPageSize, (safeGpuPage + 1) * gpuPageSize)
const sortedProcesses = metrics const sortedProcesses = metrics
? [...metrics.processes].sort((a, b) => b[sortBy] - a[sortBy]) ? [...metrics.processes]
.filter(p => !search || p.name.toLowerCase().includes(searchLower) || String(p.pid).includes(search))
.sort((a, b) => b[sortBy] - a[sortBy])
: [] : []
const totalPages = Math.ceil(sortedProcesses.length / pageSize) const totalPages = Math.ceil(sortedProcesses.length / pageSize)
const safePage = Math.min(page, Math.max(0, totalPages - 1)) const safePage = Math.min(page, Math.max(0, totalPages - 1))
@@ -590,6 +594,26 @@ export default function App() {
</header> </header>
<main className="p-5 space-y-4 max-w-4xl mx-auto"> <main className="p-5 space-y-4 max-w-4xl mx-auto">
{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-zinc-600 pointer-events-none" />
<input
type="text"
placeholder="Search by name or PID…"
value={search}
onChange={e => { setSearch(e.target.value); setPage(0); setGpuPage(0) }}
className="w-full bg-zinc-900/60 border border-zinc-800 rounded-lg pl-8 pr-8 py-2 text-sm text-zinc-300 placeholder-zinc-700 focus:outline-none focus:border-zinc-600 transition-colors"
/>
{search && (
<button
onClick={() => { setSearch(''); setPage(0); setGpuPage(0) }}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-600 hover:text-zinc-400 transition-colors"
>
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
{/* Processes */} {/* Processes */}
<Card> <Card>
<CardHeader <CardHeader
@@ -717,14 +741,14 @@ export default function App() {
<span className="text-xs text-zinc-600"> <span className="text-xs text-zinc-600">
{safePage * pageSize + 1}{Math.min((safePage + 1) * pageSize, sortedProcesses.length)} of {sortedProcesses.length} {safePage * pageSize + 1}{Math.min((safePage + 1) * pageSize, sortedProcesses.length)} of {sortedProcesses.length}
</span> </span>
<select <div className="flex rounded border border-zinc-800 overflow-hidden" title="Items per page">
value={pageSize} {[10, 20, 50, 100].map(n => (
onChange={e => { setPageSize(Number(e.target.value)); setPage(0) }} <button key={n} onClick={() => { setPageSize(n); setPage(0) }}
className="text-xs bg-zinc-900 border border-zinc-800 text-zinc-400 rounded px-1.5 py-0.5 cursor-pointer hover:border-zinc-600 transition-colors" className={cn('px-1.5 py-0.5 text-xs transition-colors', pageSize === n ? 'bg-zinc-700 text-zinc-200' : 'bg-zinc-900 text-zinc-600 hover:text-zinc-400 hover:bg-zinc-800')}>
title="Items per page" {n}
> </button>
{[10, 20, 50, 100].map(n => <option key={n} value={n}>{n} / page</option>)} ))}
</select> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
@@ -910,14 +934,14 @@ export default function App() {
<span className="text-xs text-zinc-600"> <span className="text-xs text-zinc-600">
{safeGpuPage * gpuPageSize + 1}{Math.min((safeGpuPage + 1) * gpuPageSize, gpuProcesses.length)} of {gpuProcesses.length} {safeGpuPage * gpuPageSize + 1}{Math.min((safeGpuPage + 1) * gpuPageSize, gpuProcesses.length)} of {gpuProcesses.length}
</span> </span>
<select <div className="flex rounded border border-zinc-800 overflow-hidden" title="Items per page">
value={gpuPageSize} {[4, 10, 20, 50].map(n => (
onChange={e => { setGpuPageSize(Number(e.target.value)); setGpuPage(0) }} <button key={n} onClick={() => { setGpuPageSize(n); setGpuPage(0) }}
className="text-xs bg-zinc-900 border border-zinc-800 text-zinc-400 rounded px-1.5 py-0.5 cursor-pointer hover:border-zinc-600 transition-colors" className={cn('px-1.5 py-0.5 text-xs transition-colors', gpuPageSize === n ? 'bg-zinc-700 text-zinc-200' : 'bg-zinc-900 text-zinc-600 hover:text-zinc-400 hover:bg-zinc-800')}>
title="Items per page" {n}
> </button>
{[4, 10, 20, 50].map(n => <option key={n} value={n}>{n} / page</option>)} ))}
</select> </div>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button onClick={() => setGpuPage(0)} disabled={safeGpuPage === 0} <button onClick={() => setGpuPage(0)} disabled={safeGpuPage === 0}