mirror of
https://github.com/Rainyy21/k3s-health-dashboard.git
synced 2026-10-11 00:00:20 -04:00
finish the main code
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
"""
|
||||||
|
app/__init__.py
|
||||||
|
|
||||||
|
Makes `app/` a Python package and exposes a single shared Config instance.
|
||||||
|
|
||||||
|
Usage anywhere in the project:
|
||||||
|
from app import cfg
|
||||||
|
print(cfg.cache_ttl)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app.config import load
|
||||||
|
|
||||||
|
cfg = load()
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"""
|
||||||
|
app/config.py
|
||||||
|
|
||||||
|
Central place for all runtime configuration.
|
||||||
|
Values are read from environment variables, with sensible defaults.
|
||||||
|
Nothing in this file imports from the rest of the project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Config:
|
||||||
|
# Kubernetes connection
|
||||||
|
in_cluster: bool # True when running as a pod inside k3s
|
||||||
|
kubeconfig: str # path to kubeconfig file (ignored if in_cluster=True)
|
||||||
|
|
||||||
|
# Dashboard behaviour
|
||||||
|
cache_ttl: int # seconds between Kubernetes API refreshes
|
||||||
|
page_title: str
|
||||||
|
page_icon: str
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
metrics_enabled: bool # set False if metrics-server is not installed
|
||||||
|
|
||||||
|
|
||||||
|
def load() -> Config:
|
||||||
|
"""
|
||||||
|
Read config from the environment.
|
||||||
|
Call once at app startup — the result is immutable.
|
||||||
|
"""
|
||||||
|
return Config(
|
||||||
|
in_cluster=os.getenv("IN_CLUSTER", "").lower() in ("1", "true", "yes"),
|
||||||
|
kubeconfig=os.getenv("KUBECONFIG", os.path.expanduser("~/.kube/config")),
|
||||||
|
cache_ttl=int(os.getenv("CACHE_TTL", "15")),
|
||||||
|
page_title=os.getenv("PAGE_TITLE", "k3s Dashboard"),
|
||||||
|
page_icon=os.getenv("PAGE_ICON", "☸️"),
|
||||||
|
metrics_enabled=os.getenv("METRICS_ENABLED", "true").lower() != "false",
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import os
|
||||||
|
import kubernetes import client, config
|
||||||
|
|
||||||
|
def get_core_client() -> client.CoreV1Api:
|
||||||
|
if os.getenv("IN_CLUSTER"):
|
||||||
|
config.load_incluster_config()
|
||||||
|
else:
|
||||||
|
config.load_kube_config( # runs locally
|
||||||
|
config_file=os.getenv("KUBECONFIG", "~/.kube/config")
|
||||||
|
)
|
||||||
|
return client.CoreV1Api()
|
||||||
|
|
||||||
|
def get_custom_client() -> client.CustomObjectsApi:
|
||||||
|
return client.CustomObjectsApi()
|
||||||
|
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from app.routers import nodes, pods, metrics
|
||||||
|
|
||||||
|
app = FastAPI(title="k3s Health Dashboard")
|
||||||
|
|
||||||
|
app.include_router(nodes.router)
|
||||||
|
app.include_router(pods.router)
|
||||||
|
app.include_router(metrics.router)
|
||||||
|
|
||||||
|
app.mount("/static", StaticFiles(directory="dashboard/static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
def serve_ui():
|
||||||
|
return FileResponse("dashboard/index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok"}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from k8s_client import get_custom_client
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/metrics", tags=["metrics"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/nodes")
|
||||||
|
def node_metrics():
|
||||||
|
api = get_custom_client()
|
||||||
|
try:
|
||||||
|
data = api.list_cluster_custom_object(
|
||||||
|
group="metrics.k8s.io", version="v1beta1", plural="nodes"
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": item["metadata"]["name"],
|
||||||
|
"cpu_usage": item["usage"]["cpu"], # e.g. "250m"
|
||||||
|
"memory_usage": item["usage"]["memory"], # e.g. "512Mi"
|
||||||
|
}
|
||||||
|
for item in data["items"]
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e), "hint": "Is metrics-server installed?"}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from app.k8s_client import get_core_client
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/nodes", tags=["nodes"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_node():
|
||||||
|
v1 = get_core_client()
|
||||||
|
nodes = v1.list_node().items
|
||||||
|
result = []
|
||||||
|
for n in nodes:
|
||||||
|
conditions = {c.type: c.status for c in n.status.conditions}
|
||||||
|
result.append(
|
||||||
|
{
|
||||||
|
"name": n.metadata.name,
|
||||||
|
"ready": conditions.get("Ready") == "True",
|
||||||
|
"cpu_capacity": n.status.capacity.get("cpu"),
|
||||||
|
"memory_capacity": n.status.capacity.get("memory"),
|
||||||
|
"labels": n.metadata.labels,
|
||||||
|
"age_days": _age(n.metadata.creation_timestamp),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _age(ts) -> int:
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
return (now - ts).days
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from fastapi import APIRouter, Query
|
||||||
|
from k8s_client import get_core_client
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/pods", tags=["pods"])
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
def list_pods(namespace: str = Query("default")):
|
||||||
|
v1 = get_core_client()
|
||||||
|
pods = v1.list_namespaced_pod(namespace=namespace).items
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": p.metadata.name,
|
||||||
|
"namespace": p.metadata.namespace,
|
||||||
|
"phase": p.status.phase, # Pending/Running/Succeeded/Failed
|
||||||
|
"ready": _is_ready(p),
|
||||||
|
"restarts": _restarts(p),
|
||||||
|
"node": p.spec.node_name,
|
||||||
|
}
|
||||||
|
for p in pods
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_ready(pod) -> bool :
|
||||||
|
if not pod.status.conditions:
|
||||||
|
return False
|
||||||
|
for any(c.type == "Ready" and c.status == "True" for c in pod.status.conditions)
|
||||||
|
|
||||||
|
def _restarts(pod) -> int:
|
||||||
|
if not pod.status.container_statuses:
|
||||||
|
return 0
|
||||||
|
return sum(c.restart_count for c in pod.status.container_statuses)
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: dashboard-sa
|
||||||
|
namespace: default
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: dashboard-reader
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["nodes", "pods", "namespaces", "events"]
|
||||||
|
verbs: ["get", "list", "watch"]
|
||||||
|
- apiGroups: ["metrics.k8s.io"]
|
||||||
|
resources: ["nodes", "pods"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: dashboard-reader-binding
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: dashboard-sa
|
||||||
|
namespace: default
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
name: dashboard-reader
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: k3s-dashboard
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: k3s-dashboard
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: k3s-dashboard
|
||||||
|
spec:
|
||||||
|
serviceAccountName: dashboard-sa
|
||||||
|
containers:
|
||||||
|
- name: dashboard
|
||||||
|
image: your-registry/k3s-dashboard:latest
|
||||||
|
env:
|
||||||
|
- name: IN_CLUSTER
|
||||||
|
value: "true"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
kubernetes
|
||||||
|
python-dotenv
|
||||||
|
httpx
|
||||||
Reference in New Issue
Block a user