sova-devtools 0.1.10

In-app DevTools bar for Sova (HTML inject, SSE timeline, request snapshots)
Documentation
<script setup lang="ts">
import { computed } from "vue";
import { useConsoleStore } from "../stores/console";
import { useDevToolsStore } from "../stores/devtools";

const consoleStore = useConsoleStore();
const store = useDevToolsStore();

const hasRedis = computed(() => {
  const cfg = store.config as { features?: string[] } | null;
  return cfg?.features?.includes("console-redis") ?? false;
});

const needsValue = computed(
  () =>
    consoleStore.redisOp === "set" || consoleStore.redisOp === "publish",
);

const resultText = computed(() => {
  if (!consoleStore.last?.result) return "";
  return JSON.stringify(consoleStore.last.result, null, 2);
});
</script>

<template>
  <div v-if="!hasRedis" class="text-[12px] text-[var(--dt-muted)]">
    Redis console requires <code class="dt-mono">devtools-console-redis</code>.
  </div>
  <div
    v-else
    class="flex min-h-0 flex-1 flex-col gap-3 rounded-[var(--dt-radius)] border border-[var(--dt-border)] bg-[var(--dt-surface)] p-3"
  >
    <div class="flex flex-wrap items-center gap-2">
      <label class="text-[10px] uppercase tracking-wide text-[var(--dt-faint)]"
        >DB</label
      >
      <select
        :value="consoleStore.redisDb"
        class="dt-mono rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-2 py-1.5 text-[11px]"
        @change="
          consoleStore.setRedisDb(Number(($event.target as HTMLSelectElement).value))
        "
      >
        <option v-for="n in 16" :key="n - 1" :value="n - 1">{{ n - 1 }}</option>
      </select>
      <select
        v-model="consoleStore.redisOp"
        class="rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-2 py-1.5 text-[11px] font-medium"
      >
        <option value="get">GET</option>
        <option value="set">SET</option>
        <option value="del">DEL</option>
        <option value="ttl">TTL</option>
        <option value="type">TYPE</option>
        <option value="scan">SCAN</option>
        <option value="publish">PUBLISH</option>
      </select>
      <button
        type="button"
        class="ml-auto rounded bg-[var(--dt-accent)] px-4 py-1.5 text-[11px] font-semibold text-white disabled:opacity-50"
        :disabled="consoleStore.loading"
        @click="consoleStore.runRedis(store.api)"
      >
        {{ consoleStore.loading ? "…" : "Run" }}
      </button>
    </div>

    <div v-if="consoleStore.redisOp === 'scan'" class="flex flex-wrap gap-2">
      <input
        v-model="consoleStore.redisPattern"
        type="text"
        placeholder="pattern *"
        class="min-w-0 flex-1 rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-2 py-1.5 font-mono text-[11px]"
      />
      <input
        v-model.number="consoleStore.redisCursor"
        type="number"
        min="0"
        placeholder="cursor"
        class="w-24 rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-2 py-1.5 font-mono text-[11px]"
      />
    </div>

    <template v-else-if="consoleStore.redisOp !== 'publish'">
      <input
        v-model="consoleStore.redisKey"
        type="text"
        placeholder="key"
        class="w-full rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-3 py-2 font-mono text-[12px]"
      />
    </template>

    <template v-else>
      <input
        v-model="consoleStore.redisChannel"
        type="text"
        placeholder="channel"
        class="w-full rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-3 py-2 font-mono text-[12px]"
      />
    </template>

    <div v-if="needsValue" class="flex flex-wrap gap-2">
      <input
        v-model="consoleStore.redisValue"
        type="text"
        placeholder="value / message"
        class="min-w-0 flex-1 rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-3 py-2 font-mono text-[12px]"
      />
      <input
        v-if="consoleStore.redisOp === 'set'"
        v-model.number="consoleStore.redisTtl"
        type="number"
        min="0"
        placeholder="TTL sec"
        class="w-28 rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] px-2 py-2 font-mono text-[11px]"
      />
    </div>

    <div
      v-if="consoleStore.redisOp === 'publish'"
      class="flex items-center gap-2 border-t border-[var(--dt-border)] pt-2"
    >
      <button
        type="button"
        class="rounded border border-[var(--dt-border)] px-3 py-1 text-[11px]"
        @click="consoleStore.toggleRedisListen(store.api)"
      >
        {{ consoleStore.redisListening ? "Stop listen" : "Listen (SSE)" }}
      </button>
    </div>

    <ul
      v-if="consoleStore.redisMessages.length"
      class="max-h-32 overflow-auto rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] p-2 text-[11px]"
    >
      <li
        v-for="(m, i) in consoleStore.redisMessages"
        :key="i"
        class="border-b border-[var(--dt-border)] py-1 last:border-0"
      >
        <span class="font-mono text-[var(--dt-accent)]">{{ m.channel }}</span>:
        {{ m.payload }}
      </li>
    </ul>

    <p v-if="consoleStore.error" class="text-[12px] text-[var(--dt-err)]">
      {{ consoleStore.error }}
    </p>
    <pre
      v-if="resultText"
      class="min-h-[80px] flex-1 overflow-auto rounded border border-[var(--dt-border)] bg-[var(--dt-bg)] p-3 font-mono text-[11px] leading-relaxed"
      >{{ resultText }}</pre
    >
  </div>
</template>