system-monitor: sortable columns in the pm2 view (click headers; asc/desc)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,31 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { fmtBytes, fmtDuration } from './shared';
|
||||
|
||||
type Proc = { id: number; name: string; status: string; pid: number | null; cpuPct: number; memBytes: number; restarts: number; uptimeMs: number };
|
||||
type SortKey = 'id' | 'name' | 'status' | 'cpuPct' | 'memBytes' | 'restarts' | 'uptimeMs' | 'pid';
|
||||
|
||||
const POLL_MS = 2000;
|
||||
const statusColor = (s: string) =>
|
||||
s === 'online' ? 'bg-emerald-500' : s === 'stopped' ? 'bg-muted-foreground/40' : s === 'errored' ? 'bg-red-500' : 'bg-amber-500';
|
||||
|
||||
const COLS: { key: SortKey; label: string; align: 'left' | 'right' }[] = [
|
||||
{ key: 'id', label: '#', align: 'right' },
|
||||
{ key: 'name', label: 'Name', align: 'left' },
|
||||
{ key: 'status', label: 'Status', align: 'left' },
|
||||
{ key: 'cpuPct', label: 'CPU%', align: 'right' },
|
||||
{ key: 'memBytes', label: 'Mem', align: 'right' },
|
||||
{ key: 'restarts', label: '↺', align: 'right' },
|
||||
{ key: 'uptimeMs', label: 'Uptime', align: 'left' },
|
||||
{ key: 'pid', label: 'PID', align: 'right' },
|
||||
];
|
||||
|
||||
export const Pm2View = () => {
|
||||
const { get } = useClient();
|
||||
const [procs, setProcs] = useState<Proc[] | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [sortKey, setSortKey] = useState<SortKey>('cpuPct');
|
||||
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc');
|
||||
const alive = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,6 +47,25 @@ export const Pm2View = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleSort = (key: SortKey) => {
|
||||
if (key === sortKey) setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
|
||||
else {
|
||||
setSortKey(key);
|
||||
setSortDir(key === 'name' || key === 'status' ? 'asc' : 'desc'); // text asc, numeric desc by default
|
||||
}
|
||||
};
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const arr = [...(procs ?? [])];
|
||||
arr.sort((a, b) => {
|
||||
const av = a[sortKey] ?? 0;
|
||||
const bv = b[sortKey] ?? 0;
|
||||
const cmp = typeof av === 'string' && typeof bv === 'string' ? av.localeCompare(bv) : (av as number) - (bv as number);
|
||||
return sortDir === 'asc' ? cmp : -cmp;
|
||||
});
|
||||
return arr;
|
||||
}, [procs, sortKey, sortDir]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto p-4 md:p-6">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
@@ -42,18 +75,22 @@ export const Pm2View = () => {
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||
<th className="p-3 text-right font-medium">#</th>
|
||||
<th className="p-3 font-medium">Name</th>
|
||||
<th className="p-3 font-medium">Status</th>
|
||||
<th className="p-3 text-right font-medium">CPU%</th>
|
||||
<th className="p-3 text-right font-medium">Mem</th>
|
||||
<th className="p-3 text-right font-medium">↺</th>
|
||||
<th className="p-3 font-medium">Uptime</th>
|
||||
<th className="p-3 text-right font-medium">PID</th>
|
||||
{COLS.map((c) => (
|
||||
<th key={c.key} className={`p-3 font-medium ${c.align === 'right' ? 'text-right' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleSort(c.key)}
|
||||
className={`inline-flex items-center gap-1 hover:text-foreground ${c.align === 'right' ? 'flex-row-reverse' : ''} ${sortKey === c.key ? 'text-foreground' : ''}`}
|
||||
>
|
||||
{c.label}
|
||||
<span className="w-2 text-[10px]">{sortKey === c.key ? (sortDir === 'asc' ? '▲' : '▼') : ''}</span>
|
||||
</button>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(procs ?? []).map((p) => (
|
||||
{sorted.map((p) => (
|
||||
<tr key={p.id} className="border-t border-border/60">
|
||||
<td className="p-3 text-right font-mono text-muted-foreground">{p.id}</td>
|
||||
<td className="p-3 font-medium text-foreground">{p.name}</td>
|
||||
|
||||
Reference in New Issue
Block a user