513 lines
21 KiB
HTML
513 lines
21 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{{ object.name }} - Analytics Metrics{% 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 'analytics:dashboard' %}">Analytics</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'analytics:metric_list' %}">Metrics</a></li>
|
|
<li class="breadcrumb-item active">{{ object.name }}</li>
|
|
</ol>
|
|
<!-- END breadcrumb -->
|
|
|
|
<!-- BEGIN page-header -->
|
|
<h1 class="page-header">
|
|
{{ object.name }}
|
|
<small>{{ object.get_category_display }} Metric</small>
|
|
</h1>
|
|
<!-- END page-header -->
|
|
|
|
<div class="row">
|
|
<div class="col-xl-8">
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Metric Information</h4>
|
|
<div class="panel-heading-btn">
|
|
<button class="btn btn-xs btn-success me-2" onclick="refreshMetric()">
|
|
<i class="fa fa-sync"></i> Refresh
|
|
</button>
|
|
<a href="{% url 'analytics:metric_update' object.pk %}" class="btn btn-xs btn-primary me-2">
|
|
<i class="fa fa-edit"></i> Edit
|
|
</a>
|
|
<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">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold" width="150">Name:</td>
|
|
<td>{{ object.name }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Description:</td>
|
|
<td>{{ object.description|default:"No description" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Category:</td>
|
|
<td>
|
|
<span class="badge bg-primary">{{ object.get_category_display }}</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Current Value:</td>
|
|
<td>
|
|
{% if object.current_value is not None %}
|
|
<span class="fs-18px fw-bold text-primary">
|
|
{{ object.current_value }}{% if object.unit %} {{ object.unit }}{% endif %}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">No data available</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Target:</td>
|
|
<td>
|
|
{% if object.target_value is not None %}
|
|
<span class="text-info">
|
|
{% if object.target_direction == 'HIGHER' %}↑{% else %}↓{% endif %}
|
|
{{ object.target_value }}{% if object.unit %} {{ object.unit }}{% endif %}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">No target set</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tr>
|
|
<td class="fw-bold" width="150">Status:</td>
|
|
<td>
|
|
<span class="badge bg-{% if object.status == 'ACTIVE' %}success{% elif object.status == 'DRAFT' %}warning{% else %}secondary{% endif %}">
|
|
{{ object.get_status_display }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Frequency:</td>
|
|
<td>
|
|
<i class="fa fa-{% if object.update_frequency == 'REAL_TIME' %}bolt{% else %}clock{% endif %} me-2"></i>
|
|
{{ object.get_update_frequency_display }}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Last Updated:</td>
|
|
<td>
|
|
{% if object.last_updated %}
|
|
{{ object.last_updated|date:"M d, Y H:i" }}
|
|
<br><small class="text-muted">{{ object.last_updated|timesince }} ago</small>
|
|
{% else %}
|
|
<span class="text-muted">Never updated</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Created:</td>
|
|
<td>{{ object.created_at|date:"M d, Y" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="fw-bold">Created By:</td>
|
|
<td>{{ object.created_by.get_full_name|default:"System" }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Target Performance -->
|
|
{% if object.target_value is not None and object.current_value is not None %}
|
|
<div class="mt-4">
|
|
<h6>Target Performance</h6>
|
|
<div class="progress mb-2" style="height: 25px;">
|
|
{% with performance=object.target_performance %}
|
|
<div class="progress-bar bg-{% if performance >= 100 %}success{% elif performance >= 75 %}warning{% else %}danger{% endif %}"
|
|
role="progressbar" style="width: {{ performance|floatformat:1 }}%">
|
|
{{ performance|floatformat:1 }}%
|
|
</div>
|
|
{% endwith %}
|
|
</div>
|
|
<div class="d-flex justify-content-between text-muted small">
|
|
<span>Current: {{ object.current_value }}{% if object.unit %} {{ object.unit }}{% endif %}</span>
|
|
<span>Target: {{ object.target_value }}{% if object.unit %} {{ object.unit }}{% endif %}</span>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- SQL Query -->
|
|
{% if object.sql_query %}
|
|
<div class="mt-4">
|
|
<h6>Calculation Query</h6>
|
|
<div class="bg-dark p-3 rounded">
|
|
<pre class="mb-0 text-light"><code>{{ object.sql_query }}</code></pre>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Historical Data</h4>
|
|
<div class="panel-heading-btn">
|
|
<div class="btn-group btn-group-xs">
|
|
<button class="btn btn-default" onclick="loadChart('7d')">7D</button>
|
|
<button class="btn btn-default" onclick="loadChart('30d')">30D</button>
|
|
<button class="btn btn-default active" onclick="loadChart('90d')">90D</button>
|
|
<button class="btn btn-default" onclick="loadChart('1y')">1Y</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div id="metric-chart" style="height: 300px;"></div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Recent Updates</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% if recent_updates %}
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Value</th>
|
|
<th>Change</th>
|
|
<th>Updated By</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for update in recent_updates %}
|
|
<tr>
|
|
<td>{{ update.created_at|date:"M d, Y H:i:s" }}</td>
|
|
<td>
|
|
<span class="fw-bold">
|
|
{{ update.value }}{% if object.unit %} {{ object.unit }}{% endif %}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if update.change %}
|
|
<span class="badge bg-{% if update.change > 0 %}success{% elif update.change < 0 %}danger{% else %}secondary{% endif %}">
|
|
{% if update.change > 0 %}+{% endif %}{{ update.change|floatformat:2 }}
|
|
</span>
|
|
{% else %}
|
|
<span class="text-muted">-</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ update.updated_by.get_full_name|default:"System" }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-4 text-muted">
|
|
<i class="fa fa-history fa-3x mb-3"></i>
|
|
<p>No update history found for this metric.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
</div>
|
|
|
|
<div class="col-xl-4">
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Quick Actions</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-success" onclick="refreshMetric()">
|
|
<i class="fa fa-sync me-2"></i>Refresh Metric
|
|
</button>
|
|
|
|
<a href="{% url 'analytics:metric_update' object.pk %}" class="btn btn-primary">
|
|
<i class="fa fa-edit me-2"></i>Edit Metric
|
|
</a>
|
|
|
|
<button class="btn btn-info" onclick="exportData()">
|
|
<i class="fa fa-download me-2"></i>Export Data
|
|
</button>
|
|
|
|
<button class="btn btn-warning" onclick="setAlert()">
|
|
<i class="fa fa-bell me-2"></i>Set Alert
|
|
</button>
|
|
|
|
<hr>
|
|
|
|
{% if object.status == 'ACTIVE' %}
|
|
<button class="btn btn-outline-secondary" onclick="deactivateMetric()">
|
|
<i class="fa fa-pause me-2"></i>Deactivate
|
|
</button>
|
|
{% else %}
|
|
<button class="btn btn-outline-success" onclick="activateMetric()">
|
|
<i class="fa fa-play me-2"></i>Activate
|
|
</button>
|
|
{% endif %}
|
|
|
|
<a href="{% url 'analytics:metric_delete' object.pk %}" class="btn btn-danger">
|
|
<i class="fa fa-trash me-2"></i>Delete Metric
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Metric Statistics</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="row text-center">
|
|
<div class="col-6 mb-3">
|
|
<div class="fs-24px fw-bold text-primary">{{ object.update_count }}</div>
|
|
<div class="small text-muted">Total Updates</div>
|
|
</div>
|
|
<div class="col-6 mb-3">
|
|
<div class="fs-24px fw-bold text-success">{{ object.avg_value|floatformat:2 }}</div>
|
|
<div class="small text-muted">Average Value</div>
|
|
</div>
|
|
<div class="col-6 mb-3">
|
|
<div class="fs-24px fw-bold text-info">{{ object.min_value|floatformat:2 }}</div>
|
|
<div class="small text-muted">Minimum</div>
|
|
</div>
|
|
<div class="col-6 mb-3">
|
|
<div class="fs-24px fw-bold text-warning">{{ object.max_value|floatformat:2 }}</div>
|
|
<div class="small text-muted">Maximum</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
|
|
<!-- BEGIN panel -->
|
|
<div class="panel panel-inverse">
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Alert Configuration</h4>
|
|
</div>
|
|
<div class="panel-body">
|
|
{% if object.alerts.exists %}
|
|
<div class="list-group list-group-flush">
|
|
{% for alert in object.alerts.all %}
|
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<div class="fw-bold">{{ alert.name }}</div>
|
|
<div class="small text-muted">
|
|
{{ alert.get_condition_display }}: {{ alert.threshold_value }}
|
|
</div>
|
|
</div>
|
|
<span class="badge bg-{{ alert.is_active|yesno:'success,secondary' }}">
|
|
{{ alert.is_active|yesno:'Active,Inactive' }}
|
|
</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-3 text-muted">
|
|
<i class="fa fa-bell fa-2x mb-2"></i>
|
|
<p class="mb-0">No alerts configured.</p>
|
|
<button class="btn btn-sm btn-outline-primary mt-2" onclick="setAlert()">
|
|
Add Alert
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<!-- END panel -->
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<script src="{% static 'assets/plugins/apexcharts/dist/apexcharts.min.js' %}"></script>
|
|
<script>
|
|
var chart;
|
|
|
|
$(document).ready(function() {
|
|
// Initialize chart
|
|
loadChart('90d');
|
|
});
|
|
|
|
function loadChart(period) {
|
|
$.ajax({
|
|
url: '{% url "analytics:metric_chart_data" object.pk %}',
|
|
data: { period: period },
|
|
success: function(response) {
|
|
if (chart) {
|
|
chart.destroy();
|
|
}
|
|
|
|
var options = {
|
|
series: [{
|
|
name: '{{ object.name }}',
|
|
data: response.data
|
|
}],
|
|
chart: {
|
|
type: 'line',
|
|
height: 300,
|
|
toolbar: {
|
|
show: true
|
|
}
|
|
},
|
|
dataLabels: {
|
|
enabled: false
|
|
},
|
|
stroke: {
|
|
curve: 'smooth',
|
|
width: 3
|
|
},
|
|
xaxis: {
|
|
type: 'datetime',
|
|
categories: response.labels
|
|
},
|
|
yaxis: {
|
|
title: {
|
|
text: '{{ object.unit|default:"Value" }}'
|
|
}
|
|
},
|
|
colors: ['#348fe2'],
|
|
grid: {
|
|
borderColor: '#e7e7e7',
|
|
row: {
|
|
colors: ['#f3f3f3', 'transparent'],
|
|
opacity: 0.5
|
|
}
|
|
},
|
|
markers: {
|
|
size: 4
|
|
},
|
|
tooltip: {
|
|
x: {
|
|
format: 'dd/MM/yy HH:mm'
|
|
}
|
|
}
|
|
};
|
|
|
|
{% if object.target_value %}
|
|
options.annotations = {
|
|
yaxis: [{
|
|
y: {{ object.target_value }},
|
|
borderColor: '#00E396',
|
|
label: {
|
|
borderColor: '#00E396',
|
|
style: {
|
|
color: '#fff',
|
|
background: '#00E396'
|
|
},
|
|
text: 'Target: {{ object.target_value }}'
|
|
}
|
|
}]
|
|
};
|
|
{% endif %}
|
|
|
|
chart = new ApexCharts(document.querySelector("#metric-chart"), options);
|
|
chart.render();
|
|
},
|
|
error: function() {
|
|
$('#metric-chart').html('<div class="text-center py-5 text-muted">Failed to load chart data</div>');
|
|
}
|
|
});
|
|
|
|
// Update active button
|
|
$('.btn-group-xs .btn').removeClass('active');
|
|
$('.btn-group-xs .btn').filter(function() {
|
|
return $(this).text() === period.toUpperCase();
|
|
}).addClass('active');
|
|
}
|
|
|
|
function refreshMetric() {
|
|
$.ajax({
|
|
url: '{% url "analytics:metric_refresh" object.pk %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
beforeSend: function() {
|
|
toastr.info('Refreshing metric...');
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Metric refreshed successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Failed to refresh metric: ' + (response.error || 'Unknown error'));
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('An error occurred while refreshing the metric');
|
|
}
|
|
});
|
|
}
|
|
|
|
function activateMetric() {
|
|
$.ajax({
|
|
url: '{% url "analytics:metric_activate" object.pk %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Metric activated successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Failed to activate metric');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('An error occurred while activating the metric');
|
|
}
|
|
});
|
|
}
|
|
|
|
function deactivateMetric() {
|
|
if (confirm('Deactivate this metric? It will stop updating automatically.')) {
|
|
$.ajax({
|
|
url: '{% url "analytics:metric_deactivate" object.pk %}',
|
|
method: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': '{{ csrf_token }}'
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
toastr.success('Metric deactivated successfully');
|
|
location.reload();
|
|
} else {
|
|
toastr.error('Failed to deactivate metric');
|
|
}
|
|
},
|
|
error: function() {
|
|
toastr.error('An error occurred while deactivating the metric');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function exportData() {
|
|
window.location.href = '{% url "analytics:metric_export_data" object.pk %}';
|
|
}
|
|
|
|
function setAlert() {
|
|
// Implementation for setting alerts
|
|
toastr.info('Alert configuration functionality will be implemented');
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|