feat: add backend caching and pagination for resources
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
This commit is contained in:
1 parent
f8422404d7
commit
77febb993e
3 files changed
+104
-74
No files matched your search
+75
-24
@@ -1,6 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@@ -12,6 +14,9 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
cache = {}
|
||||
CACHE_TTL = 300 # 5 minutes
|
||||
|
||||
def run_kubectl(args, context=None):
|
||||
cmd = ["kubectl"]
|
||||
if context:
|
||||
@@ -20,6 +25,21 @@ def run_kubectl(args, context=None):
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result
|
||||
|
||||
def get_cache_key(resource, namespace, context):
|
||||
return f"{resource}:{namespace}:{context or ''}"
|
||||
|
||||
def get_cached_items(resource, namespace, context):
|
||||
key = get_cache_key(resource, namespace, context)
|
||||
if key in cache:
|
||||
ts, items = cache[key]
|
||||
if time.time() - ts < CACHE_TTL:
|
||||
return items
|
||||
return None
|
||||
|
||||
def set_cached_items(resource, namespace, context, items):
|
||||
key = get_cache_key(resource, namespace, context)
|
||||
cache[key] = (time.time(), items)
|
||||
|
||||
@app.get("/")
|
||||
def root():
|
||||
return {"message": "Kubectl Web GUI backend"}
|
||||
@@ -45,37 +65,68 @@ def get_namespaces(context: str = None):
|
||||
namespaces = [line for line in result.stdout.splitlines() if line]
|
||||
return {"namespaces": namespaces}
|
||||
|
||||
def paginated_list(resource, namespace, context, search, page, page_size, bypass, kubectl_args, item_mapper):
|
||||
key = get_cache_key(resource, namespace, context)
|
||||
items = None
|
||||
if not bypass:
|
||||
items = get_cached_items(resource, namespace, context)
|
||||
if items is None:
|
||||
result = run_kubectl(kubectl_args, context=context)
|
||||
try:
|
||||
obj = json.loads(result.stdout)
|
||||
raw_items = obj.get("items", [])
|
||||
except Exception:
|
||||
raw_items = []
|
||||
items = [item_mapper(i) for i in raw_items]
|
||||
set_cached_items(resource, namespace, context, items)
|
||||
if search:
|
||||
s = search.lower()
|
||||
items = [i for i in items if s in (i.get("name") or "").lower()]
|
||||
total = len(items)
|
||||
start = (page - 1) * page_size
|
||||
paginated = items[start:start + page_size]
|
||||
return {
|
||||
"items": paginated,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
}
|
||||
|
||||
@app.get("/pods")
|
||||
def get_pods(namespace: str = "default", context: str = None):
|
||||
result = run_kubectl(
|
||||
["get", "pods", "-n", namespace, "-o", "json"],
|
||||
context=context
|
||||
)
|
||||
return {"raw": result.stdout, "namespace": namespace, "context": context}
|
||||
def get_pods(namespace: str = "default", context: str = None, search: str = "", page: int = 1, page_size: int = 10, bypass: bool = False):
|
||||
def mapper(i):
|
||||
return {
|
||||
"name": i.get("metadata", {}).get("name", "unknown"),
|
||||
"namespace": i.get("metadata", {}).get("namespace", namespace)
|
||||
}
|
||||
return paginated_list("pods", namespace, context, search, page, page_size, bypass, ["get", "pods", "-n", namespace, "-o", "json"], mapper)
|
||||
|
||||
@app.get("/nodes")
|
||||
def get_nodes(context: str = None):
|
||||
result = run_kubectl(
|
||||
["get", "nodes", "-o", "json"],
|
||||
context=context
|
||||
)
|
||||
return {"raw": result.stdout, "context": context}
|
||||
def get_nodes(context: str = None, search: str = "", page: int = 1, page_size: int = 10, bypass: bool = False):
|
||||
def mapper(i):
|
||||
return {
|
||||
"name": i.get("metadata", {}).get("name", "unknown"),
|
||||
"namespace": "-"
|
||||
}
|
||||
return paginated_list("nodes", "default", context, search, page, page_size, bypass, ["get", "nodes", "-o", "json"], mapper)
|
||||
|
||||
@app.get("/pvcs")
|
||||
def get_pvcs(namespace: str = "default", context: str = None):
|
||||
result = run_kubectl(
|
||||
["get", "pvc", "-n", namespace, "-o", "json"],
|
||||
context=context
|
||||
)
|
||||
return {"raw": result.stdout, "namespace": namespace, "context": context}
|
||||
def get_pvcs(namespace: str = "default", context: str = None, search: str = "", page: int = 1, page_size: int = 10, bypass: bool = False):
|
||||
def mapper(i):
|
||||
return {
|
||||
"name": i.get("metadata", {}).get("name", "unknown"),
|
||||
"namespace": i.get("metadata", {}).get("namespace", namespace)
|
||||
}
|
||||
return paginated_list("pvcs", namespace, context, search, page, page_size, bypass, ["get", "pvc", "-n", namespace, "-o", "json"], mapper)
|
||||
|
||||
@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}
|
||||
def get_configmaps(namespace: str = "default", context: str = None, search: str = "", page: int = 1, page_size: int = 10, bypass: bool = False):
|
||||
def mapper(i):
|
||||
return {
|
||||
"name": i.get("metadata", {}).get("name", "unknown"),
|
||||
"namespace": i.get("metadata", {}).get("namespace", namespace)
|
||||
}
|
||||
return paginated_list("configmaps", namespace, context, search, page, page_size, bypass, ["get", "configmaps", "-n", namespace, "-o", "json"], mapper)
|
||||
|
||||
@app.get("/describe")
|
||||
def describe(resource: str, name: str, namespace: str = "default", context: str = None):
|
||||
|
||||
Reference in new issue
Block a user