Files

68 lines
3.1 KiB
TypeScript

import { NextResponse } from 'next/server'
import { apps, namespaces, nodes } from '@/lib/clusterMock'
/**
* Mock stand-in for the real atlas `POST /api/exec`. Same closed-command-enum shape the real
* endpoint must have — anything unmatched rejects here too — but reads fixture data, not a live
* snapshot, and has no rate limiting or session state.
*/
const COMMANDS = ['get nodes', 'get pods <ns>', 'get apps', 'top nodes', 'describe pod <ns> <name>', 'help']
const pad = (s: string, n: number) => s.padEnd(n, ' ')
type Line = { kind: 'output' | 'reject'; text: string }
export async function POST(req: Request) {
const body = await req.json().catch(() => ({}))
const raw = typeof body.cmd === 'string' ? body.cmd : ''
const trimmed = raw.trim().toLowerCase()
const reject = (text: string): Line => {
console.warn(`[api/exec] rejected: "${raw}" — ${text}`)
return { kind: 'reject', text }
}
let lines: Line[]
if (trimmed === 'help') {
lines = [{ kind: 'output', text: COMMANDS.map((c) => ` ${c}`).join('\n') }]
} else if (trimmed === 'get nodes') {
const head = `${pad('NAME', 14)}${pad('ROLE', 16)}${pad('CPU', 6)}${pad('MEM', 8)}GPU`
const rows = nodes.map(
(n) => `${pad(n.name, 14)}${pad(n.role, 16)}${pad(String(n.cpu), 6)}${pad(`${n.memoryGi}Gi`, 8)}${n.gpu}`,
)
lines = [{ kind: 'output', text: [head, ...rows].join('\n') }]
} else if (trimmed === 'get apps') {
const head = `${pad('NAME', 24)}${pad('WAVE', 7)}${pad('SYNC', 12)}HEALTH`
const rows = apps.map((a) => `${pad(a.name, 24)}${pad(String(a.wave ?? '-'), 7)}${pad(a.sync, 12)}${a.health}`)
lines = [{ kind: 'output', text: [head, ...rows].join('\n') }]
} else if (trimmed === 'top nodes') {
const head = `${pad('NAME', 14)}${pad('CPU%', 8)}MEM%`
const rows = nodes.map((n) => `${pad(n.name, 14)}${pad(n.gpu ? '61%' : '18%', 8)}${n.gpu ? '74%' : '42%'}`)
lines = [{ kind: 'output', text: [head, ...rows].join('\n') }]
} else if (trimmed.startsWith('get pods')) {
const ns = trimmed.split(/\s+/)[2]
if (!ns) {
lines = [reject('get pods requires a namespace. Try: get pods llm-serving')]
} else {
const known = namespaces.find((n) => n.name === ns)
if (!known) {
lines = [reject(`unknown namespace "${ns}" — rejected on snapshot membership, no lookup performed`)]
} else {
const rows = Array.from({ length: Math.min(known.total, 8) }, (_, i) => {
const running = i < known.running
return `${pad(`${ns}-workload-${i + 1}`, 30)}${pad(running ? '1/1' : '0/1', 6)}${running ? 'Running' : 'Pending'}`
})
lines = [{ kind: 'output', text: [`${pad('NAME', 30)}${pad('READY', 6)}STATUS`, ...rows].join('\n') }]
}
}
} else if (trimmed.startsWith('describe pod')) {
lines = [{ kind: 'output', text: 'phase: Running\nrestarts: 0\nage: 17h\nnode: worker-1\nready: true' }]
} else {
lines = [
reject(`"${raw.trim()}" is not in the command set. Input parses to a closed enum; nothing else reaches the cluster. Type help.`),
]
}
return NextResponse.json({ lines })
}