peerman 0.2.8

DN42 peer manager with WireGuard, BIRD, and cluster support
import { RefreshCw } from 'lucide-react';
import { useWireGuardStatus, useBirdStatus, useApplyStatus, useApplyConfigNow } from '../../hooks/useManagement';

export default function StatusPage() {
  const wg = useWireGuardStatus();
  const bird = useBirdStatus();
  const applyStatus = useApplyStatus();
  const applyNow = useApplyConfigNow();

  return (
    <div className="space-y-lg animate-fade-in">
      <div className="flex items-center justify-between">
        <h1 className="text-display-md text-ink">System Status</h1>
        <button
          onClick={() => { wg.refetch(); bird.refetch(); applyStatus.refetch(); }}
          className="btn-ghost text-xs flex items-center gap-1"
        >
          <RefreshCw className="w-3 h-3" />
          Refresh
        </button>
      </div>

      {/* WireGuard */}
      <div className="card">
        <h2 className="text-body-md-strong text-ink mb-md">WireGuard</h2>
        {wg.loading && <div className="text-body-sm text-body">Loading...</div>}
        {wg.error && <div className="text-body-sm text-error">{wg.error}</div>}
        {!wg.loading && !wg.error && wg.interfaces.length === 0 && (
          <div className="text-body-sm text-body">No WireGuard interfaces found</div>
        )}
        {wg.interfaces.map((iface) => (
          <div key={iface.name} className="space-y-sm">
            <div className="text-body-sm text-body">
              {iface.name} — pubkey: <code className="code-block text-xs">{iface.publicKey.substring(0, 12)}...</code>, port: {iface.listenPort}
            </div>
            {iface.peers.length === 0 && (
              <div className="text-caption text-mute ml-md">No peers</div>
            )}
            {iface.peers.map((peer, i) => (
              <div key={i} className="card-soft text-caption">
                <div className="grid grid-cols-2 md:grid-cols-3 gap-xxs">
                  <div><span className="text-mute">Peer:</span> <code>{peer.publicKey.substring(0, 10)}...</code></div>
                  <div><span className="text-mute">Endpoint:</span> {peer.endpoint || '—'}</div>
                  <div><span className="text-mute">Handshake:</span> {peer.latestHandshake || '—'}</div>
                  <div><span className="text-mute">RX:</span> {peer.transferRx || '—'}</div>
                  <div><span className="text-mute">TX:</span> {peer.transferTx || '—'}</div>
                </div>
              </div>
            ))}
          </div>
        ))}
      </div>

      {/* BIRD */}
      <div className="card">
        <h2 className="text-body-md-strong text-ink mb-md">BIRD</h2>
        {bird.loading && <div className="text-body-sm text-body">Loading...</div>}
        {bird.error && <div className="text-body-sm text-error">{bird.error}</div>}
        {!bird.loading && !bird.error && bird.protocols.length === 0 && (
          <div className="text-body-sm text-body">No BIRD protocols found</div>
        )}
        {!bird.loading && !bird.error && bird.protocols.length > 0 && (
          <div className="data-table">
            <table>
              <thead>
                <tr>
                  <th>Name</th>
                  <th>Proto</th>
                  <th>Table</th>
                  <th>State</th>
                  <th>Since</th>
                  <th>Info</th>
                </tr>
              </thead>
              <tbody>
                {bird.protocols.map((p) => (
                  <tr key={p.name}>
                    <td className="font-mono text-caption-mono">{p.name}</td>
                    <td>{p.proto}</td>
                    <td>{p.table}</td>
                    <td>
                      <span className={`badge ${p.state === 'up' ? 'bg-green-500/20 text-green-500' : 'bg-red-500/20 text-red-500'}`}>
                        {p.state}
                      </span>
                    </td>
                    <td className="text-mute">{p.since}</td>
                    <td className="text-mute text-xs">{p.info}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </div>

      {/* Config Status */}
      <div className="card">
        <h2 className="text-body-md-strong text-ink mb-md">Config Status</h2>
        {applyStatus.loading && <div className="text-body-sm text-body">Loading...</div>}
        {applyStatus.error && <div className="text-body-sm text-error">{applyStatus.error}</div>}
        {!applyStatus.loading && !applyStatus.error && applyStatus.status && (
          <div className="space-y-sm">
            <div className="grid grid-cols-2 gap-sm text-body-sm">
              <div>
                <span className="text-mute">Status:</span>{' '}
                <span className={`badge ${
                  applyStatus.status.pending
                    ? 'bg-yellow-500/20 text-yellow-500'
                    : applyStatus.status.lastError
                      ? 'bg-red-500/20 text-red-500'
                      : 'bg-green-500/20 text-green-500'
                }`}>
                  {applyStatus.status.pending ? 'pending' : applyStatus.status.lastError ? 'error' : 'synced'}
                </span>
              </div>
              <div>
                <span className="text-mute">Last Apply:</span>{' '}
                {applyStatus.status.lastApplyAt || '—'}
              </div>
            </div>
            {applyStatus.status.lastError && (
              <div className="text-body-sm text-error bg-error-soft px-md py-sm rounded-sm">
                {applyStatus.status.lastError}
              </div>
            )}
            {applyStatus.status.managedInterfaces.length > 0 && (
              <div className="text-caption text-mute">
                Managed interfaces: {applyStatus.status.managedInterfaces.join(', ')}
              </div>
            )}
            <button
              onClick={async () => {
                await applyNow.apply();
                applyStatus.refetch();
              }}
              disabled={applyNow.loading}
              className="btn-primary text-sm mt-sm"
            >
              {applyNow.loading ? 'Applying...' : 'Apply Now'}
            </button>
          </div>
        )}
      </div>
    </div>
  );
}