Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,23 @@ class APIClient {
return response.data;
}

/**
* API root info including document ingestion epoch
*/
async getApiInfo(): Promise<{ epoch: number; status: string }> {
const response = await this.client.get<{ epoch: number; status: string }>('/');
return response.data;
}

/**
* Graph change epoch (cache invalidation counter).
* Increments on any graph mutation (concept/edge/source create/delete).
*/
async getDatabaseEpoch(): Promise<number> {
const response = await this.client.get<{ epoch: number }>('/database/epoch');
return response.data.epoch ?? 0;
}

// ============================================================
// ADMIN / OAUTH CLIENT MANAGEMENT
// ============================================================
Expand Down
22 changes: 20 additions & 2 deletions web/src/components/admin/SystemTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const SystemTab: React.FC<SystemTabProps> = ({ onError }) => {
const [dbStats, setDbStats] = useState<any>(null);
const [dbCounters, setDbCounters] = useState<any>(null);
const [schedulerStatus, setSchedulerStatus] = useState<SchedulerStatus | null>(null);
const [docsIngested, setDocsIngested] = useState<number>(0);
const [graphEpoch, setGraphEpoch] = useState<number>(0);
const [embeddingConfigs, setEmbeddingConfigs] = useState<EmbeddingConfig[]>([]);
const [extractionConfig, setExtractionConfig] = useState<ExtractionConfig | null>(null);
const [apiKeys, setApiKeys] = useState<ApiKeyInfo[]>([]);
Expand All @@ -67,19 +69,23 @@ export const SystemTab: React.FC<SystemTabProps> = ({ onError }) => {
const loadData = async () => {
setLoading(true);
try {
const [status, stats, counters, scheduler, embeddings, extraction, keys] = await Promise.all([
const [status, stats, counters, scheduler, embeddings, extraction, keys, apiInfo, dbEpoch] = await Promise.all([
apiClient.getSystemStatus().catch(() => null),
apiClient.getDatabaseStats().catch(() => null),
apiClient.getDatabaseCounters().catch(() => null),
apiClient.getSchedulerStatus().catch(() => null),
apiClient.listEmbeddingConfigs().catch(() => []),
apiClient.getExtractionConfig().catch(() => null),
apiClient.listApiKeys().catch(() => []),
apiClient.getApiInfo().catch(() => ({ epoch: 0, status: 'error' })),
apiClient.getDatabaseEpoch().catch(() => 0),
]);
setSystemStatus(status);
setDbStats(stats);
setDbCounters(counters);
setSchedulerStatus(scheduler);
setDocsIngested(apiInfo.epoch || 0);
setGraphEpoch(dbEpoch);
setEmbeddingConfigs(embeddings);
setExtractionConfig(extraction);
setApiKeys(keys);
Expand Down Expand Up @@ -412,7 +418,7 @@ export const SystemTab: React.FC<SystemTabProps> = ({ onError }) => {
icon={<Activity className="w-5 h-5" />}
>
{schedulerStatus?.jobs_by_status ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="grid grid-cols-2 md:grid-cols-6 gap-4">
<div className="p-4 bg-status-warning/10 rounded-lg">
<div className="text-2xl font-bold text-status-warning">
{(schedulerStatus.jobs_by_status.pending ?? 0) + (schedulerStatus.jobs_by_status.awaiting_approval ?? 0)}
Expand All @@ -437,6 +443,18 @@ export const SystemTab: React.FC<SystemTabProps> = ({ onError }) => {
</div>
<div className="text-sm text-destructive">Failed</div>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-2xl font-bold text-foreground">
{docsIngested.toLocaleString()}
</div>
<div className="text-sm text-muted-foreground">Docs Ingested</div>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-2xl font-bold text-foreground">
{graphEpoch.toLocaleString()}
</div>
<div className="text-sm text-muted-foreground">Graph Epoch</div>
</div>
</div>
) : (
<p className="text-muted-foreground text-center py-8">
Expand Down
20 changes: 17 additions & 3 deletions web/src/components/home/HomeWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface SystemStatus {
ontologies: number;
};
health?: boolean;
epoch?: number;
}

/** Welcome page showing login prompt (unauthenticated) or system status dashboard
Expand All @@ -56,8 +57,8 @@ export const HomeWorkspace: React.FC = () => {
const loadStatus = async () => {
setLoading(true);
try {
// Load health, stats, and ontologies in parallel
const [health, stats, ontologies] = await Promise.all([
// Load health, stats, ontologies, and API info in parallel
const [health, stats, ontologies, apiInfo] = await Promise.all([
apiClient.healthCheck().catch(err => {
console.error('Health check failed:', err);
return { status: 'error' };
Expand All @@ -70,6 +71,10 @@ export const HomeWorkspace: React.FC = () => {
console.error('Ontologies fetch failed:', err);
return { ontologies: [], count: 0 };
}),
apiClient.getApiInfo().catch(err => {
console.error('API info failed:', err);
return { epoch: 0, status: 'error' };
}),
]);

// Parse stats response: { nodes: { concepts: X, sources: Y }, relationships: { total: N } }
Expand All @@ -84,6 +89,7 @@ export const HomeWorkspace: React.FC = () => {

setStatus({
health: isOnline,
epoch: apiInfo.epoch || 0,
database: {
concepts,
relationships,
Expand Down Expand Up @@ -223,7 +229,7 @@ export const HomeWorkspace: React.FC = () => {
Loading status...
</div>
) : status ? (
<div className="grid grid-cols-5 gap-4">
<div className="grid grid-cols-6 gap-4">
<div className="p-4 rounded-lg bg-card border border-border dark:border-gray-800">
<div className="flex items-center gap-2 mb-2">
{status.health ? (
Expand Down Expand Up @@ -269,6 +275,14 @@ export const HomeWorkspace: React.FC = () => {
{status.database?.sources?.toLocaleString() || 0}
</div>
</div>
<div className="p-4 rounded-lg bg-card border border-border dark:border-gray-800">
<div className="text-sm font-medium text-muted-foreground dark:text-gray-400 mb-2">
Docs Ingested
</div>
<div className="text-2xl font-bold text-card-foreground dark:text-gray-200">
{status.epoch?.toLocaleString() || 0}
</div>
</div>
</div>
) : (
<div className="text-muted-foreground dark:text-gray-400">
Expand Down