diff --git a/README.md b/README.md index 3a43589..b61c3e8 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ This app provides a lightweight local interface to manage Kubernetes clusters vi ## Features - Choose Kubernetes context and namespace -- Search resources: pods, nodes, PVCs, and others -- Perform actions: describe, delete, view logs, and more +- Search resources: pods, nodes, PVCs, configmaps, and others +- Perform actions: describe, delete, view logs, exec (non-interactive), and more ## Tech Stack - Backend: Python @@ -29,12 +29,17 @@ This app provides a lightweight local interface to manage Kubernetes clusters vi - Improve error handling and feedback - Session persistence for context/namespace defaults -### Phase 3: UI Polish & Documentation +### Phase 3: More Resources & Actions +- Add configmaps and additional resource types +- Non-interactive exec into pods (with container selection) +- Additional kubectl actions and endpoints + +### Phase 4: UI Polish & Documentation - Better UI/UX and responsive layout - Documentation and setup instructions - Confirmation dialogs for destructive actions -### Phase 4: Error Handling & Advanced Features +### Phase 5: Error Handling & Advanced Features - Improved error handling and feedback - Possible features: - Authentication / access controls @@ -54,8 +59,9 @@ This app provides a lightweight local interface to manage Kubernetes clusters vi - [x] Phase 2: Search pods, nodes, PVCs, and others - [x] Phase 2: Describe, delete, and logs actions - [x] Phase 2: Session persistence for context/namespace -- [ ] Phase 3: UI polish and documentation -- [ ] Phase 4: Error handling and advanced features +- [ ] Phase 3: More resources (configmaps) and non-interactive exec +- [ ] Phase 4: UI polish and documentation +- [ ] Phase 5: Error handling and advanced features ## Suggestions - Use a lightweight Python framework (e.g., Flask or FastAPI) for the backend diff --git a/backend/main.py b/backend/main.py index 5424f8b..78a0a9a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -69,9 +69,17 @@ def get_pvcs(namespace: str = "default", context: str = None): ) return {"raw": result.stdout, "namespace": namespace, "context": context} +@app.get("/configmaps") +def get_configmaps(namespace: str = "default", context: str = None): + result = run_kubectl( + ["get", "configmaps", "-n", namespace, "-o", "json"], + context=context + ) + return {"raw": result.stdout, "namespace": namespace, "context": context} + @app.get("/describe") def describe(resource: str, name: str, namespace: str = "default", context: str = None): - allowed = {"pod", "node", "pvc", "service", "deployment"} + allowed = {"pod", "node", "pvc", "service", "deployment", "configmap"} if resource not in allowed: return {"error": "Invalid resource type"} result = run_kubectl( @@ -82,7 +90,7 @@ def describe(resource: str, name: str, namespace: str = "default", context: str @app.post("/delete") def delete(resource: str, name: str, namespace: str = "default", context: str = None): - allowed = {"pod", "node", "pvc", "service", "deployment"} + allowed = {"pod", "node", "pvc", "service", "deployment", "configmap"} if resource not in allowed: return {"error": "Invalid resource type"} result = run_kubectl( @@ -98,3 +106,12 @@ def logs(pod: str, namespace: str = "default", context: str = None, tail: int = context=context ) return {"output": result.stdout, "stderr": result.stderr, "returncode": result.returncode} + +@app.get("/exec") +def exec_pod(pod: str, namespace: str = "default", context: str = None, container: str = None, command: str = "ls"): + cmd = ["exec", pod, "-n", namespace] + if container: + cmd += ["-c", container] + cmd += ["--", "sh", "-c", command] + result = run_kubectl(cmd, context=context) + return {"output": result.stdout, "stderr": result.stderr, "returncode": result.returncode} diff --git a/frontend/app.js b/frontend/app.js index 14725db..d5d0979 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -84,6 +84,8 @@ document.addEventListener('DOMContentLoaded', () => { url = `http://localhost:8000/nodes${ctx ? '?context=' + encodeURIComponent(ctx) : ''}`; } else if (resType === 'pvcs') { url = `http://localhost:8000/pvcs?namespace=${encodeURIComponent(ns)}${ctx ? '&context=' + encodeURIComponent(ctx) : ''}`; + } else if (resType === 'configmaps') { + url = `http://localhost:8000/configmaps?namespace=${encodeURIComponent(ns)}${ctx ? '&context=' + encodeURIComponent(ctx) : ''}`; } else { url = `http://localhost:8000/pods?namespace=${encodeURIComponent(ns)}`; } @@ -93,7 +95,7 @@ document.addEventListener('DOMContentLoaded', () => { currentData = []; try { const obj = JSON.parse(data.raw || '{}'); - if (resType === 'pods' || resType === 'pvcs') { + if (resType === 'pods' || resType === 'pvcs' || resType === 'configmaps') { const items = obj.items || []; currentData = items.map(i => ({ name: i.metadata?.name || 'unknown', @@ -136,6 +138,7 @@ document.addEventListener('DOMContentLoaded', () => { li.innerHTML = `${item.name} (${item.namespace})`; li.addEventListener('click', () => { actionName.value = item.name; + document.getElementById('exec-pod').value = item.name; Array.from(resourceList.children).forEach(child => { child.classList.remove('selected'); child.style.background = ''; @@ -176,7 +179,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('btn-describe').addEventListener('click', async () => { const name = actionName.value.trim(); if (!name) return; - const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : 'pod'); + const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : (resourceSelect.value === 'configmaps' ? 'configmap' : 'pod')); const ns = namespaceSelect.value || 'default'; const ctx = contextSelect.value || null; try { @@ -214,7 +217,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('btn-delete').addEventListener('click', async () => { const name = actionName.value.trim(); if (!name || !confirm('Delete ' + name + '?')) return; - const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : 'pod'); + const resType = resourceSelect.value === 'pvcs' ? 'pvc' : (resourceSelect.value === 'nodes' ? 'node' : (resourceSelect.value === 'configmaps' ? 'configmap' : 'pod')); const ns = namespaceSelect.value || 'default'; const ctx = contextSelect.value || null; try { @@ -228,5 +231,27 @@ document.addEventListener('DOMContentLoaded', () => { } }); + document.getElementById('btn-exec').addEventListener('click', async () => { + const pod = document.getElementById('exec-pod').value.trim(); + if (!pod) return; + const ns = namespaceSelect.value || 'default'; + const ctx = contextSelect.value || null; + const container = document.getElementById('exec-container').value.trim() || null; + const cmd = document.getElementById('exec-cmd').value.trim() || 'ls'; + try { + const url = new URL('http://localhost:8000/exec'); + url.searchParams.set('pod', pod); + url.searchParams.set('namespace', ns); + url.searchParams.set('command', cmd); + if (container) url.searchParams.set('container', container); + if (ctx) url.searchParams.set('context', ctx); + const res = await fetch(url); + const data = await res.json(); + actionOutput.textContent = data.output || data.error || JSON.stringify(data); + } catch (e) { + actionOutput.textContent = 'Error: ' + e; + } + }); + loadContexts(); }); diff --git a/frontend/index.html b/frontend/index.html index 855f7bd..109ef2e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -23,6 +23,7 @@ + @@ -52,6 +53,13 @@ +