mirror of
https://github.com/OpenBankProject/API-Manager.git
synced 2026-02-06 14:16:46 +00:00
feature/CRUD for Rate Limiting
This commit is contained in:
parent
2cec411e4a
commit
5d918eac98
@ -182,3 +182,169 @@
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
/* Rate Limiting CRUD Interface Styles */
|
||||
#rateLimitForm {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#rateLimitForm .panel-heading {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
#rateLimitForm .panel-title {
|
||||
color: #495057;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#addRateLimitBtn {
|
||||
background-color: #28a745;
|
||||
border-color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#addRateLimitBtn:hover {
|
||||
background-color: #218838;
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
/* Rate limits table styling */
|
||||
#rateLimitsList .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#rateLimitsList .table th {
|
||||
background-color: #f8f9fa;
|
||||
color: #495057;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#rateLimitsList .table td {
|
||||
font-size: 13px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Action buttons styling */
|
||||
#rateLimitsList .btn-sm {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#rateLimitsList .btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
#rateLimitsList .btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
#rateLimitsList .btn-danger {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
#rateLimitsList .btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
}
|
||||
|
||||
/* Form styling */
|
||||
#rateLimitFormElement .form-group label {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#rateLimitFormElement .form-control {
|
||||
font-size: 13px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#rateLimitFormElement .btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* Empty state styling */
|
||||
.alert-info {
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.alert-info .glyphicon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* Responsive table */
|
||||
@media (max-width: 1200px) {
|
||||
#rateLimitsList .table-responsive {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#rateLimitsList .table th,
|
||||
#rateLimitsList .table td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
#rateLimitsList .btn-sm {
|
||||
font-size: 10px;
|
||||
padding: 3px 6px;
|
||||
margin: 1px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#rateLimitForm .col-xs-6 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#rateLimitsList .table {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#addRateLimitBtn {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for form show/hide */
|
||||
#rateLimitForm {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Highlight new/updated rows */
|
||||
.table tr.highlight {
|
||||
background-color: #d4edda;
|
||||
transition: background-color 2s ease-out;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.btn[disabled] {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Panel improvements */
|
||||
.panel-default > .panel-heading {
|
||||
background-image: none;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.panel-default {
|
||||
border-color: #dee2e6;
|
||||
box-shadow:
|
||||
0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
@ -2,8 +2,8 @@ $(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");
|
||||
var fromDateField = $("#from_date");
|
||||
var toDateField = $("#to_date");
|
||||
|
||||
// If fields are empty, set default values
|
||||
if (!fromDateField.val()) {
|
||||
@ -27,33 +27,35 @@ $(document).ready(function ($) {
|
||||
var toDate = $("[data-to-date]").data("to-date");
|
||||
|
||||
if (fromDate && fromDate !== "1099-12-31T23:00:00Z") {
|
||||
$("#id_from_date").val(convertISOToLocalDateTime(fromDate));
|
||||
$("#from_date").val(convertISOToLocalDateTime(fromDate));
|
||||
}
|
||||
if (toDate && toDate !== "1099-12-31T23:00:00Z") {
|
||||
$("#id_to_date").val(convertISOToLocalDateTime(toDate));
|
||||
$("#to_date").val(convertISOToLocalDateTime(toDate));
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateRateLimitingForm() {
|
||||
$("form").on("submit", function (e) {
|
||||
$("#rateLimitFormElement").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;
|
||||
}
|
||||
});
|
||||
$(this)
|
||||
.find('input[type="number"]')
|
||||
.each(function () {
|
||||
var value = parseInt($(this).val());
|
||||
if (isNaN(value) || 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());
|
||||
var fromDate = new Date($("#from_date").val());
|
||||
var toDate = new Date($("#to_date").val());
|
||||
|
||||
if (fromDate && toDate && fromDate > toDate) {
|
||||
hasError = true;
|
||||
@ -65,6 +67,52 @@ $(document).ready(function ($) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle form submission via AJAX
|
||||
e.preventDefault();
|
||||
submitRateLimitForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Submit rate limit form via AJAX
|
||||
function submitRateLimitForm() {
|
||||
var form = $("#rateLimitFormElement");
|
||||
var formData = new FormData(form[0]);
|
||||
var submitBtn = $("#submitBtn");
|
||||
var originalText = submitBtn.text();
|
||||
|
||||
// Disable submit button and show loading
|
||||
submitBtn.prop("disabled", true).text("Saving...");
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
"X-CSRFToken": $("[name=csrfmiddlewaretoken]").val(),
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
// Hide form and reload page to show updated data
|
||||
hideRateLimitForm();
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert("Error: " + (response.error || "Unknown error occurred"));
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
var errorMessage = "Error saving rate limit";
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
errorMessage = xhr.responseJSON.error;
|
||||
}
|
||||
alert(errorMessage);
|
||||
},
|
||||
complete: function () {
|
||||
// Re-enable submit button
|
||||
submitBtn.prop("disabled", false).text(originalText);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -98,3 +146,11 @@ $(document).ready(function ($) {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Global functions are now defined inline in the template
|
||||
// This file now only contains form validation and initialization
|
||||
|
||||
// Refresh rate limits list
|
||||
function refreshRateLimits() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load humanize static %}
|
||||
{% load i18n %}
|
||||
{% load consumer_extras %}
|
||||
|
||||
{% block page_title %}{{ block.super }} / Consumer {{ consumer.app_name }}{% endblock page_title %}
|
||||
|
||||
@ -13,96 +14,169 @@
|
||||
<div class="col-xs-12">
|
||||
|
||||
<h2>{% trans "Rate Limiting Configuration" %}</h2>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.consumer_id }}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{{ form.non_field_errors }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
{% if form.from_date.errors %}<div class="alert alert-danger">{{ form.from_date.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.from_date.label_tag }}
|
||||
{{ form.from_date }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
{% if form.to_date.errors %}<div class="alert alert-danger">{{ form.to_date.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.to_date.label_tag }}
|
||||
{{ form.to_date }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add New Rate Limit Button -->
|
||||
<div class="row" style="margin-bottom: 20px;">
|
||||
<div class="col-xs-12">
|
||||
<button type="button" class="btn btn-success" id="addRateLimitBtn" onclick="showAddRateLimitForm()">
|
||||
<span class="glyphicon glyphicon-plus"></span> {% trans "Add New Rate Limit" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_second_call_limit.errors %}<div class="alert alert-danger">{{ form.per_second_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_second_call_limit.label_tag }}
|
||||
{{ form.per_second_call_limit }}
|
||||
</div>
|
||||
<!-- Add/Edit Rate Limit Form (Initially Hidden) -->
|
||||
<div id="rateLimitForm" style="display: none; margin-bottom: 30px;">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title" id="formTitle">{% trans "Add New Rate Limit" %}</h4>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_minute_call_limit.errors %}<div class="alert alert-danger">{{ form.per_minute_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_minute_call_limit.label_tag }}
|
||||
{{ form.per_minute_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_hour_call_limit.errors %}<div class="alert alert-danger">{{ form.per_hour_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_hour_call_limit.label_tag }}
|
||||
{{ form.per_hour_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_day_call_limit.errors %}<div class="alert alert-danger">{{ form.per_day_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_day_call_limit.label_tag }}
|
||||
{{ form.per_day_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_week_call_limit.errors %}<div class="alert alert-danger">{{ form.per_week_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_week_call_limit.label_tag }}
|
||||
{{ form.per_week_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
{% if form.per_month_call_limit.errors %}<div class="alert alert-danger">{{ form.per_month_call_limit.errors }}</div>{% endif %}
|
||||
<div class="form-group">
|
||||
{{ form.per_month_call_limit.label_tag }}
|
||||
{{ form.per_month_call_limit }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form id="rateLimitFormElement" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="rateLimitId" name="rate_limit_id" value="">
|
||||
<input type="hidden" name="action" id="formAction" value="create">
|
||||
|
||||
<button type="submit" class="btn btn-primary">{% trans "Update Rate Limits" %}</button>
|
||||
</form>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="from_date">{% trans "From Date" %}</label>
|
||||
<input type="datetime-local" class="form-control" id="from_date" name="from_date" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="to_date">{% trans "To Date" %}</label>
|
||||
<input type="datetime-local" class="form-control" id="to_date" name="to_date" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if consumer.created_at %}
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>{% trans "Rate Limits Created At" %}</label>
|
||||
<input type="text" class="form-control" value="{{ consumer.created_at }}" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-6">
|
||||
<div class="form-group">
|
||||
<label>{% trans "Rate Limits Updated At" %}</label>
|
||||
<input type="text" class="form-control" value="{{ consumer.updated_at }}" readonly />
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_second_call_limit">{% trans "Per Second" %}</label>
|
||||
<input type="number" class="form-control" id="per_second_call_limit" name="per_second_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_minute_call_limit">{% trans "Per Minute" %}</label>
|
||||
<input type="number" class="form-control" id="per_minute_call_limit" name="per_minute_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_hour_call_limit">{% trans "Per Hour" %}</label>
|
||||
<input type="number" class="form-control" id="per_hour_call_limit" name="per_hour_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_day_call_limit">{% trans "Per Day" %}</label>
|
||||
<input type="number" class="form-control" id="per_day_call_limit" name="per_day_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_week_call_limit">{% trans "Per Week" %}</label>
|
||||
<input type="number" class="form-control" id="per_week_call_limit" name="per_week_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-4 col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="per_month_call_limit">{% trans "Per Month" %}</label>
|
||||
<input type="number" class="form-control" id="per_month_call_limit" name="per_month_call_limit" value="-1" min="-1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn">{% trans "Save Rate Limit" %}</button>
|
||||
<button type="button" class="btn btn-default" onclick="hideRateLimitForm()">{% trans "Cancel" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rate Limits List -->
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">{% trans "Existing Rate Limits" %}</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="rateLimitsList">
|
||||
{% if call_limits and call_limits.limits %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "From Date" %}</th>
|
||||
<th>{% trans "To Date" %}</th>
|
||||
<th>{% trans "Per Second" %}</th>
|
||||
<th>{% trans "Per Minute" %}</th>
|
||||
<th>{% trans "Per Hour" %}</th>
|
||||
<th>{% trans "Per Day" %}</th>
|
||||
<th>{% trans "Per Week" %}</th>
|
||||
<th>{% trans "Per Month" %}</th>
|
||||
<th>{% trans "Created" %}</th>
|
||||
<th>{% trans "Updated" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for limit in call_limits.limits %}
|
||||
<tr>
|
||||
<td>{{ limit.from_date|parse_iso_date:"Y-m-d H:i" }}</td>
|
||||
<td>{{ limit.to_date|parse_iso_date:"Y-m-d H:i" }}</td>
|
||||
<td>{{ limit.per_second_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.per_minute_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.per_hour_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.per_day_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.per_week_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.per_month_call_limit|default:"-1" }}</td>
|
||||
<td>{{ limit.created_at|parse_iso_date:"Y-m-d H:i" }}</td>
|
||||
<td>{{ limit.updated_at|parse_iso_date:"Y-m-d H:i" }}</td>
|
||||
<td>
|
||||
{% if limit.rate_limiting_id %}
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
onclick="editRateLimit('{{ limit.rate_limiting_id }}', '{{ limit.from_date|escapejs }}', '{{ limit.to_date|escapejs }}', '{{ limit.per_second_call_limit|default:"-1" }}', '{{ limit.per_minute_call_limit|default:"-1" }}', '{{ limit.per_hour_call_limit|default:"-1" }}', '{{ limit.per_day_call_limit|default:"-1" }}', '{{ limit.per_week_call_limit|default:"-1" }}', '{{ limit.per_month_call_limit|default:"-1" }}')">
|
||||
<span class="glyphicon glyphicon-edit"></span> {% trans "Edit" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-danger"
|
||||
onclick="deleteRateLimit('{{ limit.rate_limiting_id|escapejs }}')">
|
||||
<span class="glyphicon glyphicon-trash"></span> {% trans "Delete" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<span class="text-muted">{% trans "No ID" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
{% trans "No rate limits configured for this consumer. Click 'Add New Rate Limit' to create one." %}
|
||||
<!-- Debug: Show raw API response for troubleshooting -->
|
||||
{% if call_limits %}
|
||||
<br><small><strong>Debug - Raw call_limits data:</strong><br>
|
||||
<pre style="font-size: 10px; background: #f5f5f5; padding: 10px; margin-top: 10px;">{{ call_limits }}</pre>
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -278,6 +352,118 @@
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
// Global functions for CRUD operations - attached to window for global access
|
||||
window.showAddRateLimitForm = function() {
|
||||
// Reset form for new entry
|
||||
$("#rateLimitForm").show();
|
||||
$("#formTitle").text("Add New Rate Limit");
|
||||
$("#formAction").val("create");
|
||||
$("#rateLimitId").val("");
|
||||
|
||||
// Set default values
|
||||
$("#from_date").val("2024-01-01T00:00");
|
||||
$("#to_date").val("2026-01-01T00:00");
|
||||
$("#per_second_call_limit").val("-1");
|
||||
$("#per_minute_call_limit").val("-1");
|
||||
$("#per_hour_call_limit").val("-1");
|
||||
$("#per_day_call_limit").val("-1");
|
||||
$("#per_week_call_limit").val("-1");
|
||||
$("#per_month_call_limit").val("-1");
|
||||
|
||||
$("#submitBtn").text("Create Rate Limit");
|
||||
|
||||
// Scroll to form
|
||||
$("html, body").animate({
|
||||
scrollTop: $("#rateLimitForm").offset().top - 20
|
||||
}, 500);
|
||||
};
|
||||
|
||||
window.hideRateLimitForm = function() {
|
||||
$("#rateLimitForm").hide();
|
||||
};
|
||||
|
||||
window.editRateLimit = function(rateLimitingId, fromDate, toDate, perSecond, perMinute, perHour, perDay, perWeek, perMonth) {
|
||||
// Show form for editing
|
||||
$("#rateLimitForm").show();
|
||||
$("#formTitle").text("Edit Rate Limit");
|
||||
$("#formAction").val("update");
|
||||
$("#rateLimitId").val(rateLimitingId);
|
||||
|
||||
// Convert ISO dates to datetime-local format
|
||||
function convertISOToLocal(isoString) {
|
||||
if (!isoString || isoString === "1099-12-31T23:00:00Z") return "2024-01-01T00:00";
|
||||
try {
|
||||
var date = new Date(isoString);
|
||||
var year = date.getFullYear();
|
||||
var month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
var day = String(date.getDate()).padStart(2, "0");
|
||||
var hours = String(date.getHours()).padStart(2, "0");
|
||||
var minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
return year + "-" + month + "-" + day + "T" + hours + ":" + minutes;
|
||||
} catch (e) {
|
||||
return "2024-01-01T00:00";
|
||||
}
|
||||
}
|
||||
|
||||
// Populate form fields
|
||||
$("#from_date").val(convertISOToLocal(fromDate));
|
||||
$("#to_date").val(convertISOToLocal(toDate));
|
||||
$("#per_second_call_limit").val(perSecond);
|
||||
$("#per_minute_call_limit").val(perMinute);
|
||||
$("#per_hour_call_limit").val(perHour);
|
||||
$("#per_day_call_limit").val(perDay);
|
||||
$("#per_week_call_limit").val(perWeek);
|
||||
$("#per_month_call_limit").val(perMonth);
|
||||
|
||||
$("#submitBtn").text("Update Rate Limit");
|
||||
|
||||
// Scroll to form
|
||||
$("html, body").animate({
|
||||
scrollTop: $("#rateLimitForm").offset().top - 20
|
||||
}, 500);
|
||||
};
|
||||
|
||||
window.deleteRateLimit = function(rateLimitingId) {
|
||||
console.log("Deleting rate limit with ID:", rateLimitingId);
|
||||
if (!confirm("Are you sure you want to delete this rate limit? This action cannot be undone.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create form data for delete request
|
||||
var formData = new FormData();
|
||||
formData.append("action", "delete");
|
||||
formData.append("rate_limiting_id", rateLimitingId);
|
||||
formData.append("csrfmiddlewaretoken", $("[name=csrfmiddlewaretoken]").val());
|
||||
|
||||
$.ajax({
|
||||
url: window.location.pathname,
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
success: function(response) {
|
||||
console.log("Delete response:", response);
|
||||
if (response.success) {
|
||||
// Reload page to show updated data
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert("Error: " + (response.error || "Unknown error occurred"));
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Delete error:", xhr, status, error);
|
||||
var errorMessage = "Error deleting rate limit";
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
errorMessage = xhr.responseJSON.error;
|
||||
}
|
||||
alert(errorMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Global variables for refresh functionality
|
||||
let refreshInterval = null;
|
||||
let refreshCount = 0;
|
||||
|
||||
1
apimanager/consumers/templatetags/__init__.py
Normal file
1
apimanager/consumers/templatetags/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Template tags module for consumers app
|
||||
96
apimanager/consumers/templatetags/consumer_extras.py
Normal file
96
apimanager/consumers/templatetags/consumer_extras.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""
|
||||
Custom template filters for consumers app
|
||||
"""
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
register = template.Library()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register.filter
|
||||
def parse_iso_date(date_str, format_str="Y-m-d H:i"):
|
||||
"""
|
||||
Parse ISO date string and format it for display
|
||||
Usage: {{ date_string|parse_iso_date:"Y-m-d H:i" }}
|
||||
"""
|
||||
if not date_str or date_str in ["", "null", "None", None]:
|
||||
return "N/A"
|
||||
|
||||
# Convert to string if it's not already
|
||||
if not isinstance(date_str, str):
|
||||
date_str = str(date_str)
|
||||
|
||||
# List of common date formats to try
|
||||
formats_to_try = [
|
||||
"%Y-%m-%dT%H:%M:%SZ", # 2024-01-01T12:00:00Z
|
||||
"%Y-%m-%dT%H:%M:%S", # 2024-01-01T12:00:00
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ", # 2024-01-01T12:00:00.000Z
|
||||
"%Y-%m-%dT%H:%M:%S.%f", # 2024-01-01T12:00:00.000
|
||||
"%Y-%m-%d %H:%M:%S", # 2024-01-01 12:00:00
|
||||
settings.API_DATE_FORMAT_WITH_SECONDS, # From settings
|
||||
]
|
||||
|
||||
# Try to parse with different formats
|
||||
for fmt in formats_to_try:
|
||||
try:
|
||||
parsed_date = datetime.strptime(date_str, fmt)
|
||||
# Convert Django date format to Python strftime format
|
||||
django_to_python = {
|
||||
"Y": "%Y",
|
||||
"m": "%m",
|
||||
"d": "%d",
|
||||
"H": "%H",
|
||||
"i": "%M",
|
||||
"s": "%S",
|
||||
}
|
||||
|
||||
# Simple format conversion for common cases
|
||||
python_format = format_str
|
||||
for django_fmt, python_fmt in django_to_python.items():
|
||||
python_format = python_format.replace(django_fmt, python_fmt)
|
||||
|
||||
return parsed_date.strftime(python_format)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
|
||||
# Try using fromisoformat for Python 3.7+
|
||||
try:
|
||||
# Handle timezone indicator
|
||||
clean_date_str = date_str.replace("Z", "+00:00")
|
||||
parsed_date = datetime.fromisoformat(clean_date_str.replace("Z", ""))
|
||||
|
||||
# Convert format and return
|
||||
python_format = format_str
|
||||
django_to_python = {
|
||||
"Y": "%Y",
|
||||
"m": "%m",
|
||||
"d": "%d",
|
||||
"H": "%H",
|
||||
"i": "%M",
|
||||
"s": "%S",
|
||||
}
|
||||
for django_fmt, python_fmt in django_to_python.items():
|
||||
python_format = python_format.replace(django_fmt, python_fmt)
|
||||
|
||||
return parsed_date.strftime(python_format)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
# Last resort - return the original string or N/A
|
||||
logger.warning(f"Could not parse date string: {date_str}")
|
||||
return "Invalid Date"
|
||||
|
||||
|
||||
@register.filter
|
||||
def smart_default(value, default_value="N/A"):
|
||||
"""
|
||||
Smart default filter that handles various empty/null cases
|
||||
Usage: {{ value|smart_default:"Default Value" }}
|
||||
"""
|
||||
if value is None or value == "" or value == "null" or value == "None":
|
||||
return default_value
|
||||
return value
|
||||
@ -12,7 +12,7 @@ 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 django.http import JsonResponse, HttpResponseRedirect
|
||||
|
||||
from obp.api import API, APIError
|
||||
from base.filters import BaseFilter, FilterTime
|
||||
@ -22,32 +22,36 @@ from .forms import ApiConsumersForm
|
||||
|
||||
class FilterAppType(BaseFilter):
|
||||
"""Filter consumers by application type"""
|
||||
filter_type = 'app_type'
|
||||
|
||||
filter_type = "app_type"
|
||||
|
||||
def _apply(self, data, filter_value):
|
||||
filtered = [x for x in data if x['app_type'] == filter_value]
|
||||
filtered = [x for x in data if x["app_type"] == filter_value]
|
||||
return filtered
|
||||
|
||||
|
||||
class FilterEnabled(BaseFilter):
|
||||
"""Filter consumers by enabled state"""
|
||||
filter_type = 'enabled'
|
||||
|
||||
filter_type = "enabled"
|
||||
|
||||
def _apply(self, data, filter_value):
|
||||
enabled = filter_value in ['true']
|
||||
filtered = [x for x in data if x['enabled'] == enabled]
|
||||
enabled = filter_value in ["true"]
|
||||
filtered = [x for x in data if x["enabled"] == enabled]
|
||||
return filtered
|
||||
|
||||
|
||||
class IndexView(LoginRequiredMixin, TemplateView):
|
||||
"""Index view for consumers"""
|
||||
|
||||
template_name = "consumers/index.html"
|
||||
|
||||
def scrub(self, consumers):
|
||||
"""Scrubs data in the given consumers to adher to certain formats"""
|
||||
for consumer in consumers:
|
||||
consumer['created'] = datetime.strptime(
|
||||
consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS )
|
||||
consumer["created"] = datetime.strptime(
|
||||
consumer["created"], settings.API_DATE_FORMAT_WITH_SECONDS
|
||||
)
|
||||
return consumers
|
||||
|
||||
def compile_statistics(self, consumers):
|
||||
@ -55,46 +59,50 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||
unique_developer_email = {}
|
||||
unique_name = {}
|
||||
for consumer in consumers:
|
||||
unique_developer_email[consumer['developer_email']] = True
|
||||
unique_name[consumer['app_name']] = True
|
||||
unique_developer_email[consumer["developer_email"]] = True
|
||||
unique_name[consumer["app_name"]] = True
|
||||
unique_developer_email = unique_developer_email.keys()
|
||||
unique_name = unique_name.keys()
|
||||
statistics = {
|
||||
'consumers_num': len(consumers),
|
||||
'unique_developer_email_num': len(unique_developer_email),
|
||||
'unique_name_num': len(unique_name),
|
||||
"consumers_num": len(consumers),
|
||||
"unique_developer_email_num": len(unique_developer_email),
|
||||
"unique_name_num": len(unique_name),
|
||||
}
|
||||
return statistics
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(IndexView, self).get_context_data(**kwargs)
|
||||
consumers = []
|
||||
sorted_consumers=[]
|
||||
api = API(self.request.session.get('obp'))
|
||||
sorted_consumers = []
|
||||
api = API(self.request.session.get("obp"))
|
||||
try:
|
||||
limit = self.request.GET.get('limit', 50)
|
||||
offset = self.request.GET.get('offset', 0)
|
||||
urlpath = '/management/consumers?limit={}&offset={}'.format(limit, offset)
|
||||
limit = self.request.GET.get("limit", 50)
|
||||
offset = self.request.GET.get("offset", 0)
|
||||
urlpath = "/management/consumers?limit={}&offset={}".format(limit, offset)
|
||||
consumers = api.get(urlpath)
|
||||
if 'code' in consumers and consumers['code'] >= 400:
|
||||
messages.error(self.request, consumers['message'])
|
||||
if "code" in consumers and consumers["code"] >= 400:
|
||||
messages.error(self.request, consumers["message"])
|
||||
else:
|
||||
consumers = FilterEnabled(context, self.request.GET)\
|
||||
.apply(consumers['consumers'])
|
||||
consumers = FilterAppType(context, self.request.GET)\
|
||||
.apply(consumers)
|
||||
consumers = FilterTime(context, self.request.GET, 'created')\
|
||||
.apply(consumers)
|
||||
consumers = FilterEnabled(context, self.request.GET).apply(
|
||||
consumers["consumers"]
|
||||
)
|
||||
consumers = FilterAppType(context, self.request.GET).apply(consumers)
|
||||
consumers = FilterTime(context, self.request.GET, "created").apply(
|
||||
consumers
|
||||
)
|
||||
consumers = self.scrub(consumers)
|
||||
sorted_consumers = sorted(
|
||||
consumers, key=lambda consumer: consumer['created'], reverse=True)
|
||||
consumers, key=lambda consumer: consumer["created"], reverse=True
|
||||
)
|
||||
|
||||
context.update({
|
||||
'consumers': sorted_consumers,
|
||||
'limit': limit,
|
||||
'offset': offset,
|
||||
'statistics': self.compile_statistics(consumers),
|
||||
})
|
||||
context.update(
|
||||
{
|
||||
"consumers": sorted_consumers,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"statistics": self.compile_statistics(consumers),
|
||||
}
|
||||
)
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
|
||||
@ -103,66 +111,256 @@ class IndexView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
class DetailView(LoginRequiredMixin, FormView):
|
||||
"""Detail view for a consumer"""
|
||||
|
||||
form_class = ApiConsumersForm
|
||||
template_name = "consumers/detail.html"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.api = API(request.session.get('obp'))
|
||||
self.api = API(request.session.get("obp"))
|
||||
return super(DetailView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_form(self, *args, **kwargs):
|
||||
form = super(DetailView, self).get_form(*args, **kwargs)
|
||||
form.fields['consumer_id'].initial = self.kwargs['consumer_id']
|
||||
form.fields["consumer_id"].initial = self.kwargs["consumer_id"]
|
||||
|
||||
# Get call limits data to populate form
|
||||
api = API(self.request.session.get('obp'))
|
||||
api = API(self.request.session.get("obp"))
|
||||
try:
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
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):
|
||||
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']:
|
||||
if "from_date" in call_limits and call_limits["from_date"]:
|
||||
try:
|
||||
from_date_str = call_limits['from_date'].replace('Z', '')
|
||||
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
|
||||
form.fields["from_date"].initial = dt
|
||||
except:
|
||||
pass
|
||||
if 'to_date' in call_limits and call_limits['to_date']:
|
||||
if "to_date" in call_limits and call_limits["to_date"]:
|
||||
try:
|
||||
to_date_str = call_limits['to_date'].replace('Z', '')
|
||||
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
|
||||
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')
|
||||
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):
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Handle POST requests for rate limit CRUD operations"""
|
||||
action = request.POST.get("action")
|
||||
|
||||
"""Put limits data to API"""
|
||||
# Check if this is an AJAX request
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest" or action in [
|
||||
"create",
|
||||
"update",
|
||||
"delete",
|
||||
]:
|
||||
if action == "create":
|
||||
return self.create_rate_limit(request)
|
||||
elif action == "update":
|
||||
return self.update_rate_limit(request)
|
||||
elif action == "delete":
|
||||
return self.delete_rate_limit(request)
|
||||
|
||||
# Fallback to original form handling for compatibility
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
return self.form_valid_legacy(request, form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def create_rate_limit(self, request):
|
||||
"""Create a new rate limit using v6.0.0 POST API"""
|
||||
try:
|
||||
data = ''
|
||||
api_consumers_form = ApiConsumersForm(self.request.POST)
|
||||
if api_consumers_form.is_valid():
|
||||
data = api_consumers_form.cleaned_data
|
||||
consumer_id = self.kwargs["consumer_id"]
|
||||
|
||||
urlpath = '/management/consumers/{}/consumer/call-limits'.format(data['consumer_id'])
|
||||
# Helper function to format datetime to UTC
|
||||
def format_datetime_utc(dt_str):
|
||||
if not dt_str:
|
||||
return "2024-01-01T00:00:00Z"
|
||||
try:
|
||||
dt = datetime.fromisoformat(dt_str)
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
except:
|
||||
return "2024-01-01T00:00:00Z"
|
||||
|
||||
payload = {
|
||||
"from_date": format_datetime_utc(request.POST.get("from_date")),
|
||||
"to_date": format_datetime_utc(request.POST.get("to_date")),
|
||||
"per_second_call_limit": str(
|
||||
request.POST.get("per_second_call_limit", "-1")
|
||||
),
|
||||
"per_minute_call_limit": str(
|
||||
request.POST.get("per_minute_call_limit", "-1")
|
||||
),
|
||||
"per_hour_call_limit": str(
|
||||
request.POST.get("per_hour_call_limit", "-1")
|
||||
),
|
||||
"per_day_call_limit": str(request.POST.get("per_day_call_limit", "-1")),
|
||||
"per_week_call_limit": str(
|
||||
request.POST.get("per_week_call_limit", "-1")
|
||||
),
|
||||
"per_month_call_limit": str(
|
||||
request.POST.get("per_month_call_limit", "-1")
|
||||
),
|
||||
}
|
||||
|
||||
# Use v6.0.0 API for creating rate limits
|
||||
urlpath = "/management/consumers/{}/consumer/call-limits".format(
|
||||
consumer_id
|
||||
)
|
||||
response = self.api.post(
|
||||
urlpath, payload, version=settings.API_VERSION["v600"]
|
||||
)
|
||||
|
||||
if "code" in response and response["code"] >= 400:
|
||||
messages.error(request, response["message"])
|
||||
else:
|
||||
messages.success(request, "Rate limit created successfully.")
|
||||
|
||||
except APIError as err:
|
||||
messages.error(request, str(err))
|
||||
except Exception as err:
|
||||
messages.error(request, "Error creating rate limit: {}".format(err))
|
||||
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
return JsonResponse({"success": True, "redirect": request.path})
|
||||
else:
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
def update_rate_limit(self, request):
|
||||
"""Update existing rate limit using v5.1.0 PUT API"""
|
||||
try:
|
||||
consumer_id = self.kwargs["consumer_id"]
|
||||
|
||||
# Helper function to format datetime to UTC
|
||||
def format_datetime_utc(dt_str):
|
||||
if not dt_str:
|
||||
return "2024-01-01T00:00:00Z"
|
||||
try:
|
||||
dt = datetime.fromisoformat(dt_str)
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
except:
|
||||
return "2024-01-01T00:00:00Z"
|
||||
|
||||
payload = {
|
||||
"from_date": format_datetime_utc(request.POST.get("from_date")),
|
||||
"to_date": format_datetime_utc(request.POST.get("to_date")),
|
||||
"per_second_call_limit": str(
|
||||
request.POST.get("per_second_call_limit", "-1")
|
||||
),
|
||||
"per_minute_call_limit": str(
|
||||
request.POST.get("per_minute_call_limit", "-1")
|
||||
),
|
||||
"per_hour_call_limit": str(
|
||||
request.POST.get("per_hour_call_limit", "-1")
|
||||
),
|
||||
"per_day_call_limit": str(request.POST.get("per_day_call_limit", "-1")),
|
||||
"per_week_call_limit": str(
|
||||
request.POST.get("per_week_call_limit", "-1")
|
||||
),
|
||||
"per_month_call_limit": str(
|
||||
request.POST.get("per_month_call_limit", "-1")
|
||||
),
|
||||
}
|
||||
|
||||
# Use v5.1.0 API for updating rate limits
|
||||
urlpath = "/management/consumers/{}/consumer/call-limits".format(
|
||||
consumer_id
|
||||
)
|
||||
response = self.api.put(
|
||||
urlpath, payload, version=settings.API_VERSION["v510"]
|
||||
)
|
||||
|
||||
if "code" in response and response["code"] >= 400:
|
||||
messages.error(request, response["message"])
|
||||
else:
|
||||
messages.success(request, "Rate limit updated successfully.")
|
||||
|
||||
except APIError as err:
|
||||
messages.error(request, str(err))
|
||||
except Exception as err:
|
||||
messages.error(request, "Error updating rate limit: {}".format(err))
|
||||
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
return JsonResponse({"success": True, "redirect": request.path})
|
||||
else:
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
def delete_rate_limit(self, request):
|
||||
"""Delete a rate limit using v6.0.0 DELETE API"""
|
||||
try:
|
||||
consumer_id = self.kwargs["consumer_id"]
|
||||
rate_limiting_id = request.POST.get("rate_limiting_id")
|
||||
|
||||
if not rate_limiting_id:
|
||||
messages.error(request, "Rate limiting ID is required for deletion.")
|
||||
return JsonResponse(
|
||||
{"success": False, "error": "Missing rate limiting ID"}
|
||||
)
|
||||
|
||||
# Use v6.0.0 API for deleting rate limits
|
||||
urlpath = "/management/consumers/{}/consumer/call-limits/{}".format(
|
||||
consumer_id, rate_limiting_id
|
||||
)
|
||||
response = self.api.delete(urlpath, version=settings.API_VERSION["v600"])
|
||||
|
||||
if "code" in response and response["code"] >= 400:
|
||||
messages.error(request, response["message"])
|
||||
else:
|
||||
messages.success(request, "Rate limit deleted successfully.")
|
||||
|
||||
except APIError as err:
|
||||
messages.error(request, str(err))
|
||||
except Exception as err:
|
||||
messages.error(request, "Error deleting rate limit: {}".format(err))
|
||||
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
return JsonResponse({"success": True, "redirect": request.path})
|
||||
else:
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
def form_valid_legacy(self, request, form):
|
||||
"""Legacy form handling for backwards compatibility"""
|
||||
try:
|
||||
data = form.cleaned_data
|
||||
|
||||
urlpath = "/management/consumers/{}/consumer/call-limits".format(
|
||||
data["consumer_id"]
|
||||
)
|
||||
|
||||
# Helper function to format datetime to UTC
|
||||
def format_datetime_utc(dt):
|
||||
@ -171,107 +369,172 @@ class DetailView(LoginRequiredMixin, FormView):
|
||||
# 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')
|
||||
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
payload = {
|
||||
'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"
|
||||
"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)
|
||||
response = self.api.put(
|
||||
urlpath, payload, version=settings.API_VERSION["v510"]
|
||||
)
|
||||
if "code" in response and response["code"] >= 400:
|
||||
messages.error(request, response["message"])
|
||||
return super(DetailView, self).form_invalid(form)
|
||||
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
return super(DetailView, self).form_invalid(api_consumers_form)
|
||||
messages.error(request, err)
|
||||
return super(DetailView, self).form_invalid(form)
|
||||
except Exception as err:
|
||||
messages.error(self.request, "{}".format(err))
|
||||
return super(DetailView, self).form_invalid(api_consumers_form)
|
||||
messages.error(request, "{}".format(err))
|
||||
return super(DetailView, self).form_invalid(form)
|
||||
|
||||
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)
|
||||
msg = "Rate limits for consumer {} have been updated successfully.".format(
|
||||
data["consumer_id"]
|
||||
)
|
||||
messages.success(request, msg)
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
return JsonResponse({"success": True, "redirect": request.path})
|
||||
else:
|
||||
return HttpResponseRedirect(request.path)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# Check if this is an AJAX request for usage data
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
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'))
|
||||
api = API(self.request.session.get("obp"))
|
||||
try:
|
||||
call_limits_urlpath = '/management/consumers/{}/consumer/call-limits'.format(self.kwargs['consumer_id'])
|
||||
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)
|
||||
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)
|
||||
return JsonResponse({"error": str(err)}, status=500)
|
||||
except Exception as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
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'))
|
||||
api = API(self.request.session.get("obp"))
|
||||
consumer = {}
|
||||
call_limits = {}
|
||||
|
||||
try:
|
||||
urlpath = '/management/consumers/{}'.format(self.kwargs['consumer_id'])
|
||||
urlpath = "/management/consumers/{}".format(self.kwargs["consumer_id"])
|
||||
consumer = api.get(urlpath)
|
||||
if 'code' in consumer and consumer['code'] >= 400:
|
||||
messages.error(self.request, consumer['message'])
|
||||
if "code" in consumer and consumer["code"] >= 400:
|
||||
messages.error(self.request, consumer["message"])
|
||||
consumer = {}
|
||||
else:
|
||||
consumer['created'] = datetime.strptime(
|
||||
consumer['created'], settings.API_DATE_FORMAT_WITH_SECONDS )
|
||||
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)
|
||||
call_limits_urlpath = (
|
||||
"/management/consumers/{}/consumer/call-limits".format(
|
||||
self.kwargs["consumer_id"]
|
||||
)
|
||||
)
|
||||
call_limits = api.get(
|
||||
call_limits_urlpath, version=settings.API_VERSION["v510"]
|
||||
)
|
||||
|
||||
if 'code' in call_limits and call_limits['code'] >= 400:
|
||||
messages.error(self.request, "{}".format(call_limits['message']))
|
||||
call_limits = {}
|
||||
if "code" in call_limits and call_limits["code"] >= 400:
|
||||
messages.error(self.request, "{}".format(call_limits["message"]))
|
||||
call_limits = {"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', ''),
|
||||
})
|
||||
# Handle different API response structures
|
||||
import uuid
|
||||
|
||||
# Handle case where API returns data directly instead of in 'limits' array
|
||||
if (
|
||||
"limits" not in call_limits
|
||||
and "per_second_call_limit" in call_limits
|
||||
):
|
||||
# API returned single limit object, wrap it in limits array
|
||||
if "rate_limiting_id" not in call_limits:
|
||||
call_limits["rate_limiting_id"] = str(uuid.uuid4())
|
||||
call_limits = {"limits": [call_limits]}
|
||||
elif "limits" not in call_limits:
|
||||
# No limits data found
|
||||
call_limits = {"limits": []}
|
||||
else:
|
||||
# Ensure each limit has a rate_limiting_id
|
||||
for limit in call_limits.get("limits", []):
|
||||
if (
|
||||
"rate_limiting_id" not in limit
|
||||
or not limit["rate_limiting_id"]
|
||||
):
|
||||
limit["rate_limiting_id"] = str(uuid.uuid4())
|
||||
|
||||
# For backwards compatibility, merge first limit into consumer if limits exist
|
||||
if call_limits.get("limits") and len(call_limits["limits"]) > 0:
|
||||
first_limit = call_limits["limits"][0]
|
||||
consumer.update(
|
||||
{
|
||||
"from_date": first_limit.get("from_date", ""),
|
||||
"to_date": first_limit.get("to_date", ""),
|
||||
"per_second_call_limit": first_limit.get(
|
||||
"per_second_call_limit", "-1"
|
||||
),
|
||||
"per_minute_call_limit": first_limit.get(
|
||||
"per_minute_call_limit", "-1"
|
||||
),
|
||||
"per_hour_call_limit": first_limit.get(
|
||||
"per_hour_call_limit", "-1"
|
||||
),
|
||||
"per_day_call_limit": first_limit.get(
|
||||
"per_day_call_limit", "-1"
|
||||
),
|
||||
"per_week_call_limit": first_limit.get(
|
||||
"per_week_call_limit", "-1"
|
||||
),
|
||||
"per_month_call_limit": first_limit.get(
|
||||
"per_month_call_limit", "-1"
|
||||
),
|
||||
"current_state": call_limits.get("current_state", {}),
|
||||
"created_at": first_limit.get("created_at", ""),
|
||||
"updated_at": first_limit.get("updated_at", ""),
|
||||
}
|
||||
)
|
||||
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
except Exception as err:
|
||||
messages.error(self.request, "{}".format(err))
|
||||
finally:
|
||||
context.update({
|
||||
'consumer': consumer,
|
||||
'call_limits': call_limits
|
||||
})
|
||||
context.update({"consumer": consumer, "call_limits": call_limits})
|
||||
return context
|
||||
|
||||
|
||||
@ -279,52 +542,61 @@ 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'))
|
||||
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)
|
||||
call_limits_urlpath = (
|
||||
"/management/consumers/{}/consumer/call-limits".format(
|
||||
self.kwargs["consumer_id"]
|
||||
)
|
||||
)
|
||||
call_limits = api.get(
|
||||
call_limits_urlpath, version=settings.API_VERSION["v510"]
|
||||
)
|
||||
|
||||
if 'code' in call_limits and call_limits['code'] >= 400:
|
||||
return JsonResponse({'error': call_limits['message']}, status=400)
|
||||
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)
|
||||
return JsonResponse({"error": str(err)}, status=500)
|
||||
except Exception as err:
|
||||
return JsonResponse({'error': str(err)}, status=500)
|
||||
return JsonResponse({"error": str(err)}, status=500)
|
||||
|
||||
|
||||
class EnableDisableView(LoginRequiredMixin, RedirectView):
|
||||
"""View to enable or disable a consumer"""
|
||||
|
||||
enabled = False
|
||||
success = None
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
api = API(self.request.session.get('obp'))
|
||||
api = API(self.request.session.get("obp"))
|
||||
try:
|
||||
urlpath = '/management/consumers/{}'.format(kwargs['consumer_id'])
|
||||
payload = {'enabled': self.enabled}
|
||||
urlpath = "/management/consumers/{}".format(kwargs["consumer_id"])
|
||||
payload = {"enabled": self.enabled}
|
||||
response = api.put(urlpath, payload)
|
||||
if 'code' in response and response['code'] >= 400:
|
||||
messages.error(self.request, response['message'])
|
||||
if "code" in response and response["code"] >= 400:
|
||||
messages.error(self.request, response["message"])
|
||||
else:
|
||||
messages.success(self.request, self.success)
|
||||
except APIError as err:
|
||||
messages.error(self.request, err)
|
||||
|
||||
urlpath = self.request.POST.get('next', reverse('consumers-index'))
|
||||
urlpath = self.request.POST.get("next", reverse("consumers-index"))
|
||||
query = self.request.GET.urlencode()
|
||||
redirect_url = '{}?{}'.format(urlpath, query)
|
||||
redirect_url = "{}?{}".format(urlpath, query)
|
||||
return redirect_url
|
||||
|
||||
|
||||
class EnableView(EnableDisableView):
|
||||
"""View to enable a consumer"""
|
||||
|
||||
enabled = True
|
||||
success = "Consumer has been enabled."
|
||||
|
||||
|
||||
class DisableView(EnableDisableView):
|
||||
"""View to disable a consumer"""
|
||||
|
||||
enabled = False
|
||||
success = "Consumer has been disabled."
|
||||
|
||||
Loading…
Reference in New Issue
Block a user