Files
opencode-krates-connector/design/server.js
Hermes Agent f55f31a6d9 feat: implement Spotlight Krate Creation workflow
- Add type-to-trigger Spotlight with keyboard (any character)
- Add canvas click to open Spotlight
- Implement keyboard navigation (↑↓ Enter Esc)
- Add keyboard shortcut handlers and spotlight store
- Create useSpotlight hook with fuzzy search
- Create mock Kubernetes resources for initial testing
- Implement krate creation with collision detection
- Add Quick Actions (all pods, services, deployments, namespaces)
- Create Spotlight with filter chips and result rendering
- Add Spotlight state management with setQuery, setFilter, setSel
- Include design specs (Krates.dc.html, server.js, support.js)
2026-06-16 12:27:47 -04:00

168 lines
5.3 KiB
JavaScript

const http = require('http');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const PORT = 5000;
const HTML_FILE = path.join(__dirname, 'Krates.dc.html');
const SUPPORT_FILE = path.join(__dirname, 'support.js');
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'application/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.ico': 'image/x-icon',
'.wasm': 'application/wasm'
};
function getMimeType(filePath) {
const ext = path.extname(filePath).toLowerCase();
return MIME_TYPES[ext] || 'application/octet-stream';
}
async function fetchClusterData() {
try {
const namespaces = JSON.parse(execSync('kubectl get namespaces -o json').toString());
const nodes = JSON.parse(execSync('kubectl get nodes -o json').toString());
const pods = JSON.parse(execSync('kubectl get pods --all-namespaces -o json').toString());
return {
namespaces: namespaces.items.map(ns => ({
name: ns.metadata.name,
status: ns.status.phase,
age: ns.metadata.creationTimestamp
})),
nodes: nodes.items.map(node => ({
name: node.metadata.name,
status: node.status.conditions.find(c => c.type === 'Ready').status,
role: Object.keys(node.metadata.labels || {}).filter(k => k.includes('node-role') || k === 'control-plane').join(', ') || 'worker',
version: node.status.nodeInfo.kubeletVersion
})),
podCount: pods.items.length,
podsByNamespace: {}
};
} catch (error) {
console.error('Error fetching cluster data:', error.message);
return null;
}
}
function createServer() {
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const filePath = url.pathname === '/' ? HTML_FILE : path.join(__dirname, url.pathname);
if (url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', cluster: 'covert-gpt-cluster' }));
return;
}
if (url.pathname === '/api/cluster') {
const data = await fetchClusterData();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data || { error: 'Failed to fetch cluster data' }));
return;
}
if (url.pathname === '/api/pods') {
try {
const { execSync } = require('child_process');
const pods = JSON.parse(execSync('kubectl get pods --all-namespaces -o json', { maxBuffer: 1024 * 1024 * 10 }).toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(pods));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/namespaces') {
try {
const namespaces = JSON.parse(execSync('kubectl get namespaces -o json').toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(namespaces));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/nodes') {
try {
const nodes = JSON.parse(execSync('kubectl get nodes -o json').toString());
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(nodes));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
return;
}
if (url.pathname === '/api/pod/logs' && req.method === 'POST') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const { namespace, pod, container } = JSON.parse(body);
let cmd = `kubectl logs ${pod} -n ${namespace}`;
if (container) cmd += ` -c ${container}`;
const logs = execSync(cmd).toString();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ logs }));
} catch (error) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
return;
}
if (!filePath.startsWith(__dirname)) {
res.writeHead(404);
res.end('Not found');
return;
}
if (!fs.existsSync(filePath)) {
res.writeHead(404);
res.end('Not found');
return;
}
const mimeType = getMimeType(filePath);
const content = fs.readFileSync(filePath);
res.writeHead(200, { 'Content-Type': mimeType });
res.end(content);
});
return server;
}
async function start() {
console.log('Starting Krates server...');
const server = createServer();
server.listen(PORT, () => {
console.log(`Krates server running at http://localhost:${PORT}`);
console.log(`Health check: http://localhost:${PORT}/health`);
console.log(`Cluster API: http://localhost:${PORT}/api/cluster`);
});
server.on('error', (error) => {
console.error(`Server error: ${error.message}`);
process.exit(1);
});
}
start();