Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frontend team #306

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
f08e0fc
Add method to Grafana API to get an array of all dashboards. Useful f…
daisycrego Apr 3, 2020
7e22fc6
Issue #225: When a GFCoonfig is created, dashboards are added for any…
daisycrego Apr 3, 2020
e055198
Issue #225: Tests for GFConfig - when a GFConfig is created, if there…
daisycrego Apr 3, 2020
2ea3592
Getting in sync with frontend-team before merging in latest.
daisycrego Apr 3, 2020
828ddfc
Initial commit for #295: GF Config: View Existing Dashboards
daisycrego Apr 4, 2020
a1e2ede
Issue #295: Add test of updated GFConfig view: If a dashboard exists,…
daisycrego Apr 4, 2020
f8326cc
Issue #295: Improve test of GFConfig GET view to confirm that the cor…
daisycrego Apr 4, 2020
434495d
Update GFConfig view to include link to Grafana for each event. Add m…
daisycrego Apr 5, 2020
9f8f33c
Improve SensorPanels form style.
daisycrego Apr 5, 2020
e8b234a
Issue #295: Add update_dashboard function to GFConfig view - when upd…
daisycrego Apr 5, 2020
512cd2c
Fix issue with test_config_post_event_exists_dashboard_created which …
daisycrego Apr 5, 2020
5728730
Black and flake8.
daisycrego Apr 5, 2020
71c5213
Issue #295: Add a CustomModelChoiceField class which extends ModelMul…
daisycrego Apr 5, 2020
abf52f4
Issue #295: Reset Dashboards view: Created a new view for handling da…
daisycrego Apr 5, 2020
c094010
Issue #295: Add delete dashboard feature to GFConfig Dashboards view.…
daisycrego Apr 5, 2020
0ab2884
Merge pull request #294 from gcivil-nyu-org/grafana
sunnybansal Apr 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions ag_data/models.py
Expand Up @@ -53,6 +53,9 @@ class AGSensor(models.Model):
name = models.CharField(max_length=1024, blank=True)
type_id = models.ForeignKey(AGSensorType, null=False, on_delete=models.PROTECT)

# def __str__(self):
# return u"{0}".format(self.name)


class AGMeasurement(models.Model):
"""Stores the information about sensor measurements, including timestamp, event, sensor
Expand Down
17 changes: 16 additions & 1 deletion mercury/forms.py
@@ -1,7 +1,7 @@
"""This module defines the ModelForms (or Forms) that are used by the rendering
engine to accept input for various features of the site"""
from django import forms
from ag_data.models import AGEvent, AGVenue
from ag_data.models import AGEvent, AGVenue, AGSensor
from mercury.models import (
GFConfig,
TemperatureSensor,
Expand Down Expand Up @@ -56,6 +56,21 @@ class Meta:
}


class CustomModelChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return "%s" % (obj.name)


class DashboardSensorPanelsForm(forms.ModelForm):
class Meta:
model = AGSensor
exclude = ["id", "name", "type_id"]

sensors = CustomModelChoiceField(
widget=forms.CheckboxSelectMultiple, queryset=AGSensor.objects.all(), label=""
)


class TemperatureForm(forms.ModelForm):
class Meta:
model = TemperatureSensor
Expand Down
101 changes: 92 additions & 9 deletions mercury/grafanaAPI/grafana_api.py
Expand Up @@ -3,6 +3,7 @@
import requests
import string
import random
from ag_data.models import AGSensor, AGEvent

TOKEN = "eyJrIjoiRTQ0cmNGcXRybkZlUUNZWmRvdFI0UlMwdFVYVUt3bzgiLCJuIjoia2V5IiwiaWQiOjF9"
HOST = "https://dbc291.grafana.net"
Expand Down Expand Up @@ -85,10 +86,24 @@ def validate_credentials(self):

return True

def get_dashboard_by_event_name(self, event_name):
def get_all_dashboards(self):
"""
:return: A list of all existing dashboards (excluding the home dashboard).
If an empty list is returned, there are no dashboards except for the home
dashboard.
"""

endpoint = os.path.join(self.hostname, "api/search/")
response = requests.get(url=endpoint, auth=("api_key", self.api_token))
json = response.json()
return json

def get_dashboard_by_name(self, event_name):
"""
:param event_name: Event name used for the target dashboard.
:return: Returns True if a dashboard was found with this name, False otherwise.
:return: Returns a JSON response from the API with basic details if a
dashboard was found with this name, including a JSON representation of the
dashboard and its panels, False otherwise.
"""
# If there are spaces in the name, the GF API will replace them with dashes
# to generate the "slug". A slug can be used to query the API.
Expand All @@ -102,6 +117,18 @@ def get_dashboard_by_event_name(self, event_name):
else:
return None

def get_dashboard_url_by_name(self, name):
name = name.lower().replace(" ", "-")

dashboard = self.get_dashboard_by_name(name)
if dashboard:
endpoint = dashboard["meta"]["url"].strip("/")
url = os.path.join(self.hostname, endpoint)
else:
url = None

return url

def get_dashboard_with_uid(self, uid):
"""
:param uid: uid of the target dashboard
Expand Down Expand Up @@ -357,7 +384,7 @@ def add_panel(self, sensor, event):
field_array.append(field)

