lazydns 0.3.20

A light and fast DNS server/forwarder implementation in Rust
Documentation
<script lang="ts">
  import { onMount, onDestroy } from "svelte";
  import { api, type ConfigDumpResponse } from "../lib/api";
  import { darkMode } from "../lib/stores";
  import ManagementTab from "../components/ManagementTab.svelte";
  import ConfigTab from "../components/ConfigTab.svelte";

  // Active tab: "admin" (server management) or "config" (configuration view)
  type TabType = "admin" | "config";
  let activeTab: TabType = "admin";

  // Configuration view state (owned here, passed down to ConfigTab).
  let configDump: ConfigDumpResponse | null = null;
  let configError: string | null = null;
  // Error from the last reload attempt (persistent banner on Configuration tab).
  let reloadError: string | null = null;

  async function fetchConfigDump() {
    try {
      configDump = await api.getConfigDump();
      configError = null;
    } catch (e) {
      configError = e instanceof Error ? e.message : "Failed to load configuration";
      console.error("Config fetch error:", e);
    }
  }

  // ManagementTab reports reload outcomes so the Configuration tab can refresh
  // (success) or surface a persistent error banner (failure).
  async function onReloadSuccess() {
    await fetchConfigDump();
    reloadError = null;
  }

  function onReloadError(e: CustomEvent<{ message: string }>) {
    reloadError = e.detail.message;
  }

  let refreshInterval: ReturnType<typeof setInterval>;
  onMount(() => {
    fetchConfigDump();
    // Periodically refresh the config dump in case it is reloaded out of band.
    refreshInterval = setInterval(fetchConfigDump, 30000);
  });

  onDestroy(() => {
    if (refreshInterval) clearInterval(refreshInterval);
  });
</script>

<div class="space-y-6">
  <!-- Page Header -->
  <div class="flex items-center justify-between">
    <div>
      <h1
        class="text-2xl font-bold {$darkMode ? 'text-white' : 'text-gray-900'}"
      >
        Admin
      </h1>
      <p class="{$darkMode ? 'text-gray-400' : 'text-gray-700'} mt-1">
        Server management and configuration
      </p>
    </div>
  </div>

  <!-- Tabs -->
  <div
    class="flex items-center gap-1 border-b {$darkMode
      ? 'border-gray-700'
      : 'border-gray-200'}"
  >
    <button
      on:click={() => (activeTab = "admin")}
      class="px-4 py-3 text-sm font-medium transition-colors border-b-2 -mb-px"
      class:border-primary-500={activeTab === "admin"}
      class:text-primary-500={activeTab === "admin"}
      class:border-transparent={activeTab !== "admin"}
      class:text-gray-400={activeTab !== "admin" && $darkMode}
      class:text-gray-600={activeTab !== "admin" && !$darkMode}
    >
      <span class="flex items-center gap-2">
        <svg
          class="w-4 h-4"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
          />
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
          />
        </svg>
        Management
      </span>
    </button>
    <button
      on:click={() => (activeTab = "config")}
      class="px-4 py-3 text-sm font-medium transition-colors border-b-2 -mb-px"
      class:border-primary-500={activeTab === "config"}
      class:text-primary-500={activeTab === "config"}
      class:border-transparent={activeTab !== "config"}
      class:text-gray-400={activeTab !== "config" && $darkMode}
      class:text-gray-600={activeTab !== "config" && !$darkMode}
    >
      <span class="flex items-center gap-2">
        <svg
          class="w-4 h-4"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            stroke-width="2"
            d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
          />
        </svg>
        Configuration
        {#if configDump}
          <span
            class="px-2 py-0.5 rounded-full text-xs {$darkMode
              ? 'bg-gray-700'
              : 'bg-gray-200 text-gray-700'}"
          >
            {configDump.plugin_count}
          </span>
        {/if}
      </span>
    </button>
  </div>

  {#if activeTab === "admin"}
    <ManagementTab
      on:reloadsuccess={onReloadSuccess}
      on:reloaderror={onReloadError}
    />
  {:else}
    <ConfigTab
      {configDump}
      {configError}
      {reloadError}
      on:dismissreloaderror={() => (reloadError = null)}
    />
  {/if}
</div>