Newer
Older
gnexus-book / ui / src / main.js
import { createApp, computed, onMounted, ref } from "vue";
import "gnexus-ui-kit/dist/css/kit.css";
import "gnexus-ui-kit/dist/assets/fonts/phosphor-icons/src/css/icons.css";
import {
  GnBadge,
  GnButton,
  GnDescriptionList,
  GnEmptyState,
  GnMetricCard,
  GnPageHeader,
  GnTabs,
  GnToolbar,
  GnToastProvider
} from "gnexus-ui-kit/vue";
import "./styles.css";

const apiGet = async (path) => {
  const response = await fetch(`/api${path}`);
  if (!response.ok) {
    throw new Error(`${path} returned ${response.status}`);
  }
  return response.json();
};

const AppScreen = {
  components: {
    GnBadge,
    GnButton,
    GnDescriptionList,
    GnEmptyState,
    GnMetricCard,
    GnPageHeader,
    GnTabs,
    GnToolbar
  },
  setup() {
    const activeTab = ref("docs");
    const loading = ref(true);
    const error = ref("");
    const health = ref(null);
    const validation = ref(null);
    const freshness = ref(null);
    const docs = ref([]);
    const inventoryTypes = ref([]);
    const changes = ref([]);
    const gitStatus = ref(null);

    const tabs = [
      { id: "docs", label: "Docs", icon: "ph-files" },
      { id: "inventory", label: "Inventory", icon: "ph-database" },
      { id: "changes", label: "Changes", icon: "ph-git-pull-request" },
      { id: "git", label: "Git", icon: "ph-git-branch" }
    ];

    const validationVariant = computed(() =>
      validation.value?.status === "ok" ? "success" : "danger"
    );

    const freshnessVariant = computed(() =>
      freshness.value?.status === "ok" ? "success" : "danger"
    );

    const overview = computed(() => [
      { key: "repo", term: "Repository", value: health.value?.repo_root || "unknown" },
      { key: "validation", term: "Validation", value: validation.value?.status || "unknown" },
      { key: "freshness", term: "Freshness", value: freshness.value?.status || "unknown" }
    ]);

    const load = async () => {
      loading.value = true;
      error.value = "";
      try {
        const [
          healthData,
          validationData,
          freshnessData,
          docsData,
          inventoryData,
          changesData,
          gitData
        ] = await Promise.all([
          apiGet("/health"),
          apiGet("/validate"),
          apiGet("/health/freshness"),
          apiGet("/docs"),
          apiGet("/inventory"),
          apiGet("/changes"),
          apiGet("/git/status")
        ]);
        health.value = healthData;
        validation.value = validationData;
        freshness.value = freshnessData;
        docs.value = docsData;
        inventoryTypes.value = inventoryData;
        changes.value = changesData;
        gitStatus.value = gitData;
      } catch (caught) {
        error.value = caught instanceof Error ? caught.message : "Failed to load API data";
      } finally {
        loading.value = false;
      }
    };

    onMounted(load);

    return {
      activeTab,
      changes,
      docs,
      error,
      freshness,
      freshnessVariant,
      gitStatus,
      inventoryTypes,
      load,
      loading,
      overview,
      tabs,
      validation,
      validationVariant
    };
  },
  template: `
    <main class="gnb-shell">
      <GnPageHeader
        title="Gnexus Book"
        subtitle="Infrastructure documentation maintenance console"
        kicker="Knowledge Base"
        accent
      >
        <template #actions>
          <GnButton variant="secondary" icon="ph-arrow-clockwise" @click="load">
            Refresh
          </GnButton>
        </template>
      </GnPageHeader>

      <section class="gnb-status">
        <GnMetricCard label="Documents" :value="String(docs.length)" icon="ph-files" meta="Markdown pages" />
        <GnMetricCard label="Inventory" :value="String(inventoryTypes.length)" icon="ph-database" meta="YAML types" />
        <GnMetricCard label="Changes" :value="String(changes.length)" icon="ph-git-pull-request" meta="Pending records" />
      </section>

      <section class="section">
        <GnToolbar title="System Status" :meta="loading ? 'Loading' : 'Live API'">
          <template #actions>
            <GnBadge :variant="validationVariant">validation {{ validation?.status || 'unknown' }}</GnBadge>
            <GnBadge :variant="freshnessVariant">freshness {{ freshness?.status || 'unknown' }}</GnBadge>
          </template>
        </GnToolbar>
        <GnDescriptionList :items="overview" />
        <div v-if="error" class="gnb-error">{{ error }}</div>
      </section>

      <section class="section">
        <GnTabs v-model="activeTab" :items="tabs">
          <template #docs>
            <div v-if="docs.length" class="gnb-list">
              <article v-for="doc in docs" :key="doc.path" class="gnb-row">
                <strong>{{ doc.title }}</strong>
                <span>{{ doc.path }}</span>
              </article>
            </div>
            <GnEmptyState v-else title="No documents loaded" text="The backend did not return document records." />
          </template>

          <template #inventory>
            <div v-if="inventoryTypes.length" class="gnb-list">
              <article v-for="type in inventoryTypes" :key="type" class="gnb-row">
                <strong>{{ type }}</strong>
                <span>/inventory/{{ type }}</span>
              </article>
            </div>
            <GnEmptyState v-else title="No inventory loaded" text="The backend did not return inventory types." />
          </template>

          <template #changes>
            <div v-if="changes.length" class="gnb-list">
              <article v-for="change in changes" :key="change.id" class="gnb-row">
                <strong>{{ change.summary }}</strong>
                <span>{{ change.status }} · {{ change.kind }} · {{ change.target }}</span>
              </article>
            </div>
            <GnEmptyState v-else title="No pending changes" text="Pending changes will appear here after agents propose updates." />
          </template>

          <template #git>
            <div v-if="gitStatus?.entries?.length" class="gnb-list">
              <article v-for="entry in gitStatus.entries" :key="entry.path" class="gnb-row">
                <strong>{{ entry.status }}</strong>
                <span>{{ entry.path }}</span>
              </article>
            </div>
            <GnEmptyState v-else title="Working tree clean" text="No tracked or untracked changes are currently reported." />
          </template>
        </GnTabs>
      </section>
    </main>
  `
};

const App = {
  components: {
    AppScreen,
    GnToastProvider
  },
  template: `
    <GnToastProvider>
      <AppScreen />
    </GnToastProvider>
  `
};

createApp(App).mount("#app");