# Find dashboard uid for event
dashboard_info = self.get_dashboard_by_event_name(event.name)
dashboard_info = self.get_dashboard_by_name(event.name)

if dashboard_info is None:
raise ValueError("Dashboard not found for this event.")
Expand Down Expand Up @@ -429,31 +456,87 @@ def add_panel(self, sensor, event):
except KeyError as error:
raise ValueError(f"Sensor panel not added: {error}")

def delete_all_panels(self, uid):
def delete_all_panels_by_dashboard_name(self, name):
"""

Deletes all panels from dashboard with given uid.
Deletes all panels from dashboard with given name.

:param uid: uid of dashboard to delete
:param name: name of dashboard to delete
:return: None.
"""

# Retrieve current dashboard dict
dashboard_info = self.get_dashboard_with_uid(uid)
dashboard_info = self.get_dashboard_by_name(name)

# Create updated dashboard dict with empty list of panels
panels = []
updated_dashboard = self.create_dashboard_update_dict(dashboard_info, panels)

# POST updated dashboard with empty list of panels
# POST updated dashboard
headers = {"Content-Type": "application/json"}
requests.post(
self.urls["dashboard_post"],
self.endpoints["dashboards"],
data=json.dumps(updated_dashboard),
headers=headers,
auth=("api_key", self.api_token),
)

# TODO For each sensor in sensors, check if the sensor is already found in the
# dashboard. If it isn't, add a new panel. If it already exists, check what is
# different - avoid overwriting style changes
def update_dashboard_panels(self, dashboard_name, sensors=[]):
"""
Updates the dashboard with title=`dashboard_name` so that it displays sensor
panels based on the ones in `sensors`. If `sensors` is empty, panels will be
cleared from the dashboard.

:param dashboard_name: Name of dashboard to reset.
:param sensors: Optional list of sensors, if provided sensor panels will be
added.
:return: N/a.
"""
# remove all panels
self.delete_all_panels_by_dashboard_name(dashboard_name)

# retrieve event object
event = AGEvent.objects.filter(name=dashboard_name).first()

if event:
# add new set of panels if provided
for sensor in sensors:
self.add_panel(sensor, event)
else:
raise ValueError(
"Unable to locate event with dashboard name: " + dashboard_name
)

def get_all_sensors(self, dashboard_name):
"""

:param dashboard_name: Name of the target dashboard
:return: Returns a list of all sensor objects which currently exist as panels
in the dashboard.

"""
# Retrieve the current dashboard
dashboard = self.get_dashboard_by_name(dashboard_name)
try:
dashboard = dashboard["dashboard"]
panels = dashboard["panels"]
except KeyError:
panels = []

sensor_names = []
for panel in panels:
sensor_names.append(panel["title"])

sensors = []
for name in sensor_names:
sensor = AGSensor.objects.filter(name=name).first()
sensors.append(sensor)

return sensors

