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");