From 51b02eb6a4efd79e7bfdd75c5e108ac4647d0489 Mon Sep 17 00:00:00 2001 From: Nikolay Tatarinov Date: Sat, 7 Feb 2026 10:23:23 +0300 Subject: [PATCH] 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. --- Dockerfile | 72 +++---- dashboard/urls.py | 14 +- dashboard/views.py | 502 ++++++++++++++++++++++---------------------- requirements.txt | 66 +++--- templates/base.html | 150 ++++++------- 5 files changed, 402 insertions(+), 402 deletions(-) diff --git a/Dockerfile b/Dockerfile index d1a83e3..c480e43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,37 @@ -FROM alpine:3 AS build - -RUN apk update && \ - apk add --no-cache --virtual .build-deps \ - ca-certificates gcc postgresql-dev linux-headers musl-dev \ - libffi-dev jpeg-dev zlib-dev \ - git bash build-base python3-dev \ - dos2unix - -RUN python3 -m venv /venv -ENV PATH "/venv/bin:$PATH" -COPY ./requirements.txt / -RUN pip install -r /requirements.txt - -COPY ./docker-entrypoint.sh /docker-entrypoint.sh -RUN dos2unix /docker-entrypoint.sh && \ - chmod +x /docker-entrypoint.sh - - -FROM alpine:3 - -ENV LANG C.UTF-8 -ENV LC_ALL C.UTF-8 - -ENV PYTHONUNBUFFERED 1 -ENV PATH "/venv/bin:$PATH" - -RUN apk add --no-cache --update python3 curl - -COPY --from=build /venv /venv -COPY --from=build /docker-entrypoint.sh /docker-entrypoint.sh - -WORKDIR /app -COPY . /app - -ENTRYPOINT ["/docker-entrypoint.sh"] +FROM alpine:3 AS build + +RUN apk update && \ + apk add --no-cache --virtual .build-deps \ + ca-certificates gcc postgresql-dev linux-headers musl-dev \ + libffi-dev jpeg-dev zlib-dev \ + git bash build-base python3-dev \ + dos2unix + +RUN python3 -m venv /venv +ENV PATH "/venv/bin:$PATH" +COPY ./requirements.txt / +RUN pip install -r /requirements.txt + +COPY ./docker-entrypoint.sh /docker-entrypoint.sh +RUN dos2unix /docker-entrypoint.sh && \ + chmod +x /docker-entrypoint.sh + + +FROM alpine:3 + +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 + +ENV PYTHONUNBUFFERED 1 +ENV PATH "/venv/bin:$PATH" + +RUN apk add --no-cache --update python3 curl + +COPY --from=build /venv /venv +COPY --from=build /docker-entrypoint.sh /docker-entrypoint.sh + +WORKDIR /app +COPY . /app + +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["python3", "manage.py", "runserver", "0.0.0.0:8000"] \ No newline at end of file diff --git a/dashboard/urls.py b/dashboard/urls.py index da9adb3..cc35b0a 100644 --- a/dashboard/urls.py +++ b/dashboard/urls.py @@ -1,8 +1,8 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('', views.index, name='index'), - path('api/stats/', views.api_stats), - path('api/audits/', views.api_audits), +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('api/stats/', views.api_stats), + path('api/audits/', views.api_audits), ] \ No newline at end of file diff --git a/dashboard/views.py b/dashboard/views.py index d215c3c..84f24d3 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -1,252 +1,252 @@ -import json -from concurrent.futures import ThreadPoolExecutor, as_completed - -from django.conf import settings -from django.core.cache import cache -from django.http import JsonResponse -from django.shortcuts import render -from dashboard.openstack_utils.connect import get_connection -from dashboard.openstack_utils.flavor import get_flavor_list -from dashboard.prometheus_utils.query import query_prometheus -from dashboard.openstack_utils.audits import get_audits -from dashboard.mock_data import get_mock_context - -# Prometheus queries run in parallel (query_key -> query string) -_PROMETHEUS_QUERIES = { - "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_usage": "sum(node_load5{job='node_exporter_compute'})", - "vcpu_allocated": "sum(libvirt_domain_info_virtual_cpus)", - "vcpu_overcommit_max": "avg(openstack_placement_resource_allocation_ratio{resourcetype='VCPU'})", - "pram_total": "sum(node_memory_MemTotal_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_overcommit_max": "avg(avg_over_time(openstack_placement_resource_allocation_ratio{resourcetype='MEMORY_MB'}[5m]))", - "vm_count": "sum(libvirt_domain_state_code)", - "vm_active": "sum(libvirt_domain_state_code{stateDesc='the domain is running'})", -} - - -def _fetch_prometheus_metrics(): - """Run all Prometheus queries in parallel and return a dict of name -> value.""" - result = {} - with ThreadPoolExecutor(max_workers=len(_PROMETHEUS_QUERIES)) as executor: - future_to_key = { - executor.submit(query_prometheus, query=q): key - for key, q in _PROMETHEUS_QUERIES.items() - } - for future in as_completed(future_to_key): - key = future_to_key[future] - try: - raw = future.result() - if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max"): - result[key] = float(raw) - else: - result[key] = int(raw) - except (ValueError, TypeError): - result[key] = 0 if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max") else 0 - return result - - -def collect_context(): - connection = get_connection() - region_name = connection._compute_region - flavors = get_flavor_list(connection=connection) - audits = get_audits(connection=connection) - - metrics = _fetch_prometheus_metrics() - hosts_total = metrics.get("hosts_total") or 1 - pcpu_total = metrics.get("pcpu_total", 0) - pcpu_usage = metrics.get("pcpu_usage", 0) - vcpu_allocated = metrics.get("vcpu_allocated", 0) - vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0) - pram_total = metrics.get("pram_total", 0) - pram_usage = metrics.get("pram_usage", 0) - vram_allocated = metrics.get("vram_allocated", 0) - vram_overcommit_max = metrics.get("vram_overcommit_max", 0) - vm_count = metrics.get("vm_count", 0) - vm_active = metrics.get("vm_active", 0) - - vcpu_total = pcpu_total * vcpu_overcommit_max - vram_total = pram_total * vram_overcommit_max - - context = { - # <--- Region data ---> - "region": { - "name": region_name, - "hosts_total": hosts_total, - }, - # <--- CPU data ---> - # pCPU data - "pcpu": { - "total": pcpu_total, - "usage": pcpu_usage, - "free": pcpu_total - pcpu_usage, - "used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0, - }, - # vCPU data - "vcpu": { - "total": vcpu_total, - "allocated": vcpu_allocated, - "free": vcpu_total - vcpu_allocated, - "allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0, - "overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0, - "overcommit_max": vcpu_overcommit_max, - }, - # <--- RAM data ---> - # pRAM data - "pram": { - "total": pram_total, - "usage": pram_usage, - "free": pram_total - pram_usage, - "used_percentage": (pram_usage / pram_total * 100) if pram_total else 0, - }, - # vRAM data - "vram": { - "total": vram_total, - "allocated": vram_allocated, - "free": vram_total - vram_allocated, - "allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0, - "overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0, - "overcommit_max": vram_overcommit_max, - }, - # <--- VM data ---> - "vm": { - "count": vm_count, - "active": vm_active, - "stopped": vm_count - vm_active, - "avg_cpu": vcpu_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, - }, - "flavors": flavors, - "audits": audits, - } - # Serialize audit list fields for JavaScript so cached context is render-ready - for audit in context["audits"]: - audit["migrations"] = json.dumps(audit["migrations"]) - audit["host_labels"] = json.dumps(audit["host_labels"]) - audit["cpu_current"] = json.dumps(audit["cpu_current"]) - audit["cpu_projected"] = json.dumps(audit["cpu_projected"]) - return context - - -def collect_stats(): - """Build stats dict: region, pcpu, pram, vcpu, vram, vm, flavors (no audits).""" - connection = get_connection() - region_name = connection._compute_region - flavors = get_flavor_list(connection=connection) - metrics = _fetch_prometheus_metrics() - hosts_total = metrics.get("hosts_total") or 1 - pcpu_total = metrics.get("pcpu_total", 0) - pcpu_usage = metrics.get("pcpu_usage", 0) - vcpu_allocated = metrics.get("vcpu_allocated", 0) - vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0) - pram_total = metrics.get("pram_total", 0) - pram_usage = metrics.get("pram_usage", 0) - vram_allocated = metrics.get("vram_allocated", 0) - vram_overcommit_max = metrics.get("vram_overcommit_max", 0) - vm_count = metrics.get("vm_count", 0) - vm_active = metrics.get("vm_active", 0) - vcpu_total = pcpu_total * vcpu_overcommit_max - vram_total = pram_total * vram_overcommit_max - return { - "region": {"name": region_name, "hosts_total": hosts_total}, - "pcpu": { - "total": pcpu_total, - "usage": pcpu_usage, - "free": pcpu_total - pcpu_usage, - "used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0, - }, - "vcpu": { - "total": vcpu_total, - "allocated": vcpu_allocated, - "free": vcpu_total - vcpu_allocated, - "allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0, - "overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0, - "overcommit_max": vcpu_overcommit_max, - }, - "pram": { - "total": pram_total, - "usage": pram_usage, - "free": pram_total - pram_usage, - "used_percentage": (pram_usage / pram_total * 100) if pram_total else 0, - }, - "vram": { - "total": vram_total, - "allocated": vram_allocated, - "free": vram_total - vram_allocated, - "allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0, - "overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0, - "overcommit_max": vram_overcommit_max, - }, - "vm": { - "count": vm_count, - "active": vm_active, - "stopped": vm_count - vm_active, - "avg_cpu": vcpu_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, - }, - "flavors": flavors, - } - - -def collect_audits(): - """Build audits list with serialized fields for frontend.""" - connection = get_connection() - audits = get_audits(connection=connection) - for audit in audits: - audit["migrations"] = json.dumps(audit["migrations"]) - audit["host_labels"] = json.dumps(audit["host_labels"]) - audit["cpu_current"] = json.dumps(audit["cpu_current"]) - audit["cpu_projected"] = json.dumps(audit["cpu_projected"]) - return audits - - -def _skeleton_context(): - """Minimal context for skeleton-only index render.""" - empty_flavors = { - "first_common_flavor": {"name": "—", "count": 0}, - "second_common_flavor": None, - "third_common_flavor": None, - } - return { - "skeleton": True, - "region": {"name": "—", "hosts_total": 0}, - "pcpu": {"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}, - "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}, - "flavors": empty_flavors, - "audits": [], - } - - -def index(request): - if getattr(settings, "USE_MOCK_DATA", False): - context = get_mock_context() - return render(request, "index.html", context) - context = _skeleton_context() - return render(request, "index.html", context) - - -def api_stats(request): - cache_key = "dashboard_stats" - cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120) - data = cache.get(cache_key) - if data is None: - data = collect_stats() - cache.set(cache_key, data, timeout=cache_ttl) - return JsonResponse(data) - - -def api_audits(request): - cache_key = "dashboard_audits" - cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120) - audits = cache.get(cache_key) - if audits is None: - audits = collect_audits() - cache.set(cache_key, audits, timeout=cache_ttl) +import json +from concurrent.futures import ThreadPoolExecutor, as_completed + +from django.conf import settings +from django.core.cache import cache +from django.http import JsonResponse +from django.shortcuts import render +from dashboard.openstack_utils.connect import get_connection +from dashboard.openstack_utils.flavor import get_flavor_list +from dashboard.prometheus_utils.query import query_prometheus +from dashboard.openstack_utils.audits import get_audits +from dashboard.mock_data import get_mock_context + +# Prometheus queries run in parallel (query_key -> query string) +_PROMETHEUS_QUERIES = { + "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_usage": "sum(node_load5{job='node_exporter_compute'})", + "vcpu_allocated": "sum(libvirt_domain_info_virtual_cpus)", + "vcpu_overcommit_max": "avg(openstack_placement_resource_allocation_ratio{resourcetype='VCPU'})", + "pram_total": "sum(node_memory_MemTotal_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_overcommit_max": "avg(avg_over_time(openstack_placement_resource_allocation_ratio{resourcetype='MEMORY_MB'}[5m]))", + "vm_count": "sum(libvirt_domain_state_code)", + "vm_active": "sum(libvirt_domain_state_code{stateDesc='the domain is running'})", +} + + +def _fetch_prometheus_metrics(): + """Run all Prometheus queries in parallel and return a dict of name -> value.""" + result = {} + with ThreadPoolExecutor(max_workers=len(_PROMETHEUS_QUERIES)) as executor: + future_to_key = { + executor.submit(query_prometheus, query=q): key + for key, q in _PROMETHEUS_QUERIES.items() + } + for future in as_completed(future_to_key): + key = future_to_key[future] + try: + raw = future.result() + if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max"): + result[key] = float(raw) + else: + result[key] = int(raw) + except (ValueError, TypeError): + result[key] = 0 if key in ("pcpu_usage", "vcpu_overcommit_max", "vram_overcommit_max") else 0 + return result + + +def collect_context(): + connection = get_connection() + region_name = connection._compute_region + flavors = get_flavor_list(connection=connection) + audits = get_audits(connection=connection) + + metrics = _fetch_prometheus_metrics() + hosts_total = metrics.get("hosts_total") or 1 + pcpu_total = metrics.get("pcpu_total", 0) + pcpu_usage = metrics.get("pcpu_usage", 0) + vcpu_allocated = metrics.get("vcpu_allocated", 0) + vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0) + pram_total = metrics.get("pram_total", 0) + pram_usage = metrics.get("pram_usage", 0) + vram_allocated = metrics.get("vram_allocated", 0) + vram_overcommit_max = metrics.get("vram_overcommit_max", 0) + vm_count = metrics.get("vm_count", 0) + vm_active = metrics.get("vm_active", 0) + + vcpu_total = pcpu_total * vcpu_overcommit_max + vram_total = pram_total * vram_overcommit_max + + context = { + # <--- Region data ---> + "region": { + "name": region_name, + "hosts_total": hosts_total, + }, + # <--- CPU data ---> + # pCPU data + "pcpu": { + "total": pcpu_total, + "usage": pcpu_usage, + "free": pcpu_total - pcpu_usage, + "used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0, + }, + # vCPU data + "vcpu": { + "total": vcpu_total, + "allocated": vcpu_allocated, + "free": vcpu_total - vcpu_allocated, + "allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0, + "overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0, + "overcommit_max": vcpu_overcommit_max, + }, + # <--- RAM data ---> + # pRAM data + "pram": { + "total": pram_total, + "usage": pram_usage, + "free": pram_total - pram_usage, + "used_percentage": (pram_usage / pram_total * 100) if pram_total else 0, + }, + # vRAM data + "vram": { + "total": vram_total, + "allocated": vram_allocated, + "free": vram_total - vram_allocated, + "allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0, + "overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0, + "overcommit_max": vram_overcommit_max, + }, + # <--- VM data ---> + "vm": { + "count": vm_count, + "active": vm_active, + "stopped": vm_count - vm_active, + "avg_cpu": vcpu_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, + }, + "flavors": flavors, + "audits": audits, + } + # Serialize audit list fields for JavaScript so cached context is render-ready + for audit in context["audits"]: + audit["migrations"] = json.dumps(audit["migrations"]) + audit["host_labels"] = json.dumps(audit["host_labels"]) + audit["cpu_current"] = json.dumps(audit["cpu_current"]) + audit["cpu_projected"] = json.dumps(audit["cpu_projected"]) + return context + + +def collect_stats(): + """Build stats dict: region, pcpu, pram, vcpu, vram, vm, flavors (no audits).""" + connection = get_connection() + region_name = connection._compute_region + flavors = get_flavor_list(connection=connection) + metrics = _fetch_prometheus_metrics() + hosts_total = metrics.get("hosts_total") or 1 + pcpu_total = metrics.get("pcpu_total", 0) + pcpu_usage = metrics.get("pcpu_usage", 0) + vcpu_allocated = metrics.get("vcpu_allocated", 0) + vcpu_overcommit_max = metrics.get("vcpu_overcommit_max", 0) + pram_total = metrics.get("pram_total", 0) + pram_usage = metrics.get("pram_usage", 0) + vram_allocated = metrics.get("vram_allocated", 0) + vram_overcommit_max = metrics.get("vram_overcommit_max", 0) + vm_count = metrics.get("vm_count", 0) + vm_active = metrics.get("vm_active", 0) + vcpu_total = pcpu_total * vcpu_overcommit_max + vram_total = pram_total * vram_overcommit_max + return { + "region": {"name": region_name, "hosts_total": hosts_total}, + "pcpu": { + "total": pcpu_total, + "usage": pcpu_usage, + "free": pcpu_total - pcpu_usage, + "used_percentage": (pcpu_usage / pcpu_total * 100) if pcpu_total else 0, + }, + "vcpu": { + "total": vcpu_total, + "allocated": vcpu_allocated, + "free": vcpu_total - vcpu_allocated, + "allocated_percentage": (vcpu_allocated / vcpu_total * 100) if vcpu_total else 0, + "overcommit_ratio": (vcpu_allocated / pcpu_total) if pcpu_total else 0, + "overcommit_max": vcpu_overcommit_max, + }, + "pram": { + "total": pram_total, + "usage": pram_usage, + "free": pram_total - pram_usage, + "used_percentage": (pram_usage / pram_total * 100) if pram_total else 0, + }, + "vram": { + "total": vram_total, + "allocated": vram_allocated, + "free": vram_total - vram_allocated, + "allocated_percentage": (vram_allocated / vram_total * 100) if vram_total else 0, + "overcommit_ratio": (vram_allocated / pram_total) if pram_total else 0, + "overcommit_max": vram_overcommit_max, + }, + "vm": { + "count": vm_count, + "active": vm_active, + "stopped": vm_count - vm_active, + "avg_cpu": vcpu_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, + }, + "flavors": flavors, + } + + +def collect_audits(): + """Build audits list with serialized fields for frontend.""" + connection = get_connection() + audits = get_audits(connection=connection) + for audit in audits: + audit["migrations"] = json.dumps(audit["migrations"]) + audit["host_labels"] = json.dumps(audit["host_labels"]) + audit["cpu_current"] = json.dumps(audit["cpu_current"]) + audit["cpu_projected"] = json.dumps(audit["cpu_projected"]) + return audits + + +def _skeleton_context(): + """Minimal context for skeleton-only index render.""" + empty_flavors = { + "first_common_flavor": {"name": "—", "count": 0}, + "second_common_flavor": None, + "third_common_flavor": None, + } + return { + "skeleton": True, + "region": {"name": "—", "hosts_total": 0}, + "pcpu": {"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}, + "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}, + "flavors": empty_flavors, + "audits": [], + } + + +def index(request): + if getattr(settings, "USE_MOCK_DATA", False): + context = get_mock_context() + return render(request, "index.html", context) + context = _skeleton_context() + return render(request, "index.html", context) + + +def api_stats(request): + cache_key = "dashboard_stats" + cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120) + data = cache.get(cache_key) + if data is None: + data = collect_stats() + cache.set(cache_key, data, timeout=cache_ttl) + return JsonResponse(data) + + +def api_audits(request): + cache_key = "dashboard_audits" + cache_ttl = getattr(settings, "DASHBOARD_CACHE_TTL", 120) + audits = cache.get(cache_key) + if audits is None: + audits = collect_audits() + cache.set(cache_key, audits, timeout=cache_ttl) return JsonResponse({"audits": audits}) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0224db2..ec99f10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,33 +1,33 @@ -asgiref==3.11.0 -certifi==2025.11.12 -cffi==2.0.0 -charset-normalizer==3.4.4 -cryptography==46.0.3 -decorator==5.2.1 -Django==5.2.8 -dogpile.cache==1.5.0 -idna==3.11 -iso8601==2.1.0 -jmespath==1.0.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -keystoneauth1==5.12.0 -numpy==2.3.5 -openstacksdk==4.8.0 -os-service-types==1.8.2 -pandas==2.3.3 -pbr==7.0.3 -platformdirs==4.5.0 -psutil==7.1.3 -pycparser==2.23 -python-dateutil==2.9.0.post0 -pytz==2025.2 -PyYAML==6.0.3 -requests==2.32.5 -requestsexceptions==1.4.0 -six==1.17.0 -sqlparse==0.5.4 -stevedore==5.6.0 -typing_extensions==4.15.0 -tzdata==2025.2 -urllib3==2.5.0 +asgiref==3.11.0 +certifi==2025.11.12 +cffi==2.0.0 +charset-normalizer==3.4.4 +cryptography==46.0.3 +decorator==5.2.1 +Django==5.2.8 +dogpile.cache==1.5.0 +idna==3.11 +iso8601==2.1.0 +jmespath==1.0.1 +jsonpatch==1.33 +jsonpointer==3.0.0 +keystoneauth1==5.12.0 +numpy==2.3.5 +openstacksdk==4.8.0 +os-service-types==1.8.2 +pandas==2.3.3 +pbr==7.0.3 +platformdirs==4.5.0 +psutil==7.1.3 +pycparser==2.23 +python-dateutil==2.9.0.post0 +pytz==2025.2 +PyYAML==6.0.3 +requests==2.32.5 +requestsexceptions==1.4.0 +six==1.17.0 +sqlparse==0.5.4 +stevedore==5.6.0 +typing_extensions==4.15.0 +tzdata==2025.2 +urllib3==2.5.0 diff --git a/templates/base.html b/templates/base.html index c5f1a08..a26fa86 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,76 +1,76 @@ -{% load static %} - - - - - - {% block title %}SWatcher{% endblock %} - - - - - {% block imports %} - {% endblock %} - {% block css %} - {% endblock %} - - - - - - -
- - {% block content %} - {% endblock %} -
- - - {% block script %} - {% endblock %} - +{% load static %} + + + + + + {% block title %}SWatcher{% endblock %} + + + + + {% block imports %} + {% endblock %} + {% block css %} + {% endblock %} + + + + + + +
+ + {% block content %} + {% endblock %} +
+ + + {% block script %} + {% endblock %} + \ No newline at end of file