import type { PresentationData, Agent, Approval, CdcChange, CdcStats, DataflowGraph, FeedEntry, GpuStatus, Movement, PiiDataset, SparkRun, SparkLive, StreamingStatus, StatusData, TerminalLine, WorkloadData, } from '../types' async function fetchJson(url: string, timeoutMs = 10000): Promise { const ctrl = new AbortController() const timer = setTimeout(() => ctrl.abort(), timeoutMs) try { const r = await fetch(url, { signal: ctrl.signal }) if (!r.ok) return null return (await r.json()) as T } catch { return null } finally { clearTimeout(timer) } } export async function fetchAgents() { const j = await fetchJson<{ agents?: Agent[] }>('/api/agents', 8000) return j?.agents || [] } export async function fetchStatus() { return (await fetchJson('/api/status', 8000)) as StatusData } export async function fetchFeed() { const j = await fetchJson<{ entries?: FeedEntry[] }>('/api/feed', 8000) return j?.entries || [] } export async function fetchApprovals() { const j = await fetchJson<{ approvals?: Approval[] }>('/api/approvals', 8000) return j?.approvals || [] } export async function fetchApprovalHistory(status: string = 'pending') { const j = await fetchJson<{ approvals?: Approval[] stats?: { pending: number; approved: number; denied: number; total: number } }>(`/api/approvals?status=${encodeURIComponent(status)}&limit=200`, 8000) return { approvals: j?.approvals || [], stats: j?.stats } } export async function fetchGpu(): Promise { return fetchJson('/api/gpu', 8000) } export async function fetchTerminals(): Promise> { const j = await fetchJson<{ terminals?: Record }>('/api/terminals', 8000) return j?.terminals || {} } export async function fetchWorkload(): Promise { return fetchJson('/api/workload?fast=true', 25000) } export async function fetchNodeDetail(nodeId: string) { const j = await fetchJson>(`/api/nodes/${nodeId}`, 15000) return j || { error: 'timeout' } } export function probeNode(nodeId: string) { return fetch(`/api/nodes/${nodeId}/probe`, { method: 'POST' }) } export function askNode(nodeId: string, message: string) { return fetch(`/api/nodes/${nodeId}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }), }) } export function sendPrompt(message: string, agentId?: string) { return fetch('/api/prompt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message, agent_id: agentId || undefined }), }) } export async function fetchPresentation(): Promise { return fetchJson('/api/presentation', 60000) } export async function fetchChanges(opts: { source?: string; op?: string; limit?: number } = {}) { const p = new URLSearchParams() if (opts.source) p.set('source', opts.source) if (opts.op) p.set('op', opts.op) p.set('limit', String(opts.limit ?? 150)) const j = await fetchJson<{ changes?: CdcChange[]; connected?: boolean; consumed?: number }>( `/api/changes?${p.toString()}`, 8000, ) return { changes: j?.changes || [], connected: !!j?.connected, consumed: j?.consumed || 0 } } export async function fetchChangeStats(minutes = 15): Promise { return fetchJson(`/api/changes/stats?minutes=${minutes}`, 8000) } export type ConnectorState = { name: string; state?: string; failed?: number[] } export type ResyncResult = { ok: boolean; restarted?: string[]; healthy?: number; total?: number; after?: ConnectorState[] } export async function resyncSources(force = true): Promise { const r = await fetch('/api/pipeline/streaming/resync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force }), }) return r.ok ? ((await r.json()) as ResyncResult) : null } export async function fetchAgentOpsStatus() { return fetchJson>('/api/agent-ops/status', 8000) } export function toggleAgentOps(enabled?: boolean) { return fetch('/api/agent-ops/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(enabled === undefined ? {} : { enabled }), }) } export function runAgentOpOnce(source?: string, op?: string) { return fetch('/api/agent-ops/run-once', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source, op }), }) } export async function fetchDataflow(refresh = false): Promise { return fetchJson(`/api/dataflow${refresh ? '?refresh=true' : ''}`, 25000) } export function runDataflowMovement(movementId: string, conf?: Record) { return fetch(`/api/dataflow/${movementId}/run`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(conf ? { conf } : {}), }) } export async function fetchMovements(): Promise { const j = await fetchJson<{ movements?: Movement[] }>('/api/movements', 8000) return j?.movements || [] } export async function fetchPii(refresh = false): Promise<{ datasets: PiiDataset[]; summary: Record } | null> { return fetchJson<{ datasets: PiiDataset[]; summary: Record }>( `/api/pii${refresh ? '?refresh=true' : ''}`, 15000, ) } export function setPiiMask(key: string, column: string, masked: boolean) { return fetch('/api/pii/policy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, column, masked }), }) } export function toggleEtlAgent(enabled?: boolean) { return fetch('/api/agent-ops/etl/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(enabled === undefined ? {} : { enabled }), }) } export async function decideApproval(id: string, approved: boolean, decidedBy: string, note: string) { return fetch(`/api/approvals/${id}/decide`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ approved, decided_by: decidedBy, note }), }) } export async function fetchStreamingStatus(refresh = false): Promise { return fetchJson(`/api/pipeline/streaming/status${refresh ? '?refresh=true' : ''}`, 20000) } export function triggerStreamingJob(jobId: string, conf?: Record) { return fetch(`/api/pipeline/streaming/jobs/${jobId}/trigger`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ conf: conf ?? {} }), }) } export function restartKafkaConnector(name: string) { return fetch(`/api/pipeline/streaming/kafka/connectors/${encodeURIComponent(name)}/restart`, { method: 'POST' }) } export function pauseKafkaConnector(name: string) { return fetch(`/api/pipeline/streaming/kafka/connectors/${encodeURIComponent(name)}/pause`, { method: 'POST' }) } export function resumeKafkaConnector(name: string) { return fetch(`/api/pipeline/streaming/kafka/connectors/${encodeURIComponent(name)}/resume`, { method: 'POST' }) } export function triggerStreamingPipeline(pipelineId: string) { return fetch(`/api/pipeline/streaming/pipeline/${encodeURIComponent(pipelineId)}`, { method: 'POST' }) } export function exportHdfsToKafka(body?: { path?: string; topic?: string; limit?: number }) { return fetch('/api/pipeline/streaming/hdfs/to-kafka', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body ?? {}), }) } export function setStreamingFlow(action: 'pause' | 'resume' | 'stop') { return fetch(`/api/pipeline/streaming/flow/${action}`, { method: 'POST' }) } // ── Spark Workbench ────────────────────────────────────────────── export async function fetchSparkCatalogs(): Promise { const j = await fetchJson<{ catalogs?: string[] }>('/api/spark/catalogs', 15000) return j?.catalogs ?? [] } export async function fetchSparkSchemas(catalog: string): Promise { const j = await fetchJson<{ schemas?: string[] }>(`/api/spark/schemas?catalog=${encodeURIComponent(catalog)}`, 15000) return j?.schemas ?? [] } export async function fetchSparkTables(catalog: string, schema: string): Promise<{ name: string; fqn: string }[]> { const j = await fetchJson<{ tables?: { name: string; fqn: string }[] }>( `/api/spark/tables?catalog=${encodeURIComponent(catalog)}&schema=${encodeURIComponent(schema)}`, 15000) return j?.tables ?? [] } export async function fetchSparkColumns(table: string): Promise<{ name: string; type: string }[]> { const j = await fetchJson<{ columns?: { name: string; type: string }[] }>( `/api/spark/columns?table=${encodeURIComponent(table)}`, 15000) return j?.columns ?? [] } export async function createSparkRun(body: Record): Promise<{ ok: boolean; run_id?: string; sql?: string; error?: string; target?: string | null }> { const r = await fetch('/api/spark/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) return r.json() } export async function fetchSparkRun(runId: string) { return fetchJson<{ ok: boolean; run?: SparkRun }>(`/api/spark/run/${runId}`, 15000) } export function cancelSparkRun(runId: string) { return fetch(`/api/spark/run/${runId}/cancel`, { method: 'POST' }) } export async function fetchSparkLive() { return fetchJson('/api/spark/live', 15000) } export async function fetchSparkRuns(): Promise { const j = await fetchJson<{ runs?: SparkRun[] }>('/api/spark/runs', 15000) return j?.runs ?? [] } export function toggleCustodianOffload(enabled?: boolean) { return fetch('/api/agent-ops/custodian/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(enabled === undefined ? {} : { enabled }), }) }