feat: add GetManPage function and integrate parent process name in ProcessDetail
This commit is contained in:
@@ -47,6 +47,7 @@ type ProcessDetail = {
|
||||
username: string
|
||||
created_at: number
|
||||
parent_pid: number
|
||||
parent_name: string
|
||||
nice: number
|
||||
num_threads: number
|
||||
num_fds: number
|
||||
@@ -64,6 +65,7 @@ type ProcessDetail = {
|
||||
}
|
||||
|
||||
type SortField = 'cpu' | 'mem'
|
||||
type NavItem = { pid: number; name: string }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -119,10 +121,10 @@ function MetricValue({ value, pct }: { value: string; pct: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
function DetailRow({ label, value, mono = false }: { label: string; value: React.ReactNode; mono?: boolean }) {
|
||||
function DetailRow({ label, value, mono = false, tooltip }: { label: string; value: React.ReactNode; mono?: boolean; tooltip?: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="text-[10px] font-semibold uppercase tracking-widest text-zinc-600">{label}</span>
|
||||
<div className="flex flex-col gap-0.5" title={tooltip}>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-widest text-zinc-600 cursor-help">{label}</span>
|
||||
<span className={cn('text-xs text-zinc-300 break-all', mono && 'font-mono')}>{value || '—'}</span>
|
||||
</div>
|
||||
)
|
||||
@@ -136,9 +138,9 @@ function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
)
|
||||
}
|
||||
|
||||
function StatBlock({ label, value, sub }: { label: string; value: React.ReactNode; sub?: string }) {
|
||||
function StatBlock({ label, value, sub, tooltip }: { label: string; value: React.ReactNode; sub?: string; tooltip?: string }) {
|
||||
return (
|
||||
<div className="bg-zinc-800/40 rounded-lg px-3 py-2.5">
|
||||
<div className="bg-zinc-800/40 rounded-lg px-3 py-2.5 cursor-help" title={tooltip}>
|
||||
<p className="text-[10px] text-zinc-600 uppercase tracking-widest mb-1">{label}</p>
|
||||
<p className="text-sm font-semibold text-zinc-100 tabular-nums">{value}</p>
|
||||
{sub && <p className="text-[10px] text-zinc-600 mt-0.5">{sub}</p>}
|
||||
@@ -153,16 +155,28 @@ function StatBlock({ label, value, sub }: { label: string; value: React.ReactNod
|
||||
function ProcessDetailPanel({
|
||||
pid,
|
||||
onClose,
|
||||
onNavigate,
|
||||
navStack,
|
||||
onNavBack,
|
||||
gpuType,
|
||||
}: {
|
||||
pid: number
|
||||
onClose: () => void
|
||||
onNavigate: (pid: number, fromName: string) => void
|
||||
navStack: NavItem[]
|
||||
onNavBack: (index: number) => void
|
||||
gpuType?: string
|
||||
}) {
|
||||
const [detail, setDetail] = useState<ProcessDetail | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [killing, setKilling] = useState(false)
|
||||
const [killConfirm, setKillConfirm] = useState(false)
|
||||
const [killError, setKillError] = useState<string | null>(null)
|
||||
const [manPage, setManPage] = useState<string | null>(null)
|
||||
const [manPageLoading, setManPageLoading] = useState(false)
|
||||
const [manOpen, setManOpen] = useState(false)
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const lastManNameRef = useRef<string>('')
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
@@ -204,6 +218,17 @@ function ProcessDetailPanel({
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [onClose])
|
||||
|
||||
useEffect(() => {
|
||||
if (!detail?.name || detail.name === lastManNameRef.current) return
|
||||
lastManNameRef.current = detail.name
|
||||
setManPageLoading(true)
|
||||
setManPage(null)
|
||||
;(window as any)['go']['main']['App']['GetManPage'](detail.name)
|
||||
.then((text: string) => { setManPage(text || null) })
|
||||
.catch(() => setManPage(null))
|
||||
.finally(() => setManPageLoading(false))
|
||||
}, [detail?.name])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
@@ -217,10 +242,40 @@ function ProcessDetailPanel({
|
||||
{/* Panel header */}
|
||||
<div className="flex items-start justify-between px-5 py-4 border-b border-zinc-800/80 shrink-0">
|
||||
<div className="min-w-0 flex-1 pr-4">
|
||||
{navStack.length > 0 && (
|
||||
<div className="flex items-center gap-1 flex-wrap mb-1.5 min-w-0">
|
||||
{navStack.map((item, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<button
|
||||
onClick={() => onNavBack(i)}
|
||||
className="text-[10px] text-zinc-500 hover:text-zinc-300 transition-colors hover:underline truncate max-w-[100px]"
|
||||
title={`Back to ${item.name || `PID ${item.pid}`} (PID ${item.pid})`}
|
||||
>
|
||||
{item.name || `PID ${item.pid}`}
|
||||
</button>
|
||||
<ChevronRight className="w-2.5 h-2.5 text-zinc-700 shrink-0" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
<span className="text-[10px] text-zinc-400 truncate max-w-[100px]">{detail?.name ?? `PID ${pid}`}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-sm font-semibold text-zinc-100 truncate">
|
||||
{detail?.name ?? `PID ${pid}`}
|
||||
</span>
|
||||
{gpuType && (
|
||||
<span
|
||||
title={gpuType === 'C' ? 'GPU Compute — CUDA/OpenCL/Vulkan Compute' : gpuType === 'G' ? 'GPU Graphics — rendering & display' : 'GPU Compute + Graphics'}
|
||||
className={cn(
|
||||
'shrink-0 text-[10px] font-mono px-1.5 py-0.5 rounded cursor-help',
|
||||
gpuType === 'C' ? 'bg-violet-900/50 text-violet-300' :
|
||||
gpuType === 'G' ? 'bg-blue-900/50 text-blue-300' :
|
||||
'bg-indigo-900/50 text-indigo-300'
|
||||
)}
|
||||
>
|
||||
GPU·{gpuType}
|
||||
</span>
|
||||
)}
|
||||
{detail?.status && (
|
||||
<span
|
||||
className={cn(
|
||||
@@ -315,25 +370,39 @@ function ProcessDetailPanel({
|
||||
{/* Identity */}
|
||||
<SectionTitle>Identity</SectionTitle>
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
<StatBlock label="Parent PID" value={detail.parent_pid > 0 ? detail.parent_pid : '—'} />
|
||||
<StatBlock label="Nice / Priority" value={detail.nice} />
|
||||
{detail.parent_pid > 0 ? (
|
||||
<div
|
||||
className="bg-zinc-800/40 rounded-lg px-3 py-2.5 cursor-pointer hover:bg-zinc-700/50 transition-colors group"
|
||||
title={`Navigate to parent process${detail.parent_name ? ` (${detail.parent_name})` : ''}`}
|
||||
onClick={() => onNavigate(detail.parent_pid, detail.name)}
|
||||
>
|
||||
<p className="text-[10px] text-zinc-600 uppercase tracking-widest mb-1 group-hover:text-zinc-400">Parent PID</p>
|
||||
<p className="text-sm font-semibold tabular-nums text-emerald-400/80 group-hover:text-emerald-400 transition-colors">{detail.parent_pid}</p>
|
||||
{detail.parent_name && <p className="text-[10px] text-zinc-500 mt-0.5 truncate">{detail.parent_name}</p>}
|
||||
</div>
|
||||
) : (
|
||||
<StatBlock label="Parent PID" value="—" tooltip="No parent process (root process)" />
|
||||
)}
|
||||
<StatBlock label="Nice / Priority" value={detail.nice} tooltip="Scheduling priority: -20 = highest, +19 = lowest. Default is 0. Lower values get more CPU time" />
|
||||
<StatBlock
|
||||
label="Created"
|
||||
value={detail.created_at ? new Date(detail.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '—'}
|
||||
sub={detail.created_at ? fmtRelative(detail.created_at) : undefined}
|
||||
tooltip="Time when this process was started"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Paths */}
|
||||
<SectionTitle>Executable</SectionTitle>
|
||||
<div className="space-y-3 mb-4">
|
||||
<DetailRow label="Path" value={detail.exe} mono />
|
||||
<DetailRow label="Path" value={detail.exe} mono tooltip="Full filesystem path to the executable binary" />
|
||||
<DetailRow
|
||||
label="Command"
|
||||
value={detail.cmdline || detail.exe}
|
||||
mono
|
||||
tooltip="Full command line including arguments used to launch this process"
|
||||
/>
|
||||
{detail.cwd && <DetailRow label="Working directory" value={detail.cwd} mono />}
|
||||
{detail.cwd && <DetailRow label="Working directory" value={detail.cwd} mono tooltip="Current working directory of the process at the time of sampling" />}
|
||||
</div>
|
||||
|
||||
{/* CPU & Memory */}
|
||||
@@ -342,26 +411,31 @@ function ProcessDetailPanel({
|
||||
<StatBlock
|
||||
label="CPU"
|
||||
value={`${detail.cpu_percent.toFixed(1)}%`}
|
||||
tooltip="CPU usage as a percentage of one core (can exceed 100% on multi-threaded processes)"
|
||||
/>
|
||||
<StatBlock
|
||||
label="Threads"
|
||||
value={detail.num_threads}
|
||||
sub={`${detail.num_fds} open FDs`}
|
||||
tooltip="Number of OS threads running in this process. Open FDs = open file descriptors (files, sockets, pipes, devices)"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
<StatBlock
|
||||
label="RSS (physical)"
|
||||
value={fmtBytes(detail.rss)}
|
||||
tooltip="Resident Set Size — physical RAM pages currently mapped and in use by this process"
|
||||
/>
|
||||
<StatBlock
|
||||
label="VMS (virtual)"
|
||||
value={fmtBytes(detail.vms)}
|
||||
tooltip="Virtual Memory Size — total virtual address space reserved, including shared libraries and mapped files"
|
||||
/>
|
||||
<StatBlock
|
||||
label="Swap"
|
||||
value={fmtBytes(detail.swap)}
|
||||
sub={`${detail.mem_percent.toFixed(1)}% of RAM`}
|
||||
tooltip="Memory paged out to disk swap. % = share of total system RAM used by this process"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -374,11 +448,13 @@ function ProcessDetailPanel({
|
||||
label="Read"
|
||||
value={fmtBytes(detail.read_bytes)}
|
||||
sub={`${detail.read_ops.toLocaleString()} ops`}
|
||||
tooltip="Total bytes read from disk since process start. ops = number of read syscalls issued"
|
||||
/>
|
||||
<StatBlock
|
||||
label="Write"
|
||||
value={fmtBytes(detail.write_bytes)}
|
||||
sub={`${detail.write_ops.toLocaleString()} ops`}
|
||||
tooltip="Total bytes written to disk since process start. ops = number of write syscalls issued"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
@@ -390,12 +466,39 @@ function ProcessDetailPanel({
|
||||
<StatBlock
|
||||
label="Open files"
|
||||
value={detail.open_files_count}
|
||||
tooltip="Number of open file descriptors: regular files, sockets, pipes, and device handles"
|
||||
/>
|
||||
<StatBlock
|
||||
label="Connections"
|
||||
value={detail.conn_count}
|
||||
tooltip="Number of active network connections (TCP/UDP sockets) opened by this process"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Man page */}
|
||||
{(manPage !== null || manPageLoading) && (
|
||||
<>
|
||||
<SectionTitle>Manual page</SectionTitle>
|
||||
{manPageLoading ? (
|
||||
<p className="text-xs text-zinc-700 mb-4">Loading…</p>
|
||||
) : (
|
||||
<div className="mb-4">
|
||||
<button
|
||||
onClick={() => setManOpen(o => !o)}
|
||||
className="flex items-center gap-1.5 text-xs text-zinc-600 hover:text-zinc-400 transition-colors mb-2"
|
||||
>
|
||||
{manOpen ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
|
||||
{manOpen ? 'Collapse' : 'Show'} man {detail.name}
|
||||
</button>
|
||||
{manOpen && (
|
||||
<pre className="text-[10px] font-mono text-zinc-500 whitespace-pre-wrap break-words bg-zinc-900/60 rounded-lg p-3 max-h-80 overflow-y-auto leading-relaxed border border-zinc-800/60">
|
||||
{manPage}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -421,9 +524,15 @@ export default function App() {
|
||||
const [procOpen, setProcOpen] = useState(true)
|
||||
const [gpuOpen, setGpuOpen] = useState(true)
|
||||
const [gpuSortBy, setGpuSortBy] = useState<'vram' | 'ram'>('vram')
|
||||
const [navStack, setNavStack] = useState<NavItem[]>([])
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
const GPU_PAGE_SIZE = 4
|
||||
const openDetail = useCallback((pid: number) => {
|
||||
setSelectedPid(pid)
|
||||
setNavStack([])
|
||||
}, [])
|
||||
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [gpuPageSize, setGpuPageSize] = useState(4)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (m: Metrics) => setMetrics(m)
|
||||
@@ -448,16 +557,16 @@ export default function App() {
|
||||
const gpuProcesses = [...(metrics?.gpu_processes ?? [])].sort((a, b) =>
|
||||
gpuSortBy === 'vram' ? b.vram_mb - a.vram_mb : b.ram - a.ram
|
||||
)
|
||||
const gpuTotalPages = Math.ceil(gpuProcesses.length / GPU_PAGE_SIZE)
|
||||
const gpuTotalPages = Math.ceil(gpuProcesses.length / gpuPageSize)
|
||||
const safeGpuPage = Math.min(gpuPage, Math.max(0, gpuTotalPages - 1))
|
||||
const pagedGpuProcesses = gpuProcesses.slice(safeGpuPage * GPU_PAGE_SIZE, (safeGpuPage + 1) * GPU_PAGE_SIZE)
|
||||
const pagedGpuProcesses = gpuProcesses.slice(safeGpuPage * gpuPageSize, (safeGpuPage + 1) * gpuPageSize)
|
||||
|
||||
const sortedProcesses = metrics
|
||||
? [...metrics.processes].sort((a, b) => b[sortBy] - a[sortBy])
|
||||
: []
|
||||
const totalPages = Math.ceil(sortedProcesses.length / PAGE_SIZE)
|
||||
const totalPages = Math.ceil(sortedProcesses.length / pageSize)
|
||||
const safePage = Math.min(page, Math.max(0, totalPages - 1))
|
||||
const pagedProcesses = sortedProcesses.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE)
|
||||
const pagedProcesses = sortedProcesses.slice(safePage * pageSize, (safePage + 1) * pageSize)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 text-zinc-100 antialiased select-none">
|
||||
@@ -554,17 +663,17 @@ export default function App() {
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-zinc-800/80">
|
||||
<th className="py-2 px-5 text-left text-xs font-medium text-zinc-600 uppercase tracking-wider w-16">PID</th>
|
||||
<th className="py-2 text-left text-xs font-medium text-zinc-600 uppercase tracking-wider">Name</th>
|
||||
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28">CPU</th>
|
||||
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28">Memory</th>
|
||||
<th className="py-2 px-5 text-left text-xs font-medium text-zinc-600 uppercase tracking-wider w-16 cursor-help" title="Process ID — unique identifier assigned by the OS">PID</th>
|
||||
<th className="py-2 text-left text-xs font-medium text-zinc-600 uppercase tracking-wider cursor-help" title="Executable name of the process">Name</th>
|
||||
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28 cursor-help" title="CPU usage as a % of a single core (can exceed 100% on multi-threaded processes)">CPU</th>
|
||||
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28 cursor-help" title="Physical RAM (RSS) currently used by this process">Memory</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pagedProcesses.map((p) => (
|
||||
<tr
|
||||
key={p.pid}
|
||||
onClick={() => setSelectedPid(p.pid)}
|
||||
onClick={() => openDetail(p.pid)}
|
||||
className={cn(
|
||||
'border-b border-zinc-900 transition-colors cursor-pointer',
|
||||
selectedPid === p.pid
|
||||
@@ -602,11 +711,21 @@ export default function App() {
|
||||
</table>
|
||||
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
{totalPages > 0 && (
|
||||
<div className="flex items-center justify-between px-5 py-3 border-t border-zinc-800/60">
|
||||
<span className="text-xs text-zinc-600">
|
||||
{safePage * PAGE_SIZE + 1}–{Math.min((safePage + 1) * PAGE_SIZE, sortedProcesses.length)} of {sortedProcesses.length}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-zinc-600">
|
||||
{safePage * pageSize + 1}–{Math.min((safePage + 1) * pageSize, sortedProcesses.length)} of {sortedProcesses.length}
|
||||
</span>
|
||||
<select
|
||||
value={pageSize}
|
||||
onChange={e => { setPageSize(Number(e.target.value)); 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"
|
||||
title="Items per page"
|
||||
>
|
||||
{[10, 20, 50, 100].map(n => <option key={n} value={n}>{n} / page</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => setPage(0)}
|
||||
@@ -746,10 +865,10 @@ export default function App() {
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider">Name</th>
|
||||
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider w-16">Type</th>
|
||||
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-24">VRAM</th>
|
||||
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-28">RAM</th>
|
||||
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider cursor-help" title="Process name using this GPU">Name</th>
|
||||
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider w-16 cursor-help" title="C = Compute (CUDA/OpenCL), G = Graphics (rendering), C+G = both">Type</th>
|
||||
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-24 cursor-help" title="GPU VRAM used by this process (video memory on the graphics card)">VRAM</th>
|
||||
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-28 cursor-help" title="System RAM (CPU-side memory) used by this process">RAM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -757,13 +876,18 @@ export default function App() {
|
||||
<tr
|
||||
key={p.pid}
|
||||
className="border-t border-zinc-900 hover:bg-zinc-800/30 transition-colors cursor-pointer"
|
||||
onClick={() => setSelectedPid(p.pid)}
|
||||
onClick={() => openDetail(p.pid)}
|
||||
>
|
||||
<td className="py-1.5 text-zinc-300 text-xs truncate max-w-[180px]">{p.name}</td>
|
||||
<td className="py-1.5">
|
||||
<span
|
||||
title={
|
||||
p.type === 'C' ? 'C — Compute: uses the GPU for compute workloads (CUDA, OpenCL, Vulkan Compute…)' :
|
||||
p.type === 'G' ? 'G — Graphics: uses the GPU for rendering and display output' :
|
||||
'C+G — Compute + Graphics: uses the GPU for both compute and rendering workloads'
|
||||
}
|
||||
className={cn(
|
||||
'inline-block px-1.5 py-0.5 rounded text-xs font-mono font-semibold',
|
||||
'inline-block px-1.5 py-0.5 rounded text-xs font-mono font-semibold cursor-help',
|
||||
p.type === 'C' ? 'bg-violet-900/50 text-violet-300' :
|
||||
p.type === 'G' ? 'bg-blue-900/50 text-blue-300' :
|
||||
'bg-indigo-900/50 text-indigo-300'
|
||||
@@ -780,11 +904,21 @@ export default function App() {
|
||||
</table>
|
||||
|
||||
{/* GPU pagination */}
|
||||
{gpuTotalPages > 1 && (
|
||||
{gpuTotalPages > 0 && (
|
||||
<div className="flex items-center justify-between pt-2 mt-1 border-t border-zinc-900">
|
||||
<span className="text-xs text-zinc-600">
|
||||
{safeGpuPage * GPU_PAGE_SIZE + 1}–{Math.min((safeGpuPage + 1) * GPU_PAGE_SIZE, gpuProcesses.length)} of {gpuProcesses.length}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-zinc-600">
|
||||
{safeGpuPage * gpuPageSize + 1}–{Math.min((safeGpuPage + 1) * gpuPageSize, gpuProcesses.length)} of {gpuProcesses.length}
|
||||
</span>
|
||||
<select
|
||||
value={gpuPageSize}
|
||||
onChange={e => { setGpuPageSize(Number(e.target.value)); 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"
|
||||
title="Items per page"
|
||||
>
|
||||
{[4, 10, 20, 50].map(n => <option key={n} value={n}>{n} / page</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button onClick={() => setGpuPage(0)} disabled={safeGpuPage === 0}
|
||||
className="px-2 py-1 rounded text-xs text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">«</button>
|
||||
@@ -827,7 +961,18 @@ export default function App() {
|
||||
{selectedPid !== null && (
|
||||
<ProcessDetailPanel
|
||||
pid={selectedPid}
|
||||
onClose={() => setSelectedPid(null)}
|
||||
onClose={() => { setSelectedPid(null); setNavStack([]) }}
|
||||
onNavigate={(targetPid, fromName) => {
|
||||
setNavStack(s => [...s, { pid: selectedPid, name: fromName }])
|
||||
setSelectedPid(targetPid)
|
||||
}}
|
||||
navStack={navStack}
|
||||
onNavBack={(index) => {
|
||||
const target = navStack[index]
|
||||
setNavStack(s => s.slice(0, index))
|
||||
setSelectedPid(target.pid)
|
||||
}}
|
||||
gpuType={metrics?.gpu_processes?.find(gp => gp.pid === selectedPid)?.type}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
@@ -3,6 +3,8 @@
|
||||
import {backend} from '../models';
|
||||
import {context} from '../models';
|
||||
|
||||
export function GetManPage(arg1:string):Promise<string>;
|
||||
|
||||
export function GetMetrics():Promise<backend.Metrics>;
|
||||
|
||||
export function GetProcessDetail(arg1:number):Promise<backend.ProcessDetail>;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function GetManPage(arg1) {
|
||||
return window['go']['main']['App']['GetManPage'](arg1);
|
||||
}
|
||||
|
||||
export function GetMetrics() {
|
||||
return window['go']['main']['App']['GetMetrics']();
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ export namespace backend {
|
||||
username: string;
|
||||
created_at: number;
|
||||
parent_pid: number;
|
||||
parent_name: string;
|
||||
nice: number;
|
||||
num_threads: number;
|
||||
num_fds: number;
|
||||
@@ -126,6 +127,7 @@ export namespace backend {
|
||||
this.username = source["username"];
|
||||
this.created_at = source["created_at"];
|
||||
this.parent_pid = source["parent_pid"];
|
||||
this.parent_name = source["parent_name"];
|
||||
this.nice = source["nice"];
|
||||
this.num_threads = source["num_threads"];
|
||||
this.num_fds = source["num_fds"];
|
||||
|
||||
Reference in New Issue
Block a user