Refactor Dockerfile for improved build process, add CMD for running server, and update requirements.txt for consistency. Adjust URL patterns in dashboard and enhance base template with theme toggle functionality and print support.
This commit is contained in:
72
Dockerfile
72
Dockerfile
@@ -1,37 +1,37 @@
|
|||||||
FROM alpine:3 AS build
|
FROM alpine:3 AS build
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
apk add --no-cache --virtual .build-deps \
|
apk add --no-cache --virtual .build-deps \
|
||||||
ca-certificates gcc postgresql-dev linux-headers musl-dev \
|
ca-certificates gcc postgresql-dev linux-headers musl-dev \
|
||||||
libffi-dev jpeg-dev zlib-dev \
|
libffi-dev jpeg-dev zlib-dev \
|
||||||
git bash build-base python3-dev \
|
git bash build-base python3-dev \
|
||||||
dos2unix
|
dos2unix
|
||||||
|
|
||||||
RUN python3 -m venv /venv
|
RUN python3 -m venv /venv
|
||||||
ENV PATH "/venv/bin:$PATH"
|
ENV PATH "/venv/bin:$PATH"
|
||||||
COPY ./requirements.txt /
|
COPY ./requirements.txt /
|
||||||
RUN pip install -r /requirements.txt
|
RUN pip install -r /requirements.txt
|
||||||
|
|
||||||
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
|
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
RUN dos2unix /docker-entrypoint.sh && \
|
RUN dos2unix /docker-entrypoint.sh && \
|
||||||
chmod +x /docker-entrypoint.sh
|
chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:3
|
FROM alpine:3
|
||||||
|
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
ENV LC_ALL C.UTF-8
|
ENV LC_ALL C.UTF-8
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
ENV PATH "/venv/bin:$PATH"
|
ENV PATH "/venv/bin:$PATH"
|
||||||
|
|
||||||
RUN apk add --no-cache --update python3 curl
|
RUN apk add --no-cache --update python3 curl
|
||||||
|
|
||||||
COPY --from=build /venv /venv
|
COPY --from=build /venv /venv
|
||||||
COPY --from=build /docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --from=build /docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"]
|
CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
path('api/stats/', views.api_stats),
|
path('api/stats/', views.api_stats),
|
||||||
path('api/audits/', views.api_audits),
|
path('api/audits/', views.api_audits),
|
||||||
]
|
]
|
||||||
@@ -1,252 +1,252 @@
|
|||||||
import json
|
import json
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from dashboard.openstack_utils.connect import get_connection
|
from dashboard.openstack_utils.connect import get_connection
|
||||||
from dashboard.openstack_utils.flavor import get_flavor_list
|
from dashboard.openstack_utils.flavor import get_flavor_list
|
||||||
from dashboard.prometheus_utils.query import query_prometheus
|
from dashboard.prometheus_utils.query import query_prometheus
|
||||||
from dashboard.openstack_utils.audits import get_audits
|
from dashboard.openstack_utils.audits import get_audits
|
||||||
from dashboard.mock_data import get_mock_context
|
from dashboard.mock_data import get_mock_context
|
||||||
|
|
||||||
# Prometheus queries run in parallel (query_key -> query string)
|
# Prometheus queries run in parallel (query_key -> query string)
|
||||||
_PROMETHEUS_QUERIES = {
|
_PROMETHEUS_QUERIES = {
|
||||||
"hosts_total": "count(node_exporter_build_info{job='node_exporter_compute'})",
|
"hosts_total": "count(node_exporter_build_info{job='node_exporter_compute'})",
|
||||||
"pcpu_total": "sum(count(node_cpu_seconds_total{job='node_exporter_compute', mode='idle'}) without (cpu,mode))",
|
"pcpu_total": "sum(count(node_cpu_seconds_total{job='node_exporter_compute', mode='idle'}) without (cpu,mode))",
|
||||||
"pcpu_usage": "sum(node_load5{job='node_exporter_compute'})",
|
"pcpu_usage": "sum(node_load5{job='node_exporter_compute'})",
|
||||||
"vcpu_allocated": "sum(libvirt_domain_info_virtual_cpus)",
|
"vcpu_allocated": "sum(libvirt_domain_info_virtual_cpus)",
|
||||||
"vcpu_overcommit_max": "avg(openstack_placement_resource_allocation_ratio{resourcetype='VCPU'})",
|
"vcpu_overcommit_max": "avg(openstack_placement_resource_allocation_ratio{resourcetype='VCPU'})",
|
||||||
"pram_total": "sum(node_memory_MemTotal_bytes{job='node_exporter_compute'})",
|
"pram_total": "sum(node_memory_MemTotal_bytes{job='node_exporter_compute'})",
|
||||||
"pram_usage": "sum(node_memory_Active_bytes{job='node_exporter_compute'})",
|
"pram_usage": "sum(node_memory_Active_bytes{job='node_exporter_compute'})",
|
||||||
"vram_allocated": "sum(libvirt_domain_info_maximum_memory_bytes)",
|
"vram_allocated": "sum(libvirt_domain_info_maximum_memory_bytes)",
|
||||||
"vram_overcommit_max": "avg(avg_over_time(openstack_placement_resource_allocation_ratio{resourcetype='MEMORY_MB'}[5m]))",
|
"vram_overcommit_max": "avg(avg_over_time(openstack_placement_resource_allocation_ratio{resourcetype='MEMORY_MB'}[5m]))",
|
||||||
"vm_count": "sum(libvirt_domain_state_code)",
|
"vm_count": "sum(libvirt_domain_state_code)",
|
||||||
"vm_active": "sum(libvirt_domain_state_code{stateDesc='the domain is running'})",
|
"vm_active": "sum(libvirt_domain_state_code{stateDesc='the domain is running'})",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _fetch_prometheus_metrics():
|
def _fetch_prometheus_metrics():
|
||||||
"""Run all Prometheus queries in parallel and return a dict of name -> value."""
|
"""Run all Prometheus queries in parallel and return a dict of name -> value."""
|
||||||
result = {}
|
result = {}
|
||||||
with ThreadPoolExecutor(max_workers=len(_PROMETHEUS_QUERIES)) as executor:
|
with ThreadPoolExecutor(max_workers=len(_PROMETHEUS_QUERIES)) as executor:
|
||||||
future_to_key = {
|
future_to_key = {
|
||||||
executor.submit(query_prometheus, query=q): key
|
executor.submit(query_prometheus, query=q): key
|
||||||
for key, q in _PROMETHEUS_QUERIES.items()
|
for key, q in _PROMETHEUS_QUERIES.items()
|
||||||
}
|
}
|
||||||
for future in as_completed(future_to_key):
|
for future in as_completed(future_to_key):
|
||||||
key = future_to_key[future]
|
key = future_to_key[future]
|
||||||
try:
|
try:
|
||||||
raw = future.result()
|
raw = future.result()
|
||||||
if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max"):
|
if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max"):
|
||||||
result[key] = float(raw)
|
result[key] = float(raw)
|
||||||
else:
|
else:
|
||||||
result[key] = int(raw)
|
result[key] = int(raw)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
result[key] = 0 if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max") else 0
|
result[key] = 0 if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max") else 0
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def collect_context():
|
def collect_context():
|
||||||
connection = get_connection()
|
connection = get_connection()
|
||||||
region_name = connection._compute_region
|
region_name = connection._compute_region
|
||||||
flavors = get_flavor_list(connection=connection)
|
flavors = get_flavor_list(connection=connection)
|
||||||
audits = get_audits(connection=connection)
|
audits = get_audits(connection=connection)
|
||||||
|
|
||||||
metrics = _fetch_prometheus_metrics()
|
metrics = _fetch_prometheus_metrics()
|
||||||
hosts_total = metrics.get("hosts_total") or 1
|
hosts_total = metrics.get("hosts_total") or 1
|
||||||
pcpu_total = metrics.get("pcpu_total", 0)
|
pcpu_total = metrics.get("pcpu_total", 0)
|
||||||
pcpu_usage = metrics.get("pcpu_usage", 0)
|
pcpu_usage = metrics.get("pcpu_usage", 0)
|
||||||
vcpu_allocated = metrics.get("vcpu_allocated", 0)
|
vcpu_allocated = metrics.get("vcpu_allocated", 0)
|
||||||
vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0)
|
vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0)
|
||||||
pram_total = metrics.get("pram_total", 0)
|
pram_total = metrics.get("pram_total", 0)
|
||||||
pram_usage = metrics.get("pram_usage", 0)
|
pram_usage = metrics.get("pram_usage", 0)
|
||||||
vram_allocated = metrics.get("vram_allocated", 0)
|
vram_allocated = metrics.get("vram_allocated", 0)
|
||||||
vram_overcommit_max = metrics.get("vram_overcommit_max", 0)
|
vram_overcommit_max = metrics.get("vram_overcommit_max", 0)
|
||||||
vm_count = metrics.get("vm_count", 0)
|
vm_count = metrics.get("vm_count", 0)
|
||||||
vm_active = metrics.get("vm_active", 0)
|
vm_active = metrics.get("vm_active", 0)
|
||||||
|
|
||||||
vcpu_total = pcpu_total * vcpu_overcommit_max
|
vcpu_total = pcpu_total * vcpu_overcommit_max
|
||||||
vram_total = pram_total * vram_overcommit_max
|
vram_total = pram_total * vram_overcommit_max
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
# <--- Region data --->
|
# <--- Region data --->
|
||||||
"region": {
|
"region": {
|
||||||
"name": region_name,
|
"name": region_name,
|
||||||
"hosts_total": hosts_total,
|
"hosts_total": hosts_total,
|
||||||
},
|
},
|
||||||
# <--- CPU data --->
|
# <--- CPU data --->
|
||||||
# pCPU data
|
# pCPU data
|
||||||
"pcpu": {
|
"pcpu": {
|
||||||
"total": pcpu_total,
|
"total": pcpu_total,
|
||||||
"usage": pcpu_usage,
|
"usage": pcpu_usage,
|
||||||
"free": pcpu_total - pcpu_usage,
|
"free": pcpu_total - pcpu_usage,
|
||||||
"used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0,
|
"used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0,
|
||||||
},
|
},
|
||||||
# vCPU data
|
# vCPU data
|
||||||
"vcpu": {
|
"vcpu": {
|
||||||
"total": vcpu_total,
|
"total": vcpu_total,
|
||||||
"allocated": vcpu_allocated,
|
"allocated": vcpu_allocated,
|
||||||
"free": vcpu_total - vcpu_allocated,
|
"free": vcpu_total - vcpu_allocated,
|
||||||
"allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0,
|
"allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0,
|
||||||
"overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0,
|
"overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0,
|
||||||
"overcommit_max": vcpu_overcommit_max,
|
"overcommit_max": vcpu_overcommit_max,
|
||||||
},
|
},
|
||||||
# <--- RAM data --->
|
# <--- RAM data --->
|
||||||
# pRAM data
|
# pRAM data
|
||||||
"pram": {
|
"pram": {
|
||||||
"total": pram_total,
|
"total": pram_total,
|
||||||
"usage": pram_usage,
|
"usage": pram_usage,
|
||||||
"free": pram_total - pram_usage,
|
"free": pram_total - pram_usage,
|
||||||
"used_percentage": (pram_usage / pram_total * 100) if pram_total else 0,
|
"used_percentage": (pram_usage / pram_total * 100) if pram_total else 0,
|
||||||
},
|
},
|
||||||
# vRAM data
|
# vRAM data
|
||||||
"vram": {
|
"vram": {
|
||||||
"total": vram_total,
|
"total": vram_total,
|
||||||
"allocated": vram_allocated,
|
"allocated": vram_allocated,
|
||||||
"free": vram_total - vram_allocated,
|
"free": vram_total - vram_allocated,
|
||||||
"allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0,
|
"allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0,
|
||||||
"overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0,
|
"overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0,
|
||||||
"overcommit_max": vram_overcommit_max,
|
"overcommit_max": vram_overcommit_max,
|
||||||
},
|
},
|
||||||
# <--- VM data --->
|
# <--- VM data --->
|
||||||
"vm": {
|
"vm": {
|
||||||
"count": vm_count,
|
"count": vm_count,
|
||||||
"active": vm_active,
|
"active": vm_active,
|
||||||
"stopped": vm_count - vm_active,
|
"stopped": vm_count - vm_active,
|
||||||
"avg_cpu": vcpu_allocated / vm_count if vm_count else 0,
|
"avg_cpu": vcpu_allocated / vm_count if vm_count else 0,
|
||||||
"avg_ram": vram_allocated / vm_count if vm_count else 0,
|
"avg_ram": vram_allocated / vm_count if vm_count else 0,
|
||||||
"density": vm_count / hosts_total if hosts_total else 0,
|
"density": vm_count / hosts_total if hosts_total else 0,
|
||||||
},
|
},
|
||||||
"flavors": flavors,
|
"flavors": flavors,
|
||||||
"audits": audits,
|
"audits": audits,
|
||||||
}
|
}
|
||||||
# Serialize audit list fields for JavaScript so cached context is render-ready
|
# Serialize audit list fields for JavaScript so cached context is render-ready
|
||||||
for audit in context["audits"]:
|
for audit in context["audits"]:
|
||||||
audit["migrations"] = json.dumps(audit["migrations"])
|
audit["migrations"] = json.dumps(audit["migrations"])
|
||||||
audit["host_labels"] = json.dumps(audit["host_labels"])
|
audit["host_labels"] = json.dumps(audit["host_labels"])
|
||||||
audit["cpu_current"] = json.dumps(audit["cpu_current"])
|
audit["cpu_current"] = json.dumps(audit["cpu_current"])
|
||||||
audit["cpu_projected"] = json.dumps(audit["cpu_projected"])
|
audit["cpu_projected"] = json.dumps(audit["cpu_projected"])
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def collect_stats():
|
def collect_stats():
|
||||||
"""Build stats dict: region, pcpu, pram, vcpu, vram, vm, flavors (no audits)."""
|
"""Build stats dict: region, pcpu, pram, vcpu, vram, vm, flavors (no audits)."""
|
||||||
connection = get_connection()
|
connection = get_connection()
|
||||||
region_name = connection._compute_region
|
region_name = connection._compute_region
|
||||||
flavors = get_flavor_list(connection=connection)
|
flavors = get_flavor_list(connection=connection)
|
||||||
metrics = _fetch_prometheus_metrics()
|
metrics = _fetch_prometheus_metrics()
|
||||||
hosts_total = metrics.get("hosts_total") or 1
|
hosts_total = metrics.get("hosts_total") or 1
|
||||||
pcpu_total = metrics.get("pcpu_total", 0)
|
pcpu_total = metrics.get("pcpu_total", 0)
|
||||||
pcpu_usage = metrics.get("pcpu_usage", 0)
|
pcpu_usage = metrics.get("pcpu_usage", 0)
|
||||||
vcpu_allocated = metrics.get("vcpu_allocated", 0)
|
vcpu_allocated = metrics.get("vcpu_allocated", 0)
|
||||||
vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0)
|
vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0)
|
||||||
pram_total = metrics.get("pram_total", 0)
|
pram_total = metrics.get("pram_total", 0)
|
||||||
pram_usage = metrics.get("pram_usage", 0)
|
pram_usage = metrics.get("pram_usage", 0)
|
||||||
vram_allocated = metrics.get("vram_allocated", 0)
|
vram_allocated = metrics.get("vram_allocated", 0)
|
||||||
vram_overcommit_max = metrics.get("vram_overcommit_max", 0)
|
vram_overcommit_max = metrics.get("vram_overcommit_max", 0)
|
||||||
vm_count = metrics.get("vm_count", 0)
|
vm_count = metrics.get("vm_count", 0)
|
||||||
vm_active = metrics.get("vm_active", 0)
|
vm_active = metrics.get("vm_active", 0)
|
||||||
vcpu_total = pcpu_total * vcpu_overcommit_max
|
vcpu_total = pcpu_total * vcpu_overcommit_max
|
||||||
vram_total = pram_total * vram_overcommit_max
|
vram_total = pram_total * vram_overcommit_max
|
||||||
return {
|
return {
|
||||||
"region": {"name": region_name, "hosts_total": hosts_total},
|
"region": {"name": region_name, "hosts_total": hosts_total},
|
||||||
"pcpu": {
|
"pcpu": {
|
||||||
"total": pcpu_total,
|
"total": pcpu_total,
|
||||||
"usage": pcpu_usage,
|
"usage": pcpu_usage,
|
||||||
"free": pcpu_total - pcpu_usage,
|
"free": pcpu_total - pcpu_usage,
|
||||||
"used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0,
|
"used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0,
|
||||||
},
|
},
|
||||||
"vcpu": {
|
"vcpu": {
|
||||||
"total": vcpu_total,
|
"total": vcpu_total,
|
||||||
"allocated": vcpu_allocated,
|
"allocated": vcpu_allocated,
|
||||||
"free": vcpu_total - vcpu_allocated,
|
"free": vcpu_total - vcpu_allocated,
|
||||||
"allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0,
|
"allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0,
|
||||||
"overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0,
|
"overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0,
|
||||||
"overcommit_max": vcpu_overcommit_max,
|
"overcommit_max": vcpu_overcommit_max,
|
||||||
},
|
},
|
||||||
"pram": {
|
"pram": {
|
||||||
"total": pram_total,
|
"total": pram_total,
|
||||||
"usage": pram_usage,
|
"usage": pram_usage,
|
||||||
"free": pram_total - pram_usage,
|
"free": pram_total - pram_usage,
|
||||||
"used_percentage": (pram_usage / pram_total * 100) if pram_total else 0,
|
"used_percentage": (pram_usage / pram_total * 100) if pram_total else 0,
|
||||||
},
|
},
|
||||||
"vram": {
|
"vram": {
|
||||||
"total": vram_total,
|
"total": vram_total,
|
||||||
"allocated": vram_allocated,
|
"allocated": vram_allocated,
|
||||||
"free": vram_total - vram_allocated,
|
"free": vram_total - vram_allocated,
|
||||||
"allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0,
|
"allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0,
|
||||||
"overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0,
|
"overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0,
|
||||||
"overcommit_max": vram_overcommit_max,
|
"overcommit_max": vram_overcommit_max,
|
||||||
},
|
},
|
||||||
"vm": {
|
"vm": {
|
||||||
"count": vm_count,
|
"count": vm_count,
|
||||||
"active": vm_active,
|
"active": vm_active,
|
||||||
"stopped": vm_count - vm_active,
|
"stopped": vm_count - vm_active,
|
||||||
"avg_cpu": vcpu_allocated / vm_count if vm_count else 0,
|
"avg_cpu": vcpu_allocated / vm_count if vm_count else 0,
|
||||||
"avg_ram": vram_allocated / vm_count if vm_count else 0,
|
"avg_ram": vram_allocated / vm_count if vm_count else 0,
|
||||||
"density": vm_count / hosts_total if hosts_total else 0,
|
"density": vm_count / hosts_total if hosts_total else 0,
|
||||||
},
|
},
|
||||||
"flavors": flavors,
|
"flavors": flavors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def collect_audits():
|
def collect_audits():
|
||||||
"""Build audits list with serialized fields for frontend."""
|
"""Build audits list with serialized fields for frontend."""
|
||||||
connection = get_connection()
|
connection = get_connection()
|
||||||
audits = get_audits(connection=connection)
|
audits = get_audits(connection=connection)
|
||||||
for audit in audits:
|
for audit in audits:
|
||||||
audit["migrations"] = json.dumps(audit["migrations"])
|
audit["migrations"] = json.dumps(audit["migrations"])
|
||||||
audit["host_labels"] = json.dumps(audit["host_labels"])
|
audit["host_labels"] = json.dumps(audit["host_labels"])
|
||||||
audit["cpu_current"] = json.dumps(audit["cpu_current"])
|
audit["cpu_current"] = json.dumps(audit["cpu_current"])
|
||||||
audit["cpu_projected"] = json.dumps(audit["cpu_projected"])
|
audit["cpu_projected"] = json.dumps(audit["cpu_projected"])
|
||||||
return audits
|
return audits
|
||||||
|
|
||||||
|
|
||||||
def _skeleton_context():
|
def _skeleton_context():
|
||||||
"""Minimal context for skeleton-only index render."""
|
"""Minimal context for skeleton-only index render."""
|
||||||
empty_flavors = {
|
empty_flavors = {
|
||||||
"first_common_flavor": {"name": "—", "count": 0},
|
"first_common_flavor": {"name": "—", "count": 0},
|
||||||
"second_common_flavor": None,
|
"second_common_flavor": None,
|
||||||
"third_common_flavor": None,
|
"third_common_flavor": None,
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
"skeleton": True,
|
"skeleton": True,
|
||||||
"region": {"name": "—", "hosts_total": 0},
|
"region": {"name": "—", "hosts_total": 0},
|
||||||
"pcpu": {"total": 0, "usage": 0, "free": 0, "used_percentage": 0},
|
"pcpu": {"total": 0, "usage": 0, "free": 0, "used_percentage": 0},
|
||||||
"pram": {"total": 0, "usage": 0, "free": 0, "used_percentage": 0},
|
"pram": {"total": 0, "usage": 0, "free": 0, "used_percentage": 0},
|
||||||
"vcpu": {"total": 0, "allocated": 0, "free": 0, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 0},
|
"vcpu": {"total": 0, "allocated": 0, "free": 0, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 0},
|
||||||
"vram": {"total": 0, "allocated": 0, "free": 0, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 0},
|
"vram": {"total": 0, "allocated": 0, "free": 0, "allocated_percentage": 0, "overcommit_ratio": 0, "overcommit_max": 0},
|
||||||
"vm": {"count": 0, "active": 0, "stopped": 0, "avg_cpu": 0, "avg_ram": 0, "density": 0},
|
"vm": {"count": 0, "active": 0, "stopped": 0, "avg_cpu": 0, "avg_ram": 0, "density": 0},
|
||||||
"flavors": empty_flavors,
|
"flavors": empty_flavors,
|
||||||
"audits": [],
|
"audits": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
if getattr(settings, "USE_MOCK_DATA", False):
|
if getattr(settings, "USE_MOCK_DATA", False):
|
||||||
context = get_mock_context()
|
context = get_mock_context()
|
||||||
return render(request, "index.html", context)
|
return render(request, "index.html", context)
|
||||||
context = _skeleton_context()
|
context = _skeleton_context()
|
||||||
return render(request, "index.html", context)
|
return render(request, "index.html", context)
|
||||||
|
|
||||||
|
|
||||||
def api_stats(request):
|
def api_stats(request):
|
||||||
cache_key = "dashboard_stats"
|
cache_key = "dashboard_stats"
|
||||||
cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120)
|
cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120)
|
||||||
data = cache.get(cache_key)
|
data = cache.get(cache_key)
|
||||||
if data is None:
|
if data is None:
|
||||||
data = collect_stats()
|
data = collect_stats()
|
||||||
cache.set(cache_key, data, timeout=cache_ttl)
|
cache.set(cache_key, data, timeout=cache_ttl)
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
|
||||||
def api_audits(request):
|
def api_audits(request):
|
||||||
cache_key = "dashboard_audits"
|
cache_key = "dashboard_audits"
|
||||||
cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120)
|
cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120)
|
||||||
audits = cache.get(cache_key)
|
audits = cache.get(cache_key)
|
||||||
if audits is None:
|
if audits is None:
|
||||||
audits = collect_audits()
|
audits = collect_audits()
|
||||||
cache.set(cache_key, audits, timeout=cache_ttl)
|
cache.set(cache_key, audits, timeout=cache_ttl)
|
||||||
return JsonResponse({"audits": audits})
|
return JsonResponse({"audits": audits})
|
||||||
@@ -1,33 +1,33 @@
|
|||||||
asgiref==3.11.0
|
asgiref==3.11.0
|
||||||
certifi==2025.11.12
|
certifi==2025.11.12
|
||||||
cffi==2.0.0
|
cffi==2.0.0
|
||||||
charset-normalizer==3.4.4
|
charset-normalizer==3.4.4
|
||||||
cryptography==46.0.3
|
cryptography==46.0.3
|
||||||
decorator==5.2.1
|
decorator==5.2.1
|
||||||
Django==5.2.8
|
Django==5.2.8
|
||||||
dogpile.cache==1.5.0
|
dogpile.cache==1.5.0
|
||||||
idna==3.11
|
idna==3.11
|
||||||
iso8601==2.1.0
|
iso8601==2.1.0
|
||||||
jmespath==1.0.1
|
jmespath==1.0.1
|
||||||
jsonpatch==1.33
|
jsonpatch==1.33
|
||||||
jsonpointer==3.0.0
|
jsonpointer==3.0.0
|
||||||
keystoneauth1==5.12.0
|
keystoneauth1==5.12.0
|
||||||
numpy==2.3.5
|
numpy==2.3.5
|
||||||
openstacksdk==4.8.0
|
openstacksdk==4.8.0
|
||||||
os-service-types==1.8.2
|
os-service-types==1.8.2
|
||||||
pandas==2.3.3
|
pandas==2.3.3
|
||||||
pbr==7.0.3
|
pbr==7.0.3
|
||||||
platformdirs==4.5.0
|
platformdirs==4.5.0
|
||||||
psutil==7.1.3
|
psutil==7.1.3
|
||||||
pycparser==2.23
|
pycparser==2.23
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
pytz==2025.2
|
pytz==2025.2
|
||||||
PyYAML==6.0.3
|
PyYAML==6.0.3
|
||||||
requests==2.32.5
|
requests==2.32.5
|
||||||
requestsexceptions==1.4.0
|
requestsexceptions==1.4.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sqlparse==0.5.4
|
sqlparse==0.5.4
|
||||||
stevedore==5.6.0
|
stevedore==5.6.0
|
||||||
typing_extensions==4.15.0
|
typing_extensions==4.15.0
|
||||||
tzdata==2025.2
|
tzdata==2025.2
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
|
|||||||
@@ -1,76 +1,76 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="light">
|
<html lang="en" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{% block title %}SWatcher{% endblock %}</title>
|
<title>{% block title %}SWatcher{% endblock %}</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="{% static 'css/output.css' %}">
|
<link rel="stylesheet" href="{% static 'css/output.css' %}">
|
||||||
{% block imports %}
|
{% block imports %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
<div class="navbar bg-base-100 shadow-lg border-b border-base-200 sticky top-0 z-10">
|
<div class="navbar bg-base-100 shadow-lg border-b border-base-200 sticky top-0 z-10">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="btn btn-ghost text-xl" href="{% url 'index' %}">SWatcher</a>
|
<a class="btn btn-ghost text-xl" href="{% url 'index' %}">SWatcher</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="px-1 flex items-center gap-3 pr-10">
|
<div class="px-1 flex items-center gap-3 pr-10">
|
||||||
<button type="button" class="btn btn-ghost btn-sm no-print" onclick="window.print()" title="Save as PDF" aria-label="Save as PDF">
|
<button type="button" class="btn btn-ghost btn-sm no-print" onclick="window.print()" title="Save as PDF" aria-label="Save as PDF">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Save as PDF
|
Save as PDF
|
||||||
</button>
|
</button>
|
||||||
<span id="regionBadge" class="badge badge-primary badge-lg">{{ region.name }}</span>
|
<span id="regionBadge" class="badge badge-primary badge-lg">{{ region.name }}</span>
|
||||||
<label class="swap swap-rotate theme-toggle no-print">
|
<label class="swap swap-rotate theme-toggle no-print">
|
||||||
<input type="checkbox" class="theme-controller" value="dark" />
|
<input type="checkbox" class="theme-controller" value="dark" />
|
||||||
<svg class="swap-off fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg class="swap-off fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
|
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="swap-on fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg class="swap-on fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/>
|
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="container mx-auto px-4 py-8 min-h-screen">
|
<main class="container mx-auto px-4 py-8 min-h-screen">
|
||||||
<p class="print-only text-lg font-semibold mb-4">Dashboard report</p>
|
<p class="print-only text-lg font-semibold mb-4">Dashboard report</p>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Function to apply theme
|
// Function to apply theme
|
||||||
function applyTheme(theme) {
|
function applyTheme(theme) {
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
const checkbox = document.querySelector('.theme-controller');
|
const checkbox = document.querySelector('.theme-controller');
|
||||||
checkbox.checked = (theme === 'dark');
|
checkbox.checked = (theme === 'dark');
|
||||||
|
|
||||||
document.dispatchEvent(new Event("themechange"));
|
document.dispatchEvent(new Event("themechange"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load saved theme from localStorage
|
// Load saved theme from localStorage
|
||||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||||
applyTheme(savedTheme);
|
applyTheme(savedTheme);
|
||||||
|
|
||||||
// Listen for toggle changes
|
// Listen for toggle changes
|
||||||
document.querySelector('.theme-controller').addEventListener('change', function() {
|
document.querySelector('.theme-controller').addEventListener('change', function() {
|
||||||
const newTheme = this.checked ? 'dark' : 'light';
|
const newTheme = this.checked ? 'dark' : 'light';
|
||||||
applyTheme(newTheme);
|
applyTheme(newTheme);
|
||||||
localStorage.setItem('theme', newTheme);
|
localStorage.setItem('theme', newTheme);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user