trusty-search 0.2.3

Machine-wide hybrid code search service: BM25 + vector + KG, zero cold-start, MCP server
<script>
  /*
   * Why: The dashboard is the operator's at-a-glance view of daemon health
   * and the index catalogue — it surfaces the four headline numbers
   * (Indexes, Total documents, Uptime, Version) plus a recent-indexes table
   * that doubles as a jump-off to the Indexes view.
   * What: Stat-cards grid + a recent-indexes table. All data flows through
   * the centralised state store (`state.svelte.js`).
   * Test: `pnpm dev` in `ui/`, open http://127.0.0.1:7878/ui, confirm the
   * counters render and clicking a row navigates to /indexes.
   */
  import { getHealth, getIndexes } from '../state.svelte.js';
  import { navigate } from '../router.svelte.js';

  let health = $derived(getHealth());
  let indexes = $derived(getIndexes());

  let totalDocuments = $derived(
    indexes.reduce((sum, ix) => sum + (ix.chunk_count || 0), 0)
  );

  let recent = $derived(
    [...indexes]
      .sort((a, b) => (b.chunk_count || 0) - (a.chunk_count || 0))
      .slice(0, 10)
  );

  /**
   * Why: Operators want a quick "how long has this been up?" signal — raw
   * second counts get unreadable past a few minutes.
   * What: Returns a humanised "Xs / Xm / Xh / Xd" string.
   * Test: Pass 7200 (2 hours), expect "2h".
   */
  function humanUptime(secs) {
    if (typeof secs !== 'number' || secs < 0) return '—';
    if (secs < 60) return `${secs}s`;
    const m = Math.floor(secs / 60);
    if (m < 60) return `${m}m`;
    const h = Math.floor(m / 60);
    if (h < 24) return `${h}h`;
    const d = Math.floor(h / 24);
    return `${d}d`;
  }
</script>

<h1 class="page-title">Dashboard</h1>

<div class="stat-grid">
  <div class="stat">
    <div class="stat-label">Indexes</div>
    <div class="stat-value">{indexes.length}</div>
    <div class="stat-meta">registered</div>
  </div>
  <div class="stat">
    <div class="stat-label">Documents</div>
    <div class="stat-value">{totalDocuments.toLocaleString()}</div>
    <div class="stat-meta">indexed chunks</div>
  </div>
  <div class="stat">
    <div class="stat-label">Uptime</div>
    <div class="stat-value">{humanUptime(health?.uptime_secs)}</div>
    <div class="stat-meta">daemon</div>
  </div>
  <div class="stat">
    <div class="stat-label">Version</div>
    <div class="stat-value text-mono" style="font-size: var(--trusty-fs-lg)">
      {health?.version ?? '—'}
    </div>
    <div class="stat-meta">
      {#if health?.status === 'ok'}
        <span class="badge badge-success">healthy</span>
      {:else}
        <span class="badge badge-muted">offline</span>
      {/if}
    </div>
  </div>
</div>

<div class="card mt-4">
  <div class="card-header flex-between">
    <span>Recent indexes</span>
    <button class="btn btn-sm btn-primary" onclick={() => navigate('/indexes')}>
      Manage all
    </button>
  </div>
  <div class="card-body" style="padding: 0">
    {#if recent.length === 0}
      <div class="empty">
        No indexes yet.
        <a
          href="#/indexes"
          onclick={(e) => {
            e.preventDefault();
            navigate('/indexes');
          }}>Create one</a
        >.
      </div>
    {:else}
      <table class="table">
        <thead>
          <tr>
            <th>Name</th>
            <th>Documents</th>
            <th>Root path</th>
            <th>Status</th>
          </tr>
        </thead>
        <tbody>
          {#each recent as ix}
            <tr
              style="cursor: pointer"
              onclick={() => navigate('/indexes')}
            >
              <td><strong>{ix.id}</strong></td>
              <td>{(ix.chunk_count ?? 0).toLocaleString()}</td>
              <td class="text-mono text-xs text-muted truncate" style="max-width: 360px">
                {ix.root_path || '—'}
              </td>
              <td>
                {#if ix.error}
                  <span class="badge badge-danger">error</span>
                {:else}
                  <span class="badge badge-success">ready</span>
                {/if}
              </td>
            </tr>
          {/each}
        </tbody>
      </table>
    {/if}
  </div>
</div>

<style>
  .page-title {
    font-size: var(--trusty-fs-xl);
    margin: 0 0 var(--trusty-space-5) 0;
    font-weight: 600;
  }
</style>