first commit
This commit is contained in:
52
watcher_visio/metrics/templates/dashboard.html
Normal file
52
watcher_visio/metrics/templates/dashboard.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Prometheus dashboard</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Prometheus dashboard</h1>
|
||||
<div>
|
||||
<label>Metric: <input id="metric" value="{{ default_metric }}"/></label>
|
||||
<button id="reload">Reload</button>
|
||||
<a id="pdfLink" href="/report/pdf/?metric={{ default_metric }}" target="_blank">Download PDF</a>
|
||||
</div>
|
||||
|
||||
<canvas id="chart" width="900" height="400"></canvas>
|
||||
|
||||
<script>
|
||||
const ctx = document.getElementById('chart').getContext('2d');
|
||||
let chart = null;
|
||||
async function loadData() {
|
||||
const metric = document.getElementById('metric').value;
|
||||
const res = await fetch(`/api/metrics/?metric=${encodeURIComponent(metric)}`);
|
||||
const payload = await res.json();
|
||||
const labels = (payload.labels || []).map(ts => new Date(ts));
|
||||
const datasets = (payload.datasets || []).map((d, i) => ({
|
||||
label: d.label,
|
||||
data: d.data.map((v, idx) => ({x: labels[idx], y: v})),
|
||||
fill: false,
|
||||
// Chart.js will auto pick colors
|
||||
}));
|
||||
if (chart) chart.destroy();
|
||||
chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: { datasets },
|
||||
options: {
|
||||
parsing: false,
|
||||
scales: {
|
||||
x: { type: 'time', time: { unit: 'minute' } },
|
||||
y: { beginAtZero: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
// update PDF link
|
||||
document.getElementById('pdfLink').href = `/report/pdf/?metric=${encodeURIComponent(metric)}`;
|
||||
}
|
||||
|
||||
document.getElementById('reload').addEventListener('click', loadData);
|
||||
loadData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
watcher_visio/metrics/urls.py
Normal file
8
watcher_visio/metrics/urls.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.dashboard, name="dashboard"),
|
||||
path("api/metrics/", views.metrics_api, name="metrics_api"),
|
||||
path("report/pdf/", views.report_pdf, name="report_pdf"),
|
||||
]
|
||||
90
watcher_visio/metrics/views.py
Normal file
90
watcher_visio/metrics/views.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
# Helper: query Prometheus HTTP API (query_range)
|
||||
def query_prometheus_range(query, start, end, step="60s"):
|
||||
url = settings.PROMETHEUS_URL.rstrip("/") + "/api/v1/query_range"
|
||||
params = {"query": query, "start": start, "end": end, "step": step}
|
||||
r = requests.get(url, params=params, timeout=10)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
# API endpoint used by Chart.js frontend
|
||||
def metrics_api(request):
|
||||
# get parameters or default (last 1 hour)
|
||||
metric = request.GET.get("metric", settings.PROMETHEUS_DEFAULT_METRIC)
|
||||
now = int(time.time())
|
||||
start = request.GET.get("start", str(now - 3600)) # unix epoch seconds
|
||||
end = request.GET.get("end", str(now))
|
||||
step = request.GET.get("step", "60s")
|
||||
|
||||
# Example: if the metric is a gauge giving bytes, we may want to convert ... keep raw for now
|
||||
q = metric
|
||||
data = query_prometheus_range(q, start, end, step)
|
||||
# Prometheus returns JSON; keep it minimal for Chart.js: {labels: [...], datasets: [{label,...,data:[...]}]}
|
||||
series = []
|
||||
labels = []
|
||||
datasets = []
|
||||
|
||||
if data.get("status") != "success":
|
||||
return JsonResponse({"error": "prometheus error", "detail": data})
|
||||
|
||||
result = data["data"]["result"] # list of time series
|
||||
# if no series, return empty
|
||||
if not result:
|
||||
return JsonResponse({"labels": [], "datasets": []})
|
||||
|
||||
# Build labels from first series timestamps
|
||||
# Prometheus returns values as [[ts, value], ...]
|
||||
first_values = result[0]["values"]
|
||||
labels = [int(float(t[0])) * 1000 for t in first_values] # JS prefers ms
|
||||
for s in result:
|
||||
# create dataset for each timeseries (label from metric labels)
|
||||
metric_labels = s.get("metric", {})
|
||||
label = metric_labels.get("instance") or metric_labels.get("domain") or json.dumps(metric_labels)
|
||||
values = [float(v[1]) if v[1] != "NaN" else None for v in s["values"]]
|
||||
datasets.append({
|
||||
"label": label,
|
||||
"data": values,
|
||||
})
|
||||
|
||||
return JsonResponse({"labels": labels, "datasets": datasets})
|
||||
|
||||
# Dashboard page (Jinja template)
|
||||
def dashboard(request):
|
||||
# let template ask API for data with JS.
|
||||
return render(request, "dashboard.html", {
|
||||
"default_metric": settings.PROMETHEUS_DEFAULT_METRIC,
|
||||
})
|
||||
|
||||
# Render page to PDF using WeasyPrint
|
||||
def report_pdf(request):
|
||||
# optionally accept ?metric=...&start=...&end=...
|
||||
metric = request.GET.get("metric", settings.PROMETHEUS_DEFAULT_METRIC)
|
||||
now = int(time.time())
|
||||
start = int(request.GET.get("start", now - 3600))
|
||||
end = int(request.GET.get("end", now))
|
||||
|
||||
# fetch data server-side to include in report
|
||||
try:
|
||||
resp = query_prometheus_range(metric, start, end, step="60s")
|
||||
except Exception as e:
|
||||
return HttpResponse(f"Error fetching metrics: {e}", status=500)
|
||||
|
||||
context = {
|
||||
"metric": metric,
|
||||
"prom_data": resp.get("data", {}),
|
||||
"generated_at": time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()),
|
||||
}
|
||||
html = render_to_string("report.html", context)
|
||||
|
||||
# Generate PDF via WeasyPrint
|
||||
from weasyprint import HTML
|
||||
pdf = HTML(string=html, base_url=request.build_absolute_uri("/")).write_pdf()
|
||||
return HttpResponse(pdf, content_type="application/pdf")
|
||||
Reference in New Issue
Block a user