485 lines
20 KiB
HTML
485 lines
20 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}API Endpoints - Integration{% endblock %}
|
|
|
|
{% block css %}
|
|
<link href="{% static 'assets/plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'assets/plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- BEGIN breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'integration:dashboard' %}">Integration</a></li>
|
|
<li class="breadcrumb-item active">API Endpoints</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
API Endpoints
|
|
<small>External System Integration Points</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">API Endpoint Management</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="{% url 'integration:api_endpoint_create' %}" class="btn btn-xs btn-success me-2">
|
|
<i class="fa fa-plus"></i> Add Endpoint
|
|
</a>
|
|
<button class="btn btn-xs btn-primary me-2" onclick="testAllEndpoints()">
|
|
<i class="fa fa-play"></i> Test All
|
|
</button>
|
|
<button class="btn btn-xs btn-info me-2" data-bs-toggle="modal" data-bs-target="#bulk-actions-modal">
|
|
<i class="fa fa-cogs"></i> Bulk Actions
|
|
</button>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card border-primary">
|
|
<div class="card-body text-center">
|
|
<div class="fs-24px fw-bold text-primary">{{ total_endpoints }}</div>
|
|
<div class="small text-muted">Total Endpoints</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card border-success">
|
|
<div class="card-body text-center">
|
|
<div class="fs-24px fw-bold text-success">{{ active_endpoints }}</div>
|
|
<div class="small text-muted">Active</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card border-warning">
|
|
<div class="card-body text-center">
|
|
<div class="fs-24px fw-bold text-warning">{{ warning_endpoints }}</div>
|
|
<div class="small text-muted">Warnings</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card border-danger">
|
|
<div class="card-body text-center">
|
|
<div class="fs-24px fw-bold text-danger">{{ failed_endpoints }}</div>
|
|
<div class="small text-muted">Failed</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Filters -->
|
|
<div class="row mb-3">
|
|
<div class="col-md-12">
|
|
<div class="btn-group me-2" role="group">
|
|
<button type="button" class="btn btn-outline-primary active" onclick="filterByStatus('all')">All Status</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByStatus('ACTIVE')">Active</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByStatus('INACTIVE')">Inactive</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="filterByStatus('TESTING')">Testing</button>
|
|
</div>
|
|
|
|
<div class="btn-group me-2" role="group">
|
|
<button type="button" class="btn btn-outline-secondary active" onclick="filterByMethod('all')">All Methods</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="filterByMethod('GET')">GET</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="filterByMethod('POST')">POST</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="filterByMethod('PUT')">PUT</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="filterByMethod('DELETE')">DELETE</button>
|
|
</div>
|
|
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-info active" onclick="filterBySystem('all')">All Systems</button>
|
|
{% for system in external_systems %}
|
|
<button type="button" class="btn btn-outline-info" onclick="filterBySystem('{{ system.id }}')">{{ system.name }}</button>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API Endpoints Table -->
|
|
<div class="table-responsive">
|
|
<table id="endpoints-table" class="table table-striped table-bordered align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th width="5%">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="select-all">
|
|
</div>
|
|
</th>
|
|
<th width="20%">Name</th>
|
|
<th width="15%">System</th>
|
|
<th width="25%">URL</th>
|
|
<th width="8%">Method</th>
|
|
<th width="10%">Status</th>
|
|
<th width="10%">Last Test</th>
|
|
<th width="7%">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for endpoint in object_list %}
|
|
<tr data-status="{{ endpoint.status }}" data-method="{{ endpoint.method }}" data-system="{{ endpoint.external_system.id }}">
|
|
<td>
|
|
<div class="form-check">
|
|
<input class="form-check-input row-checkbox" type="checkbox" value="{{ endpoint.id }}">
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ endpoint.name }}</div>
|
|
<div class="small text-muted">{{ endpoint.description|truncatechars:50 }}</div>
|
|
</td>
|
|
<td>
|
|
<a href="{% url 'integration:external_system_detail' endpoint.external_system.pk %}" class="text-decoration-none">
|
|
{{ endpoint.external_system.name }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<code class="small">{{ endpoint.url|truncatechars:40 }}</code>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if endpoint.method == 'GET' %}primary{% elif endpoint.method == 'POST' %}success{% elif endpoint.method == 'PUT' %}warning{% elif endpoint.method == 'DELETE' %}danger{% else %}secondary{% endif %}">
|
|
{{ endpoint.method }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if endpoint.status == 'ACTIVE' %}success{% elif endpoint.status == 'INACTIVE' %}secondary{% elif endpoint.status == 'TESTING' %}warning{% else %}danger{% endif %}">
|
|
{{ endpoint.get_status_display }}
|
|
</span>
|
|
{% if endpoint.health_status %}
|
|
<div class="small mt-1">
|
|
<span class="badge bg-{% if endpoint.health_status == 'HEALTHY' %}success{% elif endpoint.health_status == 'WARNING' %}warning{% else %}danger{% endif %} badge-sm">
|
|
{{ endpoint.health_status }}
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if endpoint.last_tested %}
|
|
<div class="small">{{ endpoint.last_tested|date:"M d, Y" }}</div>
|
|
<div class="small text-muted">{{ endpoint.last_tested|date:"H:i" }}</div>
|
|
{% else %}
|
|
<span class="text-muted">Never</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{% url 'integration:api_endpoint_detail' endpoint.pk %}" class="btn btn-outline-primary btn-sm" title="View Details">
|
|
<i class="fa fa-eye"></i>
|
|
</a>
|
|
<button class="btn btn-outline-success btn-sm" onclick="testEndpoint({{ endpoint.id }})" title="Test Endpoint">
|
|
<i class="fa fa-play"></i>
|
|
</button>
|
|
<a href="{% url 'integration:api_endpoint_update' endpoint.pk %}" class="btn btn-outline-warning btn-sm" title="Edit">
|
|
<i class="fa fa-edit"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center text-muted py-4">
|
|
<i class="fa fa-plug fa-2x mb-2"></i>
|
|
<div>No API endpoints found</div>
|
|
<a href="{% url 'integration:api_endpoint_create' %}" class="btn btn-primary mt-2">
|
|
<i class="fa fa-plus me-2"></i>Add First Endpoint
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Bulk Actions -->
|
|
<div id="bulk-actions" class="mt-3" style="display: none;">
|
|
<div class="alert alert-info">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong><span id="selected-count">0</span></strong> endpoint(s) selected
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-sm btn-success me-2" onclick="bulkTest()">
|
|
<i class="fa fa-play me-1"></i>Test Selected
|
|
</button>
|
|
<button class="btn btn-sm btn-warning me-2" onclick="bulkActivate()">
|
|
<i class="fa fa-check me-1"></i>Activate
|
|
</button>
|
|
<button class="btn btn-sm btn-secondary me-2" onclick="bulkDeactivate()">
|
|
<i class="fa fa-pause me-1"></i>Deactivate
|
|
</button>
|
|
<button class="btn btn-sm btn-secondary" onclick="clearSelection()">
|
|
<i class="fa fa-times me-1"></i>Clear Selection
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- Bulk Actions Modal -->
|
|
<div class="modal fade" id="bulk-actions-modal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Bulk Actions</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-success" onclick="testAllEndpoints()">
|
|
<i class="fa fa-play me-2"></i>Test All Endpoints
|
|
</button>
|
|
<button class="btn btn-warning" onclick="activateAllEndpoints()">
|
|
<i class="fa fa-check me-2"></i>Activate All Endpoints
|
|
</button>
|
|
<button class="btn btn-secondary" onclick="deactivateAllEndpoints()">
|
|
<i class="fa fa-pause me-2"></i>Deactivate All Endpoints
|
|
</button>
|
|
<hr>
|
|
<button class="btn btn-info" onclick="exportEndpoints()">
|
|
<i class="fa fa-download me-2"></i>Export Configuration
|
|
</button>
|
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#import-modal">
|
|
<i class="fa fa-upload me-2"></i>Import Configuration
|
|
</button>
|
|
</div>
|
|
</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/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
|
|
<script src="{% static 'assets/plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable
|
|
var table = $('#endpoints-table').DataTable({
|
|
responsive: true,
|
|
pageLength: 25,
|
|
order: [[1, 'asc']], // Sort by name
|
|
columnDefs: [
|
|
{ orderable: false, targets: [0, 7] } // Disable sorting for checkbox and actions columns
|
|
]
|
|
});
|
|
|
|
// Select all checkbox
|
|
$('#select-all').on('change', function() {
|
|
$('.row-checkbox').prop('checked', this.checked);
|
|
updateBulkActions();
|
|
});
|
|
|
|
// Individual row checkboxes
|
|
$(document).on('change', '.row-checkbox', function() {
|
|
updateBulkActions();
|
|
|
|
// Update select all checkbox
|
|
var totalCheckboxes = $('.row-checkbox').length;
|
|
var checkedCheckboxes = $('.row-checkbox:checked').length;
|
|
$('#select-all').prop('checked', totalCheckboxes === checkedCheckboxes);
|
|
});
|
|
|
|
function updateBulkActions() {
|
|
var selectedCount = $('.row-checkbox:checked').length;
|
|
$('#selected-count').text(selectedCount);
|
|
|
|
if (selectedCount > 0) {
|
|
$('#bulk-actions').show();
|
|
} else {
|
|
$('#bulk-actions').hide();
|
|
}
|
|
}
|
|
});
|
|
|
|
function filterByStatus(status) {
|
|
$('.btn-group button').removeClass('active');
|
|
event.target.classList.add('active');
|
|
|
|
if (status === 'all') {
|
|
$('tr[data-status]').show();
|
|
} else {
|
|
$('tr[data-status]').hide();
|
|
$(`tr[data-status="${status}"]`).show();
|
|
}
|
|
}
|
|
|
|
function filterByMethod(method) {
|
|
$('.btn-group button').removeClass('active');
|
|
event.target.classList.add('active');
|
|
|
|
if (method === 'all') {
|
|
$('tr[data-method]').show();
|
|
} else {
|
|
$('tr[data-method]').hide();
|
|
$(`tr[data-method="${method}"]`).show();
|
|
}
|
|
}
|
|
|
|
function filterBySystem(systemId) {
|
|
$('.btn-group button').removeClass('active');
|
|
event.target.classList.add('active');
|
|
|
|
if (systemId === 'all') {
|
|
$('tr[data-system]').show();
|
|
} else {
|
|
$('tr[data-system]').hide();
|
|
$(`tr[data-system="${systemId}"]`).show();
|
|
}
|
|
}
|
|
|
|
function testEndpoint(endpointId) {
|
|
$.ajax({
|
|
url: '{% url "integration:api_endpoint_test" 0 %}'.replace('0', endpointId),
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
beforeSend: function() {
|
|
toastr.info('Testing endpoint...');
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Endpoint test successful');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Endpoint test failed: ' + response.error);
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to test endpoint');
|
|
}
|
|
});
|
|
}
|
|
|
|
function testAllEndpoints() {
|
|
if (confirm('Test all endpoints? This may take some time.')) {
|
|
$.ajax({
|
|
url: '{% url "integration:api_endpoint_test_all" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
beforeSend: function() {
|
|
toastr.info('Testing all endpoints...');
|
|
},
|
|
success: function(response) {
|
|
toastr.success('Endpoint testing completed');
|
|
location.reload();
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to test endpoints');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function bulkTest() {
|
|
var selectedIds = $('.row-checkbox:checked').map(function() {
|
|
return this.value;
|
|
}).get();
|
|
|
|
if (selectedIds.length === 0) {
|
|
toastr.warning('Please select endpoints to test');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: '{% url "integration:api_endpoint_bulk_test" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'endpoint_ids': selectedIds,
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
beforeSend: function() {
|
|
toastr.info('Testing selected endpoints...');
|
|
},
|
|
success: function(response) {
|
|
toastr.success('Bulk testing completed');
|
|
location.reload();
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to test selected endpoints');
|
|
}
|
|
});
|
|
}
|
|
|
|
function bulkActivate() {
|
|
var selectedIds = $('.row-checkbox:checked').map(function() {
|
|
return this.value;
|
|
}).get();
|
|
|
|
if (selectedIds.length === 0) {
|
|
toastr.warning('Please select endpoints to activate');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: '{% url "integration:api_endpoint_bulk_activate" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'endpoint_ids': selectedIds,
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
toastr.success('Selected endpoints activated');
|
|
location.reload();
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to activate selected endpoints');
|
|
}
|
|
});
|
|
}
|
|
|
|
function bulkDeactivate() {
|
|
var selectedIds = $('.row-checkbox:checked').map(function() {
|
|
return this.value;
|
|
}).get();
|
|
|
|
if (selectedIds.length === 0) {
|
|
toastr.warning('Please select endpoints to deactivate');
|
|
return;
|
|
}
|
|
|
|
$.ajax({
|
|
url: '{% url "integration:api_endpoint_bulk_deactivate" %}',
|
|
method: 'POST',
|
|
data: {
|
|
'endpoint_ids': selectedIds,
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
toastr.success('Selected endpoints deactivated');
|
|
location.reload();
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to deactivate selected endpoints');
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearSelection() {
|
|
$('.row-checkbox, #select-all').prop('checked', false);
|
|
$('#bulk-actions').hide();
|
|
}
|
|
|
|
function exportEndpoints() {
|
|
window.open('{% url "integration:api_endpoint_export" %}');
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|