# Helper method for add_panel
def create_panel_dict(self, panel_id, fields, panel_sql_query, title, x, y):
"""
Expand Down
5 changes: 5 additions & 0 deletions mercury/static/mercury/style.css
Expand Up @@ -4445,3 +4445,8 @@ a {
width: 50%;
}

.gfconfig-text-align {
text-align:left;
float: left;
width: 50%
}
128 changes: 126 additions & 2 deletions mercury/templates/gf_configs.html
Expand Up @@ -12,8 +12,131 @@
<body>
{% include 'sidebar.html' %}
<div class="topbar-container">

<!--Grafana Dashboards View-->
<div>
<h2>Existing Grafana Dashboards</h2>

{% for config in configs %}
{% if dashboards %}
{% for dashboard in dashboards %}
<div class="panel" name="{{ dashboard.name }}">
<h3 class="gfconfig-text-align"> Event:
<a href="{{ dashboard.url }}">{{ dashboard.name }}</a>
</h3>

<form class="gfconfig-text-align" method="POST"
action="update_dashboard/{{ config.id }}"
id="update_{{ dashboard.name }}">
{% csrf_token %} {% load crispy_forms_tags %}
{{ dashboard.sensor_form|crispy }}
<input type="hidden" name="dashboard_name"
value="{{ dashboard.name }}">
</form>

<form class="gfconfig-text-align" method="POST"
action="reset_dashboard/{{ config.id }}"
id="reset_{{ dashboard.name }}">
{% csrf_token %} {% load crispy_forms_tags %}
<input type="hidden" name="dashboard_name"
value="{{ dashboard.name }}">
</form>

<form class="gfconfig-text-align" method="POST"
action="delete_dashboard/{{ config.id }}"
id="delete_{{ dashboard.name }}">
{% csrf_token %} {% load crispy_forms_tags %}
<input type="hidden" name="dashboard_name"
value="{{ dashboard.name }}">
</form>

<br>
<div>
<input form="reset_{{ dashboard.name }}" class="submitbutton simulator-btn
grafana-btn
grafana-btn-blue gfconfig-text-align" type="submit"
name="submit-reset-dashboard"
value="Reset Sensor Panels">

<input form="delete_{{ dashboard.name }}"
class="submitbutton simulator-btn
grafana-btn
grafana-btn-red gfconfig-text-align" type="submit"
name="submit-delete-dashboard"
value="Delete Dashboard">

<input form="update_{{ dashboard.name }}"
class="submitbutton simulator-btn grafana-btn
grafana-btn-green gfconfig-text-align" type="submit"
name="submit-update-sensors"
value="Update Sensor Panels"

</div>
<br> <br>
<!--
<form class="gfconfig-text-align" method="POST"
action="update_dashboard/{{ config.id }}"
id="{{ dashboard.name }}">
{% csrf_token %} {% load crispy_forms_tags %}
{{ dashboard.sensor_form|crispy }}
<br>
<div>
<input type="hidden" name="dashboard_name"
value="{{ dashboard.name }}">

<input class="submitbutton simulator-btn grafana-btn
grafana-btn-blue" type="submit"
name="submit-reset-dashboard"
value="Reset Sensor Panels">

<input class="submitbutton simulator-btn grafana-btn
grafana-btn-red" type="submit"
name="submit-delete-dashboard"
value="Delete Dashboard">

<input class="submitbutton
simulator-btn
grafana-btn
grafana-btn-green" type="submit"
name="submit-update-sensors"
value="Update Sensor Panels"
</div>
<br> <br>
</form>
-->
<!--
<h4> Included: </h4>
{% for sensor in dashboard.sensors %}
<ul>
{% if sensor.panel_exists %}
<li>{{ sensor.name }}</li>
{% endif %}
</ul>
{% endfor %}

<h4> Missing: </h4>
{% for sensor in dashboard.sensors %}
{% if not sensor.panel_exists %}
<input type="checkbox"> {{ sensor.name }} </input>
{% endif %}
{% endfor %}

-->
</div>
{% endfor %}

{% else %}

<p> No dashboards yet. Add one? </p>

{% endif %}
{% endfor %}

</div> <!--Grafana Dashboards View -->


<!--Configs Table -->
<div class="mt-50">
<div>

{% if configs %}
<h2>Existing Grafana Hosts</h2>
Expand Down Expand Up @@ -51,7 +174,8 @@ <h2>Add Grafana Host</h2>
{% csrf_token %} {% load crispy_forms_tags %} {{ config_form|crispy }}
<br>
<div>
<input class="submitbutton simulator-btn grafana-btn grafana-btn-green" type="submit" name="submit" value="Submit">
<input
class="submitbutton simulator-btn grafana-btn grafana-btn-green" type="submit" name="submit" value="Submit">
</div>
<br> <br>
</form>
Expand Down