diff --git a/atdb/requirements/base.txt b/atdb/requirements/base.txt index 3e5d02fb54c39c3df950213191a70b5a1e1b140b..38935ee2a06d85b69c5d5bafc3a26dfeb44da837 100644 --- a/atdb/requirements/base.txt +++ b/atdb/requirements/base.txt @@ -10,6 +10,7 @@ django-filter==2.3.0 django-tables2==2.3.4 djangorestframework==3.14 fontawesome-free==5.15.2 +matplotlib==3.8.3 oauthlib==3.2.2 psycopg2-binary==2.9.3 python3-openid==3.2.0 diff --git a/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph.html b/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph.html index 574e0751ba3be483bf537f98cd3e10598e73d354..684799f7e615be23139b552985b4701a6fd45a38 100644 --- a/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph.html +++ b/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph.html @@ -1,13 +1,16 @@ -# graph_template.html (HTML template for rendering the graph) -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Status Graph</title> -</head> -<body> - <h1>Status Graph</h1> - <img src="records_per_hour_finished_last_100days.png" alt="Status Graph"> -</body> -</html> \ No newline at end of file +{% extends 'taskdatabase/base.html' %} +{% load static %} + +{% block myBlock %} +<div class="container-fluid"> + <div class="card"> + <div class="card-body"> + <p> + <a href="{% url 'create_status_graph' %}" class="btn btn-primary btn-sm" role="button"><i class="fas fa-chart-line"></i> Status Graph</a> + </p> + <img src="/atdb/static/status_graph.png" alt="Status Graph"> + </div> + </div> +</div> + +{% endblock %} \ No newline at end of file diff --git a/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph_input_form.html b/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph_input_form.html index c48bac0c543859ab2b126bc49b3b87dab4264d51..4df38435be85f1b0be098fdba0acfaf04d167fd1 100644 --- a/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph_input_form.html +++ b/atdb/taskdatabase/templates/taskdatabase/graphs/status_graph_input_form.html @@ -1,24 +1,24 @@ -# input_form.html (HTML template for input form) -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Create Status Graph</title> -</head> -<body> - <form action="{% url 'create_status_graph' %}" method="post"> - {% csrf_token %} - <label for="status">Status:</label> - <input type="text" id="status" name="status" value="finished"><br><br> - <label for="days">Last X days:</label> - <input type="number" id="days" name="days" value="100"><br><br> - <label for="bin_size">Bin Size:</label> - <select id="bin_size" name="bin_size"> - <option value="hour">Hour</option> - <option value="day">Day</option> - </select><br><br> - <button type="submit">Generate Graph</button> - </form> -</body> -</html> \ No newline at end of file +{% extends 'taskdatabase/base.html' %} +{% load static %} + +{% block myBlock %} +<div class="container-fluid"> + <div class="card"> + <div class="card-body"> + <form action="{% url 'create_status_graph' %}" method="post"> + {% csrf_token %} + <label for="status">Which (end) status to plot?:</label> + <input type="text" id="status" name="status" value="finished"><br><br> + <label for="days">Last X days:</label> + <input type="number" id="days" name="days" value="30"><br><br> + <label for="bin_size">Bin Size:</label> + <select id="bin_size" name="bin_size" value="day"> + <option value="day">Day</option> + <option value="hour">Hour</option> + </select><br><br> + <button type="submit">Generate Graph</button> + </form> + </div> + </div> +</div> +{% endblock %} \ No newline at end of file diff --git a/atdb/taskdatabase/templates/taskdatabase/index.html b/atdb/taskdatabase/templates/taskdatabase/index.html index e79e6adc689a4b3a03974da919f92ed80434e929..0a85fb6c9c2736f74918b0f0a76714d395af50f4 100644 --- a/atdb/taskdatabase/templates/taskdatabase/index.html +++ b/atdb/taskdatabase/templates/taskdatabase/index.html @@ -31,7 +31,7 @@ {% include 'taskdatabase/pagination.html' %} </div> </div> - <p class="footer"> Version 1 Mar 2024 + <p class="footer"> Version 7 Mar 2024 </div> {% include 'taskdatabase/refresh.html' %} diff --git a/atdb/taskdatabase/templates/taskdatabase/monitoring_page.html b/atdb/taskdatabase/templates/taskdatabase/monitoring_page.html index 38a658d9f10d4b8de456a4105012889a130ab9cc..85c149daf347c00342b5e9eb4ad5071bc40c5610 100644 --- a/atdb/taskdatabase/templates/taskdatabase/monitoring_page.html +++ b/atdb/taskdatabase/templates/taskdatabase/monitoring_page.html @@ -3,7 +3,7 @@ {% block myBlock %} -<div class="hiking-container container-fluid"> +<div class="container-fluid"> <div class="card"> <div class="card-body"> <div class="row"> @@ -16,11 +16,14 @@ <div class="thumbnail"> <h5>Health and Status of the LDV services.</h5> - {% if user.is_superuser %} + <p> - <a href="{% url 'clear-inactive-services' %}" class="btn btn-secondary btn-sm" role="button"><i class="fas fa-window-close"></i> Clear Services</a> + {% if user.is_superuser %} + <a href="{% url 'clear-inactive-services' %}" class="btn btn-secondary btn-sm" role="button"><i class="fas fa-window-close"></i> Clear Services</a> + {% endif %} + <a href="{% url 'create_status_graph' %}" class="btn btn-primary btn-sm" role="button"><i class="fas fa-chart-line"></i> Status Graph</a> </p> - {% endif %} + <table class="table table-striped"> <th>LDV-Service</th><th>Host</th><th>Enabled</th><th>Last Status</th><th>Timestamp</th><th>Process id</th><th>Description</th> <tbody> diff --git a/atdb/taskdatabase/urls.py b/atdb/taskdatabase/urls.py index e01d493c413af6df66ec62dff8db9bfe24e95a65..b846c15dcf892c1cfd9ba1dbc854ea4b46acec19 100644 --- a/atdb/taskdatabase/urls.py +++ b/atdb/taskdatabase/urls.py @@ -52,6 +52,7 @@ urlpatterns = [ path('monitoring/', views.ShowMonitoring, name='monitoring'), path('diagram/', views.DiagramView.as_view(), name='diagram'), path('config/', views.ShowConfig, name='config'), + path('create_status_graph/', views.CreateStatusGraph, name='create_status_graph'), # --- REST API --- diff --git a/atdb/taskdatabase/views.py b/atdb/taskdatabase/views.py index 8c78898faf510adf383957ca74aff97edfb86da0..57a10fbe5426fd3e76999d71efa2ee1ac28841d0 100644 --- a/atdb/taskdatabase/views.py +++ b/atdb/taskdatabase/views.py @@ -1,5 +1,13 @@ import logging import json +from datetime import datetime, timedelta +try: + import matplotlib.pyplot as plt +except: + # enable debugging, but without matplotlib + pass + +import psycopg2 from . import config from django.contrib.auth.decorators import login_required @@ -1767,3 +1775,88 @@ def UpdateSummaryFlag(request, task_id): return JsonResponse({ 'is_summary': task.is_summary }) + + + +def CreateStatusGraph(request): + query_per_hour = """ + SELECT + DATE_TRUNC('hour', timestamp) AS hour, + COUNT(*) AS num_records + FROM + taskdatabase_status + WHERE name = %s + AND timestamp >= %s -- Filter for records within the last x days + GROUP BY + DATE_TRUNC('hour', timestamp) + ORDER BY + DATE_TRUNC('hour', timestamp); + """ + query_per_day = """ + SELECT + DATE_TRUNC('day', timestamp) AS day, + COUNT(*) AS num_records + FROM + taskdatabase_status + WHERE name = %s + AND timestamp >= %s -- Filter for records within the last x days + GROUP BY + DATE_TRUNC('day', timestamp) + ORDER BY + DATE_TRUNC('day', timestamp); + """ + + if request.method == 'POST': + status = request.POST.get('status', 'finished') + try: + days_to_check = int(request.POST.get('days', 60)) + except: + days_to_check = 60 + bin_size = request.POST.get('bin_size', 'day') + + # Calculate the date x days ago + start_date = datetime.now() - timedelta(days=days_to_check) + database = settings.DATABASES['default'] + + # Connect to your PostgreSQL database + conn = psycopg2.connect( + dbname=database['NAME'], + user=database['USER'], + password=database['PASSWORD'], + host=database['HOST'], + port=database['PORT'], + ) + + # Execute the SQL query + cur = conn.cursor() + if bin_size == 'day': + cur.execute(query_per_day, (status,start_date)) + else: + cur.execute(query_per_hour, (status, start_date)) + + # Fetch the results + results = cur.fetchall() + + # Close cursor and connection + cur.close() + conn.close() + + # Extract hours and number of records from results + records = [result[0] for result in results] + num_records = [result[1] for result in results] + + # Plot the graph + plt.figure(figsize=(10, 6)) + plt.plot(records, num_records, marker='o') + plt.xlabel(bin_size.capitalize()) + plt.ylabel('Number of Records') + plt.title(f'{status} per {bin_size} for the last {days_to_check} days') + plt.xticks(rotation=45) + plt.grid(True) + plt.tight_layout() + plt.savefig('taskdatabase/static/status_graph.png') + + # Render the template with the graph + return render(request, 'taskdatabase/graphs/status_graph.html') + + return render(request, 'taskdatabase/graphs/status_graph_input_form.html', {'image_path': 'status_graph.png'}) \ No newline at end of file