miradb 0.1.0

Mira: an OTLP-native telemetry storage engine in a single binary
<script>
  // The frame algebra on screen (section 7.3): the region of telemetry around whatever
  // the query above matched.
  //
  // It runs the *same* filter the table below is showing, so the two answers
  // are about one question. Everything it returns is a link back into an
  // ordinary query, which is what the algebra's closure buys: there is nothing
  // here a reader can click that lands them somewhere they cannot then filter.

  import { addTerm, api, bounds, parseFilter, fmtTime, fmtDur, num } from './api.js'
  import { go, route } from './route.svelte.js'

  let { signal, params, nonce } = $props()

  let frame = $state(null)
  let error = $state('')
  let loading = $state(false)

  $effect(() => {
    const q = params.q || ''
    const range = params.range || '-1h'
    void nonce
    let live = true
    loading = true
    error = ''
    ;(async () => {
      try {
        const r = await api('/api/v1/correlate', {
          signal,
          ...bounds(range),
          where: parseFilter(q, signal),
          // Ordered: `traces` measures the real extent of what was found, and
          // `peers` then reads the services inside it. Reversed, `peers` would
          // run against the narrow window and find only what is already here.
          expand: ['traces', 'peers'],
        })
        if (!live) return
        frame = r.frame
      } catch (e) {
        if (live) { error = String(e.message || e); frame = null }
      } finally {
        if (live) loading = false
      }
    })()
    return () => { live = false }
  })

  // Add a service to the filter rather than replacing it: the frame is a
  // narrowing step in an investigation, and the term already typed is the
  // reason this frame exists.
  const narrow = (name) =>
    go(route.path, { ...params, q: addTerm(params.q, `service.name="${name}"`) })

  const span = $derived(frame ? num(frame.to) - num(frame.from) : 0)

  // Grouped by name, because an entity is an *instance* (section 7.2) and three
  // replicas of one service are three entities. A strip reading
  // "frontend frontend frontend" says less than "frontend x3", and the filter
  // the chip writes is on the name anyway.
  let services = $derived.by(() => {
    const by = new Map()
    for (const e of frame?.entities || []) by.set(e.name, (by.get(e.name) || 0) + 1)
    return [...by].sort((a, b) => a[0].localeCompare(b[0]))
  })
</script>

{#if error}
  <div class="err">{error}</div>
{:else if frame}
  <div class="frame">
    <div class="hd">
      <b>Frame</b>
      <span class="win">
        {fmtTime(frame.from)} → {fmtTime(frame.to)} · {fmtDur(span)}
      </span>
      {#if loading}<span class="win">refreshing…</span>{/if}
      {#if frame.truncated}
        <!-- A capped frame is a sample, and a sample presented as a census is
             the one way this panel can be confidently wrong. -->
        <span class="trunc">sample — narrow the filter before concluding</span>
      {/if}
    </div>
    {#if !services.length && !frame.traces.length}
      <div class="none">Nothing matched, so there is no frame to widen.</div>
    {:else}
      {#if services.length}
        <div class="row">
          <span class="lbl">services</span>
          {#each services as [name, n] (name)}
            <button class="chip" onclick={() => narrow(name)} title="add service.name={name} to the filter">
              {name}{n > 1 ? ` \u00d7${n}` : ''}
            </button>
          {/each}
        </div>
      {/if}
      {#if frame.traces.length}
        <div class="row">
          <span class="lbl">traces</span>
          {#each frame.traces.slice(0, 24) as t (t)}
            <a class="chip" href="#/trace/{t}">{t.slice(0, 12)}…</a>
          {/each}
          {#if frame.traces.length > 24}
            <span class="lbl">+{frame.traces.length - 24} more</span>
          {/if}
        </div>
      {/if}
    {/if}
  </div>
{/if}

<style>
  .frame {
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--panel);
    padding: 8px 12px;
    margin: 12px 0;
  }
  .hd { display: flex; gap: 12px; align-items: baseline; }
  .win { color: var(--dim); }
  .trunc { color: var(--warn); }
  .none { color: var(--dim); padding-top: 4px; }
  .row { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; padding-top: 6px; }
  .lbl { color: var(--dim); width: 62px; flex: none; }
  .chip {
    font: inherit;
    color: var(--fg);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 1px 9px;
    cursor: pointer;
    text-decoration: none;
  }
  .chip:hover { border-color: var(--accent); text-decoration: none; }
</style>