Skip to content

Commit

Permalink
Merge pull request #53 from mantiumai/AlexN/ping-redis
Browse files Browse the repository at this point in the history
add redis ping
  • Loading branch information
alex-nork authored Jul 11, 2023
2 parents cb83e03 + 83abb3d commit 46b9f59
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ cython_debug/
#.idea/

.vscode/launch.json
.vscode/settings.json

chirps/erl_crash.dump
*.dump
11 changes: 9 additions & 2 deletions chirps/target/providers/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from logging import getLogger

from django.db import models
from redis import Redis
from target.models import BaseTarget

logger = getLogger(__name__)
Expand All @@ -28,5 +29,11 @@ def search(self, query: str, max_results: int) -> str:

def test_connection(self) -> bool:
"""Ensure that the Redis target can be connected to."""
logger.error('RedisTarget search not implemented')
raise NotImplementedError
client = Redis(
host=self.host,
port=self.port,
db=self.database_name,
password=self.password,
username=self.username,
)
return client.ping()
117 changes: 80 additions & 37 deletions chirps/target/templates/target/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,54 +9,97 @@ <h1 class="text-success">Targets</h1>
</div>
<div class="ml-auto mr-0 my-auto">
<div class="dropdown"></div>
<button class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown">Create</button>
<div class="dropdown-menu">
{% for target in available_targets %}
<a class="dropdown-item" href="{% url 'target_create' target.model.html_name %}">{{target.model.html_name}}</a>
{% endfor %}
</div>
<button class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown">Create</button>
<div class="dropdown-menu">
{% for target in available_targets %}
<a class="dropdown-item"
href="{% url 'target_create' target.model.html_name %}">{{target.model.html_name}}</a>
{% endfor %}
</div>
</div>
</div>
<hr>
</div>
<hr>

<div class="row">
{% for target in page_obj %}

<div class="card my-4" id="chirps-target-{{target.id}}">
<div class="d-flex card-header">
<img width=48 class="rounded float-end my-auto ml-0 mr-3" src="{{target.logo_url}}">
<h5 class="my-auto ml-0 mr-auto">{{ target.name }}</h5>
</div>
<!-- <h5 class="card-header"><img width=48 class="rounded float-end" src="{{target.logo_url}}">{{ target.name }}</h5> -->

<div class="card-body">
<p class="card-text">

{% if target.html_name == 'Mantium' %}
Application ID: {{ target.app_id }}
{% elif target.html_name == 'Redis' %}
Host: {{ target.host }}<br/>
Port: {{ target.port }}<br/>
Database Name: {{ target.database_name }}<br/>
Username: {{ target.username }}
{% endif %}
{% if target.html_name == 'Pinecone' %}
API Key: {{ target.decrypted_api_key }}<br/>
Environment: {{ target.environment }}<br/>
Index Name: {{ target.index_name }}<br/>
Project Name: {{ target.project_name }}
{% endif %}
</p>
<div class="d-flex">
<a href="{% url 'target_delete' target.id %}" class="btn btn-danger ml-auto mr-0">Delete</a>
<a href="#" class="btn btn-primary ml-2 mr-0 disabled">Edit</a>
<div class="col-md-12 col-lg-12">
<div class="card my-4" id="chirps-target-{{target.id}}">
<div class="d-flex card-header">
<img width=48 class="rounded float-end my-auto ml-0 mr-3" src="{{target.logo_url}}">
<h5 class="my-auto ml-0 mr-auto">{{ target.name }}</h5>
</div>
<div class="card-body">
<p class="card-text">
{% if target.html_name == 'Mantium' %}
Application ID: {{ target.app_id }}
{% elif target.html_name == 'Redis' %}
Host: {{ target.host }}<br/>
Port: {{ target.port }}<br/>
Database Name: {{ target.database_name }}<br/>
Username: {{ target.username }}
{% endif %}
{% if target.html_name == 'Pinecone' %}
API Key: {{ target.decrypted_api_key }}<br/>
Environment: {{ target.environment }}<br/>
Index Name: {{ target.index_name }}<br/>
Project Name: {{ target.project_name }}
{% endif %}
</p>
<div class="d-flex">
<a href="{% url 'target_delete' target.id %}" class="btn btn-danger ml-auto mr-0">Delete</a>
<a href="#" class="btn btn-primary ml-2 mr-0 disabled">Edit</a>
{% if target.html_name == 'Redis' %}
<button data-target-id="{{ target.id }}" class="btn btn-info ml-2 mr-0 ping-btn">Ping</button>
{% endif %}
</div>
</div>
</div>
</div>

{% endfor %}
</div>

<!-- Toast notification -->
<div aria-live="polite" aria-atomic="true" style="position: fixed; top: 0; right: 0; z-index: 1000; min-height: 200px; min-width: 500px">
<div class="toast" id="toast-notification" style="position: absolute; top: 10px; right: 10px;" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-header">
<strong class="me-auto">Notification</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
<!-- Toast message will be added here -->
</div>
</div>
</div>
<!-- End of Toast notification -->

<script>
document.addEventListener('DOMContentLoaded', function () {
const pingButtons = document.querySelectorAll('.ping-btn');
pingButtons.forEach(btn => {
btn.addEventListener('click', async function () {
const targetId = btn.getAttribute('data-target-id');
const response = await fetch(`/target/ping/${targetId}/`, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
if (response.status === 200) {
showToast('Success: Connection established', 'success');
} else {
showToast('Failure: Failed to establish connection', 'danger');
}
});
});
});

function showToast(message, type) {
const toastElement = document.getElementById("toast-notification");
toastElement.querySelector(".toast-body").innerHTML = message;
toastElement.classList.remove("bg-success", "bg-danger");
toastElement.classList.add("bg-" + type);

const toast = new bootstrap.Toast(toastElement);
toast.show();
}
</script>

{% include 'pagination.html' with page_obj=page_obj %}

{% endblock %}
34 changes: 34 additions & 0 deletions chirps/target/tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
"""Test cases for the target application."""
from unittest import mock

import fakeredis
import pytest
from django.contrib.auth.models import User # noqa: E5142
from django.test import TestCase
from django.urls import reverse
from redis import exceptions
from target.providers.mantium import MantiumTarget
from target.providers.redis import RedisTarget


class TargetTests(TestCase):
Expand Down Expand Up @@ -107,3 +113,31 @@ def test_scan_dashboard_last_page(self):
response = self.client.get(reverse('target_dashboard'), {'item_count': 1, 'page': 100})
self.assertContains(response, 'chirps-pagination-widget', status_code=200)
self.assertContains(response, 'chirps-target-3', status_code=200)


class RedisTargetTests(TestCase):
"""Test the RedisTarget"""

def setUp(self):
"""Set up the RedisTarget tests"""
# Login the user before performing any tests
self.client.post(reverse('login'), {'username': 'admin', 'password': 'admin'})
self.server = fakeredis.FakeServer()
self.redis = fakeredis.FakeStrictRedis(server=self.server)

def test_ping__success(self):
"""Test that connectivity check works"""
self.server.connected = True

with mock.patch('target.providers.redis.Redis', return_value=self.redis):
target = RedisTarget(host='localhost', port=12000)
assert target.test_connection()

def test_ping__failure(self):
"""Test that ping raises ConnectionError if the server is not connected"""
self.server.connected = False

with mock.patch('target.providers.redis.Redis', return_value=self.redis):
target = RedisTarget(host='localhost', port=12000)
with pytest.raises(exceptions.ConnectionError):
assert target.test_connection()
1 change: 1 addition & 0 deletions chirps/target/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
urlpatterns = [
path('', views.dashboard, name='target_dashboard'),
path('create/<str:html_name>', views.create, name='target_create'),
path('ping/<int:target_id>/', views.ping, name='target_ping'),
path('delete/<int:target_id>', views.delete, name='target_delete'),
path('decrypted_keys/', views.decrypted_keys, name='decrypted_keys'),
]
21 changes: 20 additions & 1 deletion chirps/target/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""View handlers for targets."""
import json

from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.http import JsonResponse
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from redis import exceptions

from .forms import target_from_html_name, targets
from .models import BaseTarget
from .providers.pinecone import PineconeTarget
from .providers.redis import RedisTarget


def decrypted_keys(request):
Expand Down Expand Up @@ -72,6 +76,21 @@ def create(request, html_name):
return render(request, 'target/create.html', {'form': form, 'target': target})


@login_required
def ping(request, target_id):
"""Ping a RedisTarget database using the test_connection() function."""
target = get_object_or_404(BaseTarget, pk=target_id)
if isinstance(target, RedisTarget):
try:
result = target.test_connection()
return JsonResponse({'success': result})
except exceptions.ConnectionError:
return HttpResponseBadRequest(
json.dumps({'success': False, 'error': 'Unable to connect to Redis'}), content_type='application/json'
)
return HttpResponseBadRequest(json.dumps({'success': False, 'error': 'Not a RedisTarget'}))


@login_required
def delete(request, target_id): # pylint: disable=unused-argument
"""Delete a target from the database."""
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ mantium-client
redis==4.5.5
requests==2.31.0
pinecone-client
pytest
python-dotenv
fakeredis==2.16.0

0 comments on commit 46b9f59

Please sign in to comment.