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)
This commit is contained in:
167
design/server.js
Normal file
167
design/server.js
Normal file
@@ -0,0 +1,167 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user