feat: add configmaps, exec action, and reorder phases

Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
This commit is contained in:
Jiří Stachandaider committed 2026-10-09 17:22:27 +02:00
1 parent cdd3e3818c
commit 508cdff893
4 files changed
+67 -11

No files matched your search

+12 -6
View File
@@ -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
+19 -2
View File
@@ -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}
+28 -3
View File
@@ -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 = `<strong>${item.name}</strong> <span style="color:#666">(${item.namespace})</span>`;
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();
});
+8
View File
@@ -23,6 +23,7 @@
<option value="pods">Pods</option>
<option value="nodes">Nodes</option>
<option value="pvcs">PVCs</option>
<option value="configmaps">ConfigMaps</option>
</select>
<button id="set-default">Set as default</button>
@@ -52,6 +53,13 @@
<button id="btn-logs">Logs</button>
<button id="btn-delete" class="danger">Delete</button>
</div>
<div style="margin-top:0.75rem;">
<label>Exec (non-interactive):</label>
<input type="text" id="exec-pod" placeholder="pod name" style="min-width:120px;" />
<input type="text" id="exec-container" placeholder="container (optional)" style="min-width:120px;" />
<input type="text" id="exec-cmd" placeholder="command (default: ls)" style="min-width:160px;" />
<button id="btn-exec">Exec</button>
</div>
<pre id="action-output"></pre>
</section>
</main>