mirror of
https://github.com/Rainyy21/k3s-health-dashboard.git
synced 2026-10-10 23:00:19 -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