- 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)
168 lines
5.3 KiB
JavaScript
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();
|