499 lines
23 KiB
HTML
499 lines
23 KiB
HTML
{% extends "base.html" %}
|
|
{% load static custom_filters%}
|
|
|
|
{% block title %}Integration Dashboard - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="fas fa-network-wired me-2"></i>Integration Dashboard
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="">Dashboard</a></li>
|
|
<li class="breadcrumb-item active">Integration</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshDashboard()">
|
|
<i class="fas fa-sync-alt me-2"></i>Refresh
|
|
</button>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-plus me-2"></i>Quick Add
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'integration:external_system_create' %}">
|
|
<i class="fas fa-server me-2"></i>External System
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'integration:integration_endpoint_create' %}">
|
|
<i class="fas fa-link me-2"></i>Integration Endpoint
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'integration:webhook_endpoint_create' %}">
|
|
<i class="fas fa-webhook me-2"></i>Webhook Endpoint
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4" hx-get="{% url 'integration:integration_stats' %}" hx-trigger="load, every 30s">
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="card-title text-muted mb-1">Total Systems</h6>
|
|
<h3 class="mb-0 text-primary">{{ total_systems }}</h3>
|
|
<small class="text-muted">External systems</small>
|
|
</div>
|
|
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
|
<i class="fas fa-server fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="card-title text-muted mb-1">Active Endpoints</h6>
|
|
<h3 class="mb-0 text-success">{{ total_endpoints }}</h3>
|
|
<small class="text-muted">Integration points</small>
|
|
</div>
|
|
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
|
<i class="fas fa-link fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="card-title text-muted mb-1">Executions Today</h6>
|
|
<h3 class="mb-0 text-info">{{ executions_today }}</h3>
|
|
<small class="text-muted">{{ successful_executions }} successful</small>
|
|
</div>
|
|
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
|
<i class="fas fa-play fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-3 col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="card-title text-muted mb-1">Failed Today</h6>
|
|
<h3 class="mb-0 text-danger">{{ failed_executions }}</h3>
|
|
<small class="text-muted">Need attention</small>
|
|
</div>
|
|
<div class="bg-danger bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
|
|
<i class="fas fa-exclamation-triangle fa-lg text-white"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- System Health -->
|
|
<div class="col-lg-8">
|
|
<div class="card mb-4" hx-get="{% url 'integration:system_health' %}" hx-trigger="load, every 60s">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-heartbeat me-2"></i>System Health Status
|
|
</h5>
|
|
<a href="{% url 'integration:external_system_list' %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-list me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-4 text-center mb-3">
|
|
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-check-circle fa-lg text-white"></i>
|
|
</div>
|
|
<h4 class="text-success mb-0">{{ healthy_systems }}</h4>
|
|
<small class="text-muted">Healthy Systems</small>
|
|
</div>
|
|
<div class="col-md-4 text-center mb-3">
|
|
<div class="bg-danger bg-gradient rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-times-circle fa-lg text-white"></i>
|
|
</div>
|
|
<h4 class="text-danger mb-0">{{ unhealthy_systems }}</h4>
|
|
<small class="text-muted">Unhealthy Systems</small>
|
|
</div>
|
|
<div class="col-md-4 text-center mb-3">
|
|
<div class="bg-secondary bg-gradient rounded-circle d-flex align-items-center justify-content-center mx-auto mb-2" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-question-circle fa-lg text-white"></i>
|
|
</div>
|
|
<h4 class="text-secondary mb-0">{{ unknown_systems }}</h4>
|
|
<small class="text-muted">Unknown Status</small>
|
|
</div>
|
|
</div>
|
|
|
|
{% if unhealthy_systems > 0 %}
|
|
<div class="alert alert-warning mt-3" role="alert">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Attention Required:</strong> {{ unhealthy_systems }} system{{ unhealthy_systems|pluralize }}
|
|
{{ unhealthy_systems|pluralize:"is,are" }} currently unhealthy and may need maintenance.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Executions -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-play me-2"></i>Recent Executions
|
|
</h5>
|
|
<a href="{% url 'integration:integration_execution_list' %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-list me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_executions %}
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Endpoint</th>
|
|
<th>System</th>
|
|
<th>Status</th>
|
|
<th>Duration</th>
|
|
<th>Time</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for execution in recent_executions %}
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-link text-white small"></i>
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold small">{{ execution.endpoint.name }}</div>
|
|
<div class="text-muted small">{{ execution.endpoint.get_method_display }} {{ execution.endpoint.endpoint_path|truncatechars:30 }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">{{ execution.endpoint.system.name }}</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-{% if execution.status == 'SUCCESS' %}success{% elif execution.status == 'FAILED' %}danger{% elif execution.status == 'RUNNING' %}warning{% else %}secondary{% endif %}">
|
|
{% if execution.status == 'SUCCESS' %}
|
|
<i class="fas fa-check me-1"></i>Success
|
|
{% elif execution.status == 'FAILED' %}
|
|
<i class="fas fa-times me-1"></i>Failed
|
|
{% elif execution.status == 'RUNNING' %}
|
|
<i class="fas fa-spinner fa-spin me-1"></i>Running
|
|
{% else %}
|
|
<i class="fas fa-clock me-1"></i>{{ execution.status }}
|
|
{% endif %}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if execution.duration_seconds %}
|
|
{{ execution.duration_seconds|floatformat:2 }}s
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div>{{ execution.execution_time|date:"H:i" }}</div>
|
|
<small class="text-muted">{{ execution.execution_time|timesince }} ago</small>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-play fa-3x text-muted mb-3"></i>
|
|
<h6 class="text-muted">No recent executions</h6>
|
|
<p class="text-muted">Integration executions will appear here once they start running</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bolt me-2"></i>Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'integration:external_system_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-server me-2"></i>Add External System
|
|
</a>
|
|
<a href="{% url 'integration:integration_endpoint_create' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-link me-2"></i>Create Endpoint
|
|
</a>
|
|
<a href="{% url 'integration:webhook_endpoint_create' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-webhook me-2"></i>Setup Webhook
|
|
</a>
|
|
<button type="button" class="btn btn-outline-info" onclick="testAllConnections()">
|
|
<i class="fas fa-plug me-2"></i>Test All Connections
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Webhook Activity -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-webhook me-2"></i>Webhook Activity
|
|
</h5>
|
|
<a href="{% url 'integration:webhook_execution_list' %}" class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-list me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_webhook_executions %}
|
|
{% for webhook in recent_webhook_executions %}
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="bg-{% if webhook.status == 'SUCCESS' %}success{% elif webhook.status == 'FAILED' %}danger{% else %}warning{% endif %} bg-gradient rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 32px; height: 32px;">
|
|
<i class="fas fa-webhook text-white small"></i>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<div class="fw-semibold small">{{ webhook.webhook.name }}</div>
|
|
<div class="text-muted small">{{ webhook.execution_time|timesince }} ago</div>
|
|
</div>
|
|
<span class="badge bg-{% if webhook.status == 'SUCCESS' %}success{% elif webhook.status == 'FAILED' %}danger{% else %}warning{% endif %} small">
|
|
{{ webhook.status }}
|
|
</span>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-webhook fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted small mb-0">No recent webhook activity</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Overview -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-chart-pie me-2"></i>System Overview
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-6 mb-3">
|
|
<div class="border-end">
|
|
<h4 class="text-primary mb-0">{{ total_mappings }}</h4>
|
|
<small class="text-muted">Data Mappings</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-6 mb-3">
|
|
<h4 class="text-success mb-0">{{ total_webhooks }}</h4>
|
|
<small class="text-muted">Webhooks</small>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="border-end">
|
|
<h4 class="text-info mb-0">{{ active_integrations }}</h4>
|
|
<small class="text-muted">Active</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<h4 class="text-warning mb-0">{{ pending_integrations }}</h4>
|
|
<small class="text-muted">Pending</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<small class="text-muted">System Health</small>
|
|
<small class="text-muted">{{ healthy_systems }}/{{ total_systems }}</small>
|
|
</div>
|
|
<div class="progress" style="height: 6px;">
|
|
<div class="progress-bar bg-success" role="progressbar"
|
|
style="width: {% if total_systems > 0 %}{{ healthy_systems|mul:100|div:total_systems }}{% else %}0{% endif %}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Integration dashboard functionality
|
|
function refreshDashboard() {
|
|
location.reload();
|
|
}
|
|
|
|
function testAllConnections() {
|
|
if (confirm('This will test connections to all active external systems. This may take a few minutes. Continue?')) {
|
|
const btn = event.target;
|
|
const originalText = btn.innerHTML;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Testing...';
|
|
btn.disabled = true;
|
|
|
|
fetch('/integration/systems/test-all/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
alert(`Connection tests completed!\n\nSuccessful: ${data.successful}\nFailed: ${data.failed}\nTotal: ${data.total}`);
|
|
location.reload();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error testing connections:', error);
|
|
alert('Error testing connections: ' + error.message);
|
|
})
|
|
.finally(() => {
|
|
btn.innerHTML = originalText;
|
|
btn.disabled = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Auto-refresh dashboard every 5 minutes
|
|
setInterval(function() {
|
|
// Refresh HTMX elements
|
|
htmx.trigger(document.querySelector('[hx-get*="integration_stats"]'), 'refresh');
|
|
htmx.trigger(document.querySelector('[hx-get*="system_health"]'), 'refresh');
|
|
}, 300000);
|
|
|
|
// Real-time notifications for failed executions
|
|
function checkForFailures() {
|
|
fetch('/integration/api/recent-failures/', {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.new_failures > 0) {
|
|
// Show notification
|
|
const notification = document.createElement('div');
|
|
notification.className = 'alert alert-danger alert-dismissible fade show position-fixed';
|
|
notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
|
notification.innerHTML = `
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Integration Alert:</strong> ${data.new_failures} new execution failure${data.new_failures > 1 ? 's' : ''} detected.
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
document.body.appendChild(notification);
|
|
|
|
// Auto-remove after 10 seconds
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.remove();
|
|
}
|
|
}, 10000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error checking for failures:', error);
|
|
});
|
|
}
|
|
|
|
// Check for failures every 2 minutes
|
|
setInterval(checkForFailures, 120000);
|
|
</script>
|
|
|
|
<style>
|
|
.bg-gradient {
|
|
background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
|
}
|
|
|
|
.table th {
|
|
border-top: none;
|
|
font-weight: 600;
|
|
color: #6c757d;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.table td {
|
|
vertical-align: middle;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.badge {
|
|
font-size: 0.75em;
|
|
}
|
|
|
|
.border-end {
|
|
border-right: 1px solid #dee2e6 !important;
|
|
}
|
|
|
|
.progress {
|
|
background-color: #e9ecef;
|
|
}
|
|
|
|
.alert.position-fixed {
|
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.col-md-4 {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.alert.position-fixed {
|
|
position: relative !important;
|
|
top: auto !important;
|
|
right: auto !important;
|
|
margin: 1rem;
|
|
min-width: auto;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|