hospital-management/templates/core/api_management.html
2025-08-12 13:33:25 +03:00

655 lines
27 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}API Management{% endblock %}
{% block css %}
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
<link href="{% static 'assets/plugins/prismjs/themes/prism.css' %}" rel="stylesheet" />
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item active">API Management</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">API Management</h1>
<p class="text-muted">Manage API keys, endpoints, and monitor API usage</p>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createApiKeyModal">
<i class="fa fa-plus me-2"></i>Create API Key
</button>
</div>
</div>
<!-- API Statistics -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-primary bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-key fa-2x text-primary"></i>
</div>
<h5>Active API Keys</h5>
<div class="fs-24px fw-600 text-primary">{{ active_api_keys }}</div>
<div class="text-muted small">{{ total_api_keys }} total</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-info bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-exchange fa-2x text-info"></i>
</div>
<h5>API Calls Today</h5>
<div class="fs-24px fw-600 text-info">{{ api_calls_today|floatformat:0 }}</div>
<div class="text-muted small">{{ api_calls_change }}% vs yesterday</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-success bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-check-circle fa-2x text-success"></i>
</div>
<h5>Success Rate</h5>
<div class="fs-24px fw-600 text-success">{{ success_rate }}%</div>
<div class="text-muted small">Last 24 hours</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<div class="w-60px h-60px bg-warning bg-opacity-20 d-flex align-items-center justify-content-center rounded-circle mx-auto mb-3">
<i class="fa fa-clock fa-2x text-warning"></i>
</div>
<h5>Avg Response Time</h5>
<div class="fs-24px fw-600 text-warning">{{ avg_response_time }}ms</div>
<div class="text-muted small">Last hour</div>
</div>
</div>
</div>
</div>
<!-- API Keys Management -->
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title">API Keys</h4>
<div class="card-toolbar">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshApiKeys()">
<i class="fa fa-refresh"></i>
</button>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="exportApiKeys()">
<i class="fa fa-download"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped" id="apiKeysTable">
<thead>
<tr>
<th>Name</th>
<th>Key</th>
<th>Permissions</th>
<th>Usage</th>
<th>Rate Limit</th>
<th>Last Used</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for api_key in api_keys %}
<tr>
<td>
<div class="fw-bold">{{ api_key.name }}</div>
<div class="text-muted small">{{ api_key.description|default:"No description" }}</div>
</td>
<td>
<div class="d-flex align-items-center">
<code class="me-2">{{ api_key.key_preview }}</code>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="copyApiKey('{{ api_key.key }}')">
<i class="fa fa-copy"></i>
</button>
</div>
</td>
<td>
{% for permission in api_key.permissions.all %}
<span class="badge bg-info me-1">{{ permission.name }}</span>
{% endfor %}
</td>
<td>
<div class="fw-bold">{{ api_key.usage_count }}</div>
<div class="text-muted small">{{ api_key.usage_today }} today</div>
</td>
<td>
{% if api_key.rate_limit %}
<div>{{ api_key.rate_limit }}/{{ api_key.rate_limit_period }}</div>
<div class="progress mt-1" style="height: 4px;">
<div class="progress-bar" style="width: {{ api_key.rate_limit_usage }}%"></div>
</div>
{% else %}
<span class="text-muted">Unlimited</span>
{% endif %}
</td>
<td>
{% if api_key.last_used %}
<div>{{ api_key.last_used|date:"M d, Y" }}</div>
<div class="text-muted small">{{ api_key.last_used|timesince }} ago</div>
{% else %}
<span class="text-muted">Never</span>
{% endif %}
</td>
<td>
{% if api_key.is_active %}
<span class="badge bg-success">Active</span>
{% else %}
<span class="badge bg-danger">Inactive</span>
{% endif %}
{% if api_key.expires_at %}
<div class="text-muted small">Expires {{ api_key.expires_at|date:"M d, Y" }}</div>
{% endif %}
</td>
<td>
<div class="btn-group">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="editApiKey('{{ api_key.id }}')">
<i class="fa fa-edit"></i>
</button>
<button type="button" class="btn btn-outline-info btn-sm" onclick="viewApiKeyUsage('{{ api_key.id }}')">
<i class="fa fa-chart-line"></i>
</button>
{% if api_key.is_active %}
<button type="button" class="btn btn-outline-warning btn-sm" onclick="deactivateApiKey('{{ api_key.id }}')">
<i class="fa fa-pause"></i>
</button>
{% else %}
<button type="button" class="btn btn-outline-success btn-sm" onclick="activateApiKey('{{ api_key.id }}')">
<i class="fa fa-play"></i>
</button>
{% endif %}
<button type="button" class="btn btn-outline-danger btn-sm" onclick="deleteApiKey('{{ api_key.id }}')">
<i class="fa fa-trash"></i>
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- API Endpoints -->
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title">Available Endpoints</h4>
<div class="card-toolbar">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="refreshEndpoints()">
<i class="fa fa-refresh"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="row">
{% for endpoint_group in api_endpoints %}
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="card-title">{{ endpoint_group.name }}</h5>
<span class="badge bg-primary">{{ endpoint_group.endpoints|length }} endpoints</span>
</div>
<div class="card-body">
{% for endpoint in endpoint_group.endpoints %}
<div class="d-flex align-items-center mb-2">
<span class="badge bg-{{ endpoint.method_color }} me-2">{{ endpoint.method }}</span>
<code class="flex-1">{{ endpoint.path }}</code>
<button type="button" class="btn btn-outline-secondary btn-sm ms-2" onclick="testEndpoint('{{ endpoint.path }}', '{{ endpoint.method }}')">
<i class="fa fa-play"></i>
</button>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- API Usage Analytics -->
<div class="card">
<div class="card-header">
<h4 class="card-title">Usage Analytics</h4>
<div class="card-toolbar">
<select class="form-select form-select-sm" onchange="updateUsageChart(this.value)">
<option value="24h">Last 24 Hours</option>
<option value="7d" selected>Last 7 Days</option>
<option value="30d">Last 30 Days</option>
</select>
</div>
</div>
<div class="card-body">
<canvas id="usageChart" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- Create API Key Modal -->
<div class="modal fade" id="createApiKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create API Key</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="createApiKeyForm">
<div class="modal-body">
{% csrf_token %}
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Permissions</label>
<div class="row">
{% for permission in available_permissions %}
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="permissions" value="{{ permission.id }}">
<label class="form-check-label">{{ permission.name }}</label>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="mb-3">
<label class="form-label">Rate Limit</label>
<div class="row">
<div class="col-md-6">
<input type="number" name="rate_limit" class="form-control" placeholder="Requests">
</div>
<div class="col-md-6">
<select name="rate_limit_period" class="form-select">
<option value="minute">per minute</option>
<option value="hour">per hour</option>
<option value="day">per day</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Expiration Date (Optional)</label>
<input type="date" name="expires_at" class="form-control">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Create API Key</button>
</div>
</form>
</div>
</div>
</div>
<!-- API Key Details Modal -->
<div class="modal fade" id="apiKeyDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">API Key Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="apiKeyDetailsContent">
<!-- Content loaded via AJAX -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- API Test Modal -->
<div class="modal fade" id="apiTestModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Test API Endpoint</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Endpoint</label>
<div class="input-group">
<span class="input-group-text" id="testMethod">GET</span>
<input type="text" class="form-control" id="testEndpoint" readonly>
</div>
</div>
<div class="mb-3">
<label class="form-label">Headers</label>
<textarea class="form-control" id="testHeaders" rows="4">Content-Type: application/json
Authorization: Bearer YOUR_API_KEY</textarea>
</div>
<div class="mb-3" id="testBodySection" style="display: none;">
<label class="form-label">Request Body</label>
<textarea class="form-control" id="testBody" rows="6">{}</textarea>
</div>
<div class="mb-3">
<button type="button" class="btn btn-primary" onclick="executeApiTest()">
<i class="fa fa-play me-2"></i>Send Request
</button>
</div>
<div id="testResponse" style="display: none;">
<label class="form-label">Response</label>
<pre><code id="testResponseContent" class="language-json"></code></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script src="{% static 'assets/plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'assets/plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
<script src="{% static 'assets/plugins/chart.js/dist/Chart.min.js' %}"></script>
<script src="{% static 'assets/plugins/prismjs/components/prism-core.min.js' %}"></script>
<script src="{% static 'assets/plugins/prismjs/components/prism-json.min.js' %}"></script>
<script>
var usageChart;
$(document).ready(function() {
// Initialize DataTable
$('#apiKeysTable').DataTable({
responsive: true,
pageLength: 10,
order: [[5, 'desc']],
columnDefs: [
{ orderable: false, targets: [7] }
]
});
// Initialize usage chart
initializeUsageChart();
// Create API Key form
$('#createApiKeyForm').submit(function(e) {
e.preventDefault();
var formData = new FormData(this);
$.post('{% url "core:create_api_key" %}', formData, function(response) {
if (response.success) {
$('#createApiKeyModal').modal('hide');
toastr.success('API key created successfully');
// Show the new API key
showNewApiKey(response.api_key);
// Refresh the table
location.reload();
} else {
toastr.error('Failed to create API key: ' + response.error);
}
}).fail(function() {
toastr.error('Failed to create API key');
});
});
});
function initializeUsageChart() {
var ctx = document.getElementById('usageChart').getContext('2d');
usageChart = new Chart(ctx, {
type: 'line',
data: {
labels: {{ usage_labels|safe }},
datasets: [{
label: 'API Calls',
data: {{ usage_data|safe }},
borderColor: 'rgb(54, 162, 235)',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.4
}, {
label: 'Errors',
data: {{ error_data|safe }},
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
position: 'top'
}
}
}
});
}
function copyApiKey(apiKey) {
navigator.clipboard.writeText(apiKey).then(function() {
toastr.success('API key copied to clipboard');
}).catch(function() {
toastr.error('Failed to copy API key');
});
}
function editApiKey(apiKeyId) {
// Load API key details and show edit modal
$.get('{% url "core:api_key_detail" 0 %}'.replace('0', apiKeyId), function(data) {
// Populate edit form with current values
showEditApiKeyModal(data);
});
}
function viewApiKeyUsage(apiKeyId) {
$.get('{% url "core:api_key_usage" 0 %}'.replace('0', apiKeyId), function(data) {
$('#apiKeyDetailsContent').html(data);
$('#apiKeyDetailsModal').modal('show');
});
}
function activateApiKey(apiKeyId) {
$.post('{% url "core:toggle_api_key" %}', {
'api_key_id': apiKeyId,
'action': 'activate',
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('API key activated');
location.reload();
} else {
toastr.error('Failed to activate API key');
}
});
}
function deactivateApiKey(apiKeyId) {
if (confirm('Deactivate this API key? It will stop working immediately.')) {
$.post('{% url "core:toggle_api_key" %}', {
'api_key_id': apiKeyId,
'action': 'deactivate',
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('API key deactivated');
location.reload();
} else {
toastr.error('Failed to deactivate API key');
}
});
}
}
function deleteApiKey(apiKeyId) {
if (confirm('Delete this API key? This action cannot be undone.')) {
$.post('{% url "core:delete_api_key" %}', {
'api_key_id': apiKeyId,
'csrfmiddlewaretoken': '{{ csrf_token }}'
}, function(response) {
if (response.success) {
toastr.success('API key deleted');
location.reload();
} else {
toastr.error('Failed to delete API key');
}
});
}
}
function testEndpoint(path, method) {
$('#testEndpoint').val(path);
$('#testMethod').text(method);
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
$('#testBodySection').show();
} else {
$('#testBodySection').hide();
}
$('#testResponse').hide();
$('#apiTestModal').modal('show');
}
function executeApiTest() {
var endpoint = $('#testEndpoint').val();
var method = $('#testMethod').text();
var headers = $('#testHeaders').val();
var body = $('#testBody').val();
// Parse headers
var headerObj = {};
headers.split('\n').forEach(function(line) {
var parts = line.split(':');
if (parts.length === 2) {
headerObj[parts[0].trim()] = parts[1].trim();
}
});
// Make the API request
$.ajax({
url: endpoint,
method: method,
headers: headerObj,
data: method !== 'GET' ? body : undefined,
success: function(response) {
$('#testResponseContent').text(JSON.stringify(response, null, 2));
Prism.highlightElement(document.getElementById('testResponseContent'));
$('#testResponse').show();
},
error: function(xhr) {
$('#testResponseContent').text(JSON.stringify({
status: xhr.status,
statusText: xhr.statusText,
response: xhr.responseText
}, null, 2));
Prism.highlightElement(document.getElementById('testResponseContent'));
$('#testResponse').show();
}
});
}
function updateUsageChart(period) {
$.get('{% url "core:api_usage_data" %}', {
'period': period
}, function(data) {
usageChart.data.labels = data.labels;
usageChart.data.datasets[0].data = data.usage_data;
usageChart.data.datasets[1].data = data.error_data;
usageChart.update();
});
}
function refreshApiKeys() {
location.reload();
}
function refreshEndpoints() {
location.reload();
}
function exportApiKeys() {
window.location.href = '{% url "core:export_api_keys" %}';
}
function showNewApiKey(apiKey) {
// Show modal with the new API key for copying
var modal = `
<div class="modal fade" id="newApiKeyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">New API Key Created</h5>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<strong>Important:</strong> Copy this API key now. You won't be able to see it again.
</div>
<div class="mb-3">
<label class="form-label">API Key</label>
<div class="input-group">
<input type="text" class="form-control" value="${apiKey}" readonly>
<button type="button" class="btn btn-outline-secondary" onclick="copyApiKey('${apiKey}')">
<i class="fa fa-copy"></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">I've copied the key</button>
</div>
</div>
</div>
</div>
`;
$('body').append(modal);
$('#newApiKeyModal').modal('show');
$('#newApiKeyModal').on('hidden.bs.modal', function() {
$(this).remove();
});
}
</script>
{% endblock %}