feat: add phase 1 fastapi backend and frontend
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
This commit is contained in:
1 parent
e67d1f3316
commit
82a0839004
6 files changed
+189
-1
No files matched your search
+9
-1
@@ -1 +1,9 @@
|
|||||||
.aider*
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def root():
|
||||||
|
return {"message": "Kubectl Web GUI backend"}
|
||||||
|
|
||||||
|
@app.get("/contexts")
|
||||||
|
def get_contexts():
|
||||||
|
result = subprocess.run(
|
||||||
|
["kubectl", "config", "get-contexts", "-o", "name"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
contexts = [line for line in result.stdout.splitlines() if line]
|
||||||
|
current = ""
|
||||||
|
try:
|
||||||
|
cur = subprocess.run(
|
||||||
|
["kubectl", "config", "current-context"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
current = cur.stdout.strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"current": current, "contexts": contexts}
|
||||||
|
|
||||||
|
@app.get("/namespaces")
|
||||||
|
def get_namespaces():
|
||||||
|
result = subprocess.run(
|
||||||
|
["kubectl", "get", "namespaces", "-o", "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
namespaces = [line for line in result.stdout.splitlines() if line]
|
||||||
|
return {"namespaces": namespaces}
|
||||||
|
|
||||||
|
@app.get("/pods")
|
||||||
|
def get_pods(namespace: str = "default"):
|
||||||
|
result = subprocess.run(
|
||||||
|
["kubectl", "get", "pods", "-n", namespace, "-o", "json"],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
return {"raw": result.stdout, "namespace": namespace}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
|
python-multipart
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const contextSelect = document.getElementById('context');
|
||||||
|
const namespaceSelect = document.getElementById('namespace');
|
||||||
|
const podList = document.getElementById('pod-list');
|
||||||
|
|
||||||
|
async function loadContexts() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:8000/contexts');
|
||||||
|
const data = await res.json();
|
||||||
|
contextSelect.innerHTML = '';
|
||||||
|
data.contexts.forEach(ctx => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ctx;
|
||||||
|
opt.textContent = ctx;
|
||||||
|
if (ctx === data.current) opt.selected = true;
|
||||||
|
contextSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load contexts', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadNamespaces() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:8000/namespaces');
|
||||||
|
const data = await res.json();
|
||||||
|
namespaceSelect.innerHTML = '';
|
||||||
|
data.namespaces.forEach(ns => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = ns;
|
||||||
|
opt.textContent = ns;
|
||||||
|
namespaceSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load namespaces', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPods() {
|
||||||
|
const ns = namespaceSelect.value || 'default';
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://localhost:8000/pods?namespace=${encodeURIComponent(ns)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
podList.innerHTML = '<li>Pods loaded (raw output available)</li>';
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load pods', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadContexts();
|
||||||
|
loadNamespaces();
|
||||||
|
namespaceSelect.addEventListener('change', loadPods);
|
||||||
|
loadPods();
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Kubectl Web GUI</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Kubectl Web GUI</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section id="selectors">
|
||||||
|
<label for="context">Context:</label>
|
||||||
|
<select id="context"><option>Loading...</option></select>
|
||||||
|
|
||||||
|
<label for="namespace">Namespace:</label>
|
||||||
|
<select id="namespace"><option>default</option></select>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="resources">
|
||||||
|
<h2>Pods</h2>
|
||||||
|
<ul id="pod-list"><li>Loading...</li></ul>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 2rem;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectors {
|
||||||
|
background: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selectors label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources {
|
||||||
|
background: #fff;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pod-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user