diff --git a/apimanager/base/context_processors.py b/apimanager/base/context_processors.py index d3dff76..7325b6f 100644 --- a/apimanager/base/context_processors.py +++ b/apimanager/base/context_processors.py @@ -13,7 +13,7 @@ USER_CURRENT = "/users/current" def api_version_processor(request): """Returns the configured API_VERSION""" - return {'API_VERSION': settings.API_VERSION['v500']} + return {'API_VERSION': settings.API_VERSION['v510']} def portal_page(request): @@ -82,7 +82,7 @@ def api_user_id(request): """Returns the API user id of the logged-in user""" user_id = 'not authenticated' get_current_user_api_url = USER_CURRENT - #Here we can not get the user from obp-api side, so we use the django auth user id here. + #Here we can not get the user from obp-api side, so we use the django auth user id here. cache_key_django_user_id = request.session._session.get('_auth_user_id') cache_key = '{},{},{}'.format('api_user_id',get_current_user_api_url, cache_key_django_user_id) apicaches=None @@ -112,4 +112,3 @@ def api_tester_url(request): """Returns the URL to the API Tester for the API instance""" url = getattr(settings, 'API_TESTER_URL', None) return {'API_TESTER_URL': url} - diff --git a/apimanager/consumers/forms.py b/apimanager/consumers/forms.py index 79f673b..0d5832c 100644 --- a/apimanager/consumers/forms.py +++ b/apimanager/consumers/forms.py @@ -12,8 +12,45 @@ class ApiConsumersForm(forms.Form): required=True, ) + from_date = forms.DateTimeField( + label='From Date', + widget=forms.DateTimeInput( + attrs={ + 'class': 'form-control', + 'type': 'datetime-local', + 'value': '2024-01-01T00:00', + } + ), + required=False, + initial='2024-01-01T00:00:00', + ) + + to_date = forms.DateTimeField( + label='To Date', + widget=forms.DateTimeInput( + attrs={ + 'class': 'form-control', + 'type': 'datetime-local', + 'value': '2026-01-01T00:00', + } + ), + required=False, + initial='2026-01-01T00:00:00', + ) + + per_second_call_limit = forms.IntegerField( + label='Per Second Call Limit', + widget=forms.NumberInput( + attrs={ + 'class': 'form-control', + } + ), + initial=-1, + required=False, + ) + per_minute_call_limit = forms.IntegerField( - label='per_minute_call_limit', + label='Per Minute Call Limit', widget=forms.NumberInput( attrs={ 'class': 'form-control', @@ -24,7 +61,7 @@ class ApiConsumersForm(forms.Form): ) per_hour_call_limit = forms.IntegerField( - label='per_hour_call_limit', + label='Per Hour Call Limit', widget=forms.NumberInput( attrs={ 'class': 'form-control', @@ -33,8 +70,9 @@ class ApiConsumersForm(forms.Form): initial=-1, required=False, ) + per_day_call_limit = forms.IntegerField( - label='per_day_call_limit', + label='Per Day Call Limit', widget=forms.NumberInput( attrs={ 'class': 'form-control', @@ -43,8 +81,9 @@ class ApiConsumersForm(forms.Form): initial=-1, required=False, ) + per_week_call_limit = forms.IntegerField( - label='per_week_call_limit', + label='Per Week Call Limit', widget=forms.NumberInput( attrs={ 'class': 'form-control', @@ -55,7 +94,7 @@ class ApiConsumersForm(forms.Form): ) per_month_call_limit = forms.IntegerField( - label='per_month_call_limit', + label='Per Month Call Limit', widget=forms.NumberInput( attrs={ 'class': 'form-control', diff --git a/apimanager/consumers/static/consumers/css/consumers.css b/apimanager/consumers/static/consumers/css/consumers.css index 7c502fc..5d47940 100644 --- a/apimanager/consumers/static/consumers/css/consumers.css +++ b/apimanager/consumers/static/consumers/css/consumers.css @@ -1,20 +1,184 @@ -.consumers #consumer-list { - margin-top: 20px; +.consumers #consumer-list { + margin-top: 20px; } #consumers .btn-group-vertical.filter-enabled, #consumers .btn-group-vertical.filter-apptype { - margin-top: 10px; + margin-top: 10px; } #consumers-detail div { - margin: 5px 0; + margin: 5px 0; } #consumers .filter a { - font-size: 12px; + font-size: 12px; } #consumers .actions .btn { - margin-bottom: 2px; + margin-bottom: 2px; +} + +/* Rate Limiting Styles */ +#consumers-detail h2 { + color: #333; + border-bottom: 2px solid #007bff; + padding-bottom: 10px; + margin-bottom: 20px; +} + +#consumers-detail .panel-info { + border-color: #bee5eb; + background-color: #d1ecf1; +} + +#consumers-detail .panel-info .panel-body { + background-color: #f8f9fa; + border-radius: 5px; + padding: 15px; +} + +#consumers-detail .text-info { + color: #0c5460 !important; + font-size: 16px; + font-weight: bold; +} + +#consumers-detail .text-muted { + color: #6c757d !important; + font-size: 12px; +} + +#consumers-detail .form-group label { + font-weight: bold; + color: #495057; +} + +#consumers-detail .btn-primary { + background-color: #007bff; + border-color: #007bff; + padding: 10px 20px; + font-weight: bold; +} + +#consumers-detail .btn-primary:hover { + background-color: #0056b3; + border-color: #0056b3; +} + +/* Usage statistics 6-column layout */ +#consumers-detail .panel-info .col-sm-2 { + min-height: 80px; + padding: 10px 5px; + transition: all 0.3s ease; +} + +/* Readonly fields styling */ +#consumers-detail input[readonly] { + background-color: #f8f9fa; + color: #6c757d; + cursor: not-allowed; + border: 1px solid #dee2e6; +} + +/* Refresh button styling */ +#refreshUsageBtn { + transition: all 0.3s ease; + margin-left: 10px; +} + +#refreshUsageBtn:hover { + background-color: #138496; + border-color: #117a8b; +} + +#refreshUsageBtn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Progress bar styling */ +#refreshProgress { + height: 10px; + background-color: #f5f5f5; + border-radius: 5px; + overflow: hidden; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + +#refreshProgress .progress-bar { + transition: width 0.3s ease; + background-color: #17a2b8; +} + +/* Usage update animation */ +.usage-calls { + transition: background-color 0.5s ease; +} + +.usage-calls.updating { + background-color: #d4edda !important; + padding: 2px 6px; + border-radius: 3px; +} + +/* Spinning animation for refresh icon */ +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.glyphicon-spin { + animation: spin 1s infinite linear; +} + +/* Panel pulse effect during refresh */ +.panel-refreshing { + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(23, 162, 184, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(23, 162, 184, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(23, 162, 184, 0); + } +} + +/* Updated data highlight */ +.data-updated { + background-color: #d4edda; + border-left: 3px solid #28a745; + padding-left: 10px; + transition: all 0.5s ease; +} + +/* Responsive adjustments for usage stats */ +@media (max-width: 768px) { + #consumers-detail .panel-info .col-xs-6 { + margin-bottom: 15px; + } + + #consumers-detail .panel-info .col-sm-2 { + min-height: auto; + } + + #refreshUsageBtn { + font-size: 12px; + padding: 4px 8px; + } +} + +/* Timestamp fields in configuration section */ +#consumers-detail .form-group input[readonly] { + font-size: 12px; + padding: 6px 12px; } diff --git a/apimanager/consumers/static/consumers/js/consumers.js b/apimanager/consumers/static/consumers/js/consumers.js index 8da0463..cf25624 100644 --- a/apimanager/consumers/static/consumers/js/consumers.js +++ b/apimanager/consumers/static/consumers/js/consumers.js @@ -1,2 +1,100 @@ -$(document).ready(function($) { +$(document).ready(function ($) { + // Handle datetime-local inputs for rate limiting + function initializeDateTimeFields() { + // Set default values for datetime fields if they're empty + var fromDateField = $("#id_from_date"); + var toDateField = $("#id_to_date"); + + // If fields are empty, set default values + if (!fromDateField.val()) { + fromDateField.val("2024-01-01T00:00"); + } + if (!toDateField.val()) { + toDateField.val("2026-01-01T00:00"); + } + } + + // Convert ISO datetime strings to datetime-local format for form inputs + function convertISOToLocalDateTime(isoString) { + if (!isoString) return ""; + // Remove the 'Z' and convert to local datetime format + return isoString.replace("Z", "").substring(0, 16); + } + + // Initialize datetime fields with existing values if they exist + function setExistingDateTimeValues() { + var fromDate = $("[data-from-date]").data("from-date"); + var toDate = $("[data-to-date]").data("to-date"); + + if (fromDate && fromDate !== "1099-12-31T23:00:00Z") { + $("#id_from_date").val(convertISOToLocalDateTime(fromDate)); + } + if (toDate && toDate !== "1099-12-31T23:00:00Z") { + $("#id_to_date").val(convertISOToLocalDateTime(toDate)); + } + } + + // Form validation + function validateRateLimitingForm() { + $("form").on("submit", function (e) { + var hasError = false; + var errorMessage = ""; + + // Check if any limit values are negative (except -1 which means unlimited) + $('input[type="number"]').each(function () { + var value = parseInt($(this).val()); + if (value < -1) { + hasError = true; + errorMessage += + "Rate limit values must be -1 (unlimited) or positive numbers.\n"; + return false; + } + }); + + // Check date range + var fromDate = new Date($("#id_from_date").val()); + var toDate = new Date($("#id_to_date").val()); + + if (fromDate && toDate && fromDate > toDate) { + hasError = true; + errorMessage += "From Date must be before To Date.\n"; + } + + if (hasError) { + alert(errorMessage); + e.preventDefault(); + return false; + } + }); + } + + // Add visual feedback for current usage status + function enhanceUsageDisplay() { + $(".text-info").each(function () { + var callsMade = parseInt($(this).text().match(/\d+/)); + var parentDiv = $(this).closest(".col-xs-6, .col-sm-3"); + var limitText = parentDiv.find("strong").text().toLowerCase(); + + // You could add logic here to highlight usage that's approaching limits + // For now, we'll just ensure consistent styling + $(this).addClass("usage-indicator"); + }); + } + + // Initialize all functionality + initializeDateTimeFields(); + setExistingDateTimeValues(); + validateRateLimitingForm(); + enhanceUsageDisplay(); + + // Add tooltips for better UX + $('[data-toggle="tooltip"]').tooltip(); + + // Add help text for rate limiting fields + $('input[name*="call_limit"]').each(function () { + $(this).attr( + "title", + "Use -1 for unlimited, or enter a positive number for the limit", + ); + }); }); diff --git a/apimanager/consumers/templates/consumers/detail.html b/apimanager/consumers/templates/consumers/detail.html index 0569ed9..23ccfea 100644 --- a/apimanager/consumers/templates/consumers/detail.html +++ b/apimanager/consumers/templates/consumers/detail.html @@ -12,7 +12,7 @@
-

{% trans "Params" %}

+

{% trans "Rate Limiting Configuration" %}

{% csrf_token %} {{ form.consumer_id }} @@ -23,35 +23,59 @@ {% endif %}
-
+
+ {% if form.from_date.errors %}
{{ form.from_date.errors }}
{% endif %} +
+ {{ form.from_date.label_tag }} + {{ form.from_date }} +
+
+
+ {% if form.to_date.errors %}
{{ form.to_date.errors }}
{% endif %} +
+ {{ form.to_date.label_tag }} + {{ form.to_date }} +
+
+
+ +
+
+ {% if form.per_second_call_limit.errors %}
{{ form.per_second_call_limit.errors }}
{% endif %} +
+ {{ form.per_second_call_limit.label_tag }} + {{ form.per_second_call_limit }} +
+
+
{% if form.per_minute_call_limit.errors %}
{{ form.per_minute_call_limit.errors }}
{% endif %}
{{ form.per_minute_call_limit.label_tag }} {{ form.per_minute_call_limit }}
-
+
{% if form.per_hour_call_limit.errors %}
{{ form.per_hour_call_limit.errors }}
{% endif %}
{{ form.per_hour_call_limit.label_tag }} {{ form.per_hour_call_limit }}
-
+
{% if form.per_day_call_limit.errors %}
{{ form.per_day_call_limit.errors }}
{% endif %}
{{ form.per_day_call_limit.label_tag }} {{ form.per_day_call_limit }}
-
+
{% if form.per_week_call_limit.errors %}
{{ form.per_week_call_limit.errors }}
{% endif %}
{{ form.per_week_call_limit.label_tag }} {{ form.per_week_call_limit }}
-
+
{% if form.per_month_call_limit.errors %}
{{ form.per_month_call_limit.errors }}
{% endif %}
{{ form.per_month_call_limit.label_tag }} @@ -60,11 +84,84 @@
- - + + + {% if consumer.created_at %} +
+
+
+ + +
+
+
+
+ + +
+
+
+ {% endif %}
+ + {% if consumer.current_state %} +
+
+

{% trans "Current Usage" %} + +

+
+
+ {% if consumer.current_state.per_hour %} +
+ {% if consumer.current_state.per_second %} +
+ {% trans "Per Second" %}
+ {{ consumer.current_state.per_second.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_second.reset_in_seconds }} seconds +
+ {% endif %} + {% if consumer.current_state.per_minute %} +
+ {% trans "Per Minute" %}
+ {{ consumer.current_state.per_minute.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_minute.reset_in_seconds }} seconds +
+ {% endif %} +
+ {% trans "Per Hour" %}
+ {{ consumer.current_state.per_hour.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_hour.reset_in_seconds }} seconds +
+
+ {% trans "Per Day" %}
+ {{ consumer.current_state.per_day.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_day.reset_in_seconds }} seconds +
+
+ {% trans "Per Week" %}
+ {{ consumer.current_state.per_week.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_week.reset_in_seconds }} seconds +
+
+ {% trans "Per Month" %}
+ {{ consumer.current_state.per_month.calls_made }} calls made
+ Resets in {{ consumer.current_state.per_month.reset_in_seconds }} seconds +
+
+ {% endif %} + +
+
+
+
+ {% endif %}
@@ -122,51 +219,16 @@
-
-
-
- {% trans "Redirect URL" %}
- {{ consumer.redirect_url }} -
-
-
-
- {% trans "Per minute call limit" %}
- {{ consumer.per_minute_call_limit }} -
-
-
- -
-
-
- {% trans "Per hour call limit" %}
- {{ consumer.per_hour_call_limit }} -
-
-
-
- {% trans "Per day call limit" %}
- {{ consumer.per_day_call_limit }} -
-
-
+
+
+
+ {% trans "Redirect URL" %}
+ {{ consumer.redirect_url }} +
+
+
-
-
-
- {% trans "Per week call limit" %}
- {{ consumer.per_week_call_limit }} -
-
-
-
- {% trans "Per month call limit" %}
- {{ consumer.per_month_call_limit }} -
-
-
@@ -204,11 +266,208 @@ {% endblock %} {% block extrajs %} -{% comment %} -{% endcomment %} {% endblock extrajs %} diff --git a/apimanager/consumers/urls.py b/apimanager/consumers/urls.py index e79395b..b18fcd4 100644 --- a/apimanager/consumers/urls.py +++ b/apimanager/consumers/urls.py @@ -5,7 +5,7 @@ URLs for consumers app from django.urls import re_path -from .views import IndexView, DetailView, EnableView, DisableView +from .views import IndexView, DetailView, EnableView, DisableView, UsageDataAjaxView urlpatterns = [ re_path(r'^$', @@ -20,4 +20,7 @@ urlpatterns = [ re_path(r'^(?P[0-9a-z\-]+)/disable$', DisableView.as_view(), name='consumers-disable'), + re_path(r'^(?P[0-9a-z\-]+)/usage-data$', + UsageDataAjaxView.as_view(), + name='consumers-usage-data'), ] diff --git a/apimanager/consumers/views.py b/apimanager/consumers/views.py index 9e43f7e..dcadb7f 100644 --- a/apimanager/consumers/views.py +++ b/apimanager/consumers/views.py @@ -4,12 +4,15 @@ Views of consumers app """ from datetime import datetime +import datetime as dt_module +import json from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse from django.views.generic import TemplateView, RedirectView, FormView +from django.http import JsonResponse from obp.api import API, APIError from base.filters import BaseFilter, FilterTime @@ -110,6 +113,44 @@ class DetailView(LoginRequiredMixin, FormView): def get_form(self, *args, **kwargs): form = super(DetailView, self).get_form(*args, **kwargs) form.fields['consumer_id'].initial = self.kwargs['consumer_id'] + + # Get call limits data to populate form + api = API(self.request.session.get('obp')) + try: + call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id']) + call_limits = api.get(call_limits_urlpath) + + if not ('code' in call_limits and call_limits['code'] >= 400): + # Populate form with existing rate limiting data + if 'from_date' in call_limits and call_limits['from_date']: + try: + from_date_str = call_limits['from_date'].replace('Z', '') + # Parse and ensure no timezone info for form field + dt = datetime.fromisoformat(from_date_str) + if dt.tzinfo: + dt = dt.replace(tzinfo=None) + form.fields['from_date'].initial = dt + except: + pass + if 'to_date' in call_limits and call_limits['to_date']: + try: + to_date_str = call_limits['to_date'].replace('Z', '') + # Parse and ensure no timezone info for form field + dt = datetime.fromisoformat(to_date_str) + if dt.tzinfo: + dt = dt.replace(tzinfo=None) + form.fields['to_date'].initial = dt + except: + pass + form.fields['per_second_call_limit'].initial = call_limits.get('per_second_call_limit', '-1') + form.fields['per_minute_call_limit'].initial = call_limits.get('per_minute_call_limit', '-1') + form.fields['per_hour_call_limit'].initial = call_limits.get('per_hour_call_limit', '-1') + form.fields['per_day_call_limit'].initial = call_limits.get('per_day_call_limit', '-1') + form.fields['per_week_call_limit'].initial = call_limits.get('per_week_call_limit', '-1') + form.fields['per_month_call_limit'].initial = call_limits.get('per_month_call_limit', '-1') + except: + pass + return form def form_valid(self, form): @@ -121,15 +162,33 @@ class DetailView(LoginRequiredMixin, FormView): if api_consumers_form.is_valid(): data = api_consumers_form.cleaned_data - urlpath = '/management/consumers/{}/consumer/calls_limit'.format(data['consumer_id']) + urlpath = '/management/consumers/{}/consumer/call-limits'.format(data['consumer_id']) + + # Helper function to format datetime to UTC + def format_datetime_utc(dt): + if not dt: + return "2024-01-01T00:00:00Z" + # Convert to UTC and format as required by API + if dt.tzinfo: + dt = dt.astimezone(dt_module.timezone.utc).replace(tzinfo=None) + return dt.strftime('%Y-%m-%dT%H:%M:%SZ') payload = { - 'per_minute_call_limit': data['per_minute_call_limit'], - 'per_hour_call_limit': data['per_hour_call_limit'], - 'per_day_call_limit': data['per_day_call_limit'], - 'per_week_call_limit': data['per_week_call_limit'], - 'per_month_call_limit': data['per_month_call_limit'] + 'from_date': format_datetime_utc(data['from_date']), + 'to_date': format_datetime_utc(data['to_date']), + 'per_second_call_limit': str(data['per_second_call_limit']) if data['per_second_call_limit'] is not None else "-1", + 'per_minute_call_limit': str(data['per_minute_call_limit']) if data['per_minute_call_limit'] is not None else "-1", + 'per_hour_call_limit': str(data['per_hour_call_limit']) if data['per_hour_call_limit'] is not None else "-1", + 'per_day_call_limit': str(data['per_day_call_limit']) if data['per_day_call_limit'] is not None else "-1", + 'per_week_call_limit': str(data['per_week_call_limit']) if data['per_week_call_limit'] is not None else "-1", + 'per_month_call_limit': str(data['per_month_call_limit']) if data['per_month_call_limit'] is not None else "-1" } + + response = self.api.put(urlpath, payload) + if 'code' in response and response['code'] >= 400: + messages.error(self.request, response['message']) + return super(DetailView, self).form_invalid(api_consumers_form) + except APIError as err: messages.error(self.request, err) return super(DetailView, self).form_invalid(api_consumers_form) @@ -137,31 +196,72 @@ class DetailView(LoginRequiredMixin, FormView): messages.error(self.request, "{}".format(err)) return super(DetailView, self).form_invalid(api_consumers_form) - msg = 'calls limit of consumer {} has been updated successfully.'.format( + msg = 'Rate limits for consumer {} have been updated successfully.'.format( data['consumer_id']) messages.success(self.request, msg) self.success_url = self.request.path return super(DetailView, self).form_valid(api_consumers_form) + def get(self, request, *args, **kwargs): + # Check if this is an AJAX request for usage data + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return self.get_usage_data_ajax() + return super(DetailView, self).get(request, *args, **kwargs) + + def get_usage_data_ajax(self): + """Return usage data as JSON for AJAX refresh""" + api = API(self.request.session.get('obp')) + try: + call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id']) + call_limits = api.get(call_limits_urlpath) + + if 'code' in call_limits and call_limits['code'] >= 400: + return JsonResponse({'error': call_limits['message']}, status=400) + + return JsonResponse(call_limits) + except APIError as err: + return JsonResponse({'error': str(err)}, status=500) + except Exception as err: + return JsonResponse({'error': str(err)}, status=500) + def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) api = API(self.request.session.get('obp')) + consumer = {} + call_limits = {} + try: urlpath = '/management/consumers/{}'.format(self.kwargs['consumer_id']) consumer = api.get(urlpath) - consumer['created'] = datetime.strptime( - consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS ) - - call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id']) - consumer_call_limtis = api.get(call_limits_urlpath) - if 'code' in consumer_call_limtis and consumer_call_limtis['code'] >= 400: - messages.error(self.request, "{}".format(consumer_call_limtis['message'])) + if 'code' in consumer and consumer['code'] >= 400: + messages.error(self.request, consumer['message']) + consumer = {} else: - consumer['per_minute_call_limit'] = consumer_call_limtis['per_minute_call_limit'] - consumer['per_hour_call_limit'] = consumer_call_limtis['per_hour_call_limit'] - consumer['per_day_call_limit'] = consumer_call_limtis['per_day_call_limit'] - consumer['per_week_call_limit'] = consumer_call_limtis['per_week_call_limit'] - consumer['per_month_call_limit'] = consumer_call_limtis['per_month_call_limit'] + consumer['created'] = datetime.strptime( + consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS ) + + # Get call limits using the correct API endpoint + call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id']) + call_limits = api.get(call_limits_urlpath) + + if 'code' in call_limits and call_limits['code'] >= 400: + messages.error(self.request, "{}".format(call_limits['message'])) + call_limits = {} + else: + # Merge call limits data into consumer object + consumer.update({ + 'from_date': call_limits.get('from_date', ''), + 'to_date': call_limits.get('to_date', ''), + 'per_second_call_limit': call_limits.get('per_second_call_limit', '-1'), + 'per_minute_call_limit': call_limits.get('per_minute_call_limit', '-1'), + 'per_hour_call_limit': call_limits.get('per_hour_call_limit', '-1'), + 'per_day_call_limit': call_limits.get('per_day_call_limit', '-1'), + 'per_week_call_limit': call_limits.get('per_week_call_limit', '-1'), + 'per_month_call_limit': call_limits.get('per_month_call_limit', '-1'), + 'current_state': call_limits.get('current_state', {}), + 'created_at': call_limits.get('created_at', ''), + 'updated_at': call_limits.get('updated_at', ''), + }) except APIError as err: messages.error(self.request, err) @@ -169,11 +269,31 @@ class DetailView(LoginRequiredMixin, FormView): messages.error(self.request, "{}".format(err)) finally: context.update({ - 'consumer': consumer + 'consumer': consumer, + 'call_limits': call_limits }) return context +class UsageDataAjaxView(LoginRequiredMixin, TemplateView): + """AJAX view to return usage data for real-time updates""" + + def get(self, request, *args, **kwargs): + api = API(self.request.session.get('obp')) + try: + call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id']) + call_limits = api.get(call_limits_urlpath) + + if 'code' in call_limits and call_limits['code'] >= 400: + return JsonResponse({'error': call_limits['message']}, status=400) + + return JsonResponse(call_limits) + except APIError as err: + return JsonResponse({'error': str(err)}, status=500) + except Exception as err: + return JsonResponse({'error': str(err)}, status=500) + + class EnableDisableView(LoginRequiredMixin, RedirectView): """View to enable or disable a consumer""" enabled = False diff --git a/apimanager/obp/api.py b/apimanager/obp/api.py index e259f8f..a72ed8e 100644 --- a/apimanager/obp/api.py +++ b/apimanager/obp/api.py @@ -43,7 +43,7 @@ class API(object): self.start_session(session_data) self.session_data = session_data - def call(self, method='GET', url='', payload=None, version=settings.API_VERSION['v500']): + def call(self, method='GET', url='', payload=None, version=settings.API_VERSION['v510']): """Workhorse which actually calls the API""" log(logging.INFO, '{} {}'.format(method, url)) if payload: @@ -64,7 +64,7 @@ class API(object): response.execution_time = elapsed return response - def get(self, urlpath='', version=settings.API_VERSION['v500']): + def get(self, urlpath='', version=settings.API_VERSION['v510']): """ Gets data from the API @@ -77,7 +77,7 @@ class API(object): else: return response - def delete(self, urlpath, version=settings.API_VERSION['v500']): + def delete(self, urlpath, version=settings.API_VERSION['v510']): """ Deletes data from the API @@ -87,7 +87,7 @@ class API(object): response = self.call('DELETE', url) return self.handle_response(response) - def post(self, urlpath, payload, version=settings.API_VERSION['v500']): + def post(self, urlpath, payload, version=settings.API_VERSION['v510']): """ Posts data to given urlpath with given payload @@ -97,7 +97,7 @@ class API(object): response = self.call('POST', url, payload) return self.handle_response(response) - def put(self, urlpath, payload, version=settings.API_VERSION['v500']): + def put(self, urlpath, payload, version=settings.API_VERSION['v510']): """ Puts data on given urlpath with given payload @@ -175,4 +175,4 @@ class API(object): result = self.get('/users') for user in result['users']: choices.append((user['user_id'], user['username'])) - return choices \ No newline at end of file + return choices