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:
1 parent
cdd3e3818c
commit
508cdff893
4 files changed
+67
-11
No files matched your search
@@ -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
@@ -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
@@ -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();
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user