@@ -0,0 +1,158 @@
|
||||
{% extends "page.html" %}
|
||||
{% block page_content %}
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Графики и диаграммы</h2></div>
|
||||
<div class="filter-bar" style="flex-wrap:wrap;gap:8px;">
|
||||
<select id="metric-select">
|
||||
<option value="tasks">Задачи по месяцам</option>
|
||||
<option value="incidents">Инциденты по severity</option>
|
||||
<option value="shs">SHS история</option>
|
||||
<option value="kpi">KPI инженеров</option>
|
||||
<option value="mrr">MRR по статусам</option>
|
||||
<option value="objects">Объекты по статусам</option>
|
||||
</select>
|
||||
<select id="chart-type">
|
||||
<option value="bar">Столбчатая</option>
|
||||
<option value="line">Линейная</option>
|
||||
<option value="pie">Круговая</option>
|
||||
<option value="doughnut">Кольцевая</option>
|
||||
</select>
|
||||
<select id="chart-group">
|
||||
<option value="month">По месяцам</option>
|
||||
<option value="week">По неделям</option>
|
||||
<option value="day">По дням</option>
|
||||
</select>
|
||||
<label style="font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:4px;">
|
||||
<input type="text" id="chart-title" placeholder="Название графика" style="width:160px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--bg-card);color:var(--text);">
|
||||
</label>
|
||||
<button class="btn btn-primary btn-sm" onclick="loadChart()">Обновить</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="exportChart()">Экспорт PNG</button>
|
||||
</div>
|
||||
<div class="filter-bar" style="margin-top:8px;flex-wrap:wrap;gap:6px;">
|
||||
<span style="font-size:12px;color:var(--text-muted);">Пользовательские графики:</span>
|
||||
<button class="btn btn-secondary btn-xs" onclick="saveCustomChart()">Сохранить текущий</button>
|
||||
<select id="custom-chart-select" onchange="loadCustomChart(this.value)">
|
||||
<option value="">— Загрузить —</option>
|
||||
</select>
|
||||
<button class="btn btn-danger btn-xs" onclick="deleteCustomChart()">Удалить</button>
|
||||
</div>
|
||||
<div style="position:relative;height:400px;">
|
||||
<canvas id="chart-canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
var chartInstance = null;
|
||||
var currentChartConfig = null;
|
||||
|
||||
function loadChart(){
|
||||
var metric = document.getElementById('metric-select').value;
|
||||
var type = document.getElementById('chart-type').value;
|
||||
var group = document.getElementById('chart-group').value;
|
||||
var canvas = document.getElementById('chart-canvas');
|
||||
var title = document.getElementById('chart-title').value || '';
|
||||
|
||||
fetch('/service/api/charts/data?metric=' + metric + '&group=' + group).then(function(r){ return r.json() }).then(function(data){
|
||||
if(chartInstance) { chartInstance.destroy(); chartInstance = null; }
|
||||
|
||||
if(!data.labels || data.labels.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
currentChartConfig = { metric: metric, type: type, group: group, title: title, labels: data.labels };
|
||||
|
||||
var datasets = data.datasets.map(function(ds){
|
||||
var color = 'hsl(' + (Math.random() * 360) + ', 70%, 60%)';
|
||||
return {
|
||||
label: ds.label,
|
||||
data: ds.data,
|
||||
backgroundColor: type === 'line' ? color : (type === 'bar' ? color : [
|
||||
'rgba(0,173,239,0.7)', 'rgba(34,197,94,0.7)', 'rgba(245,158,11,0.7)',
|
||||
'rgba(239,68,68,0.7)', 'rgba(99,102,241,0.7)', 'rgba(236,72,153,0.7)'
|
||||
]),
|
||||
borderColor: color,
|
||||
borderWidth: 2,
|
||||
tension: 0.3,
|
||||
};
|
||||
});
|
||||
|
||||
chartInstance = new Chart(canvas, {
|
||||
type: type === 'doughnut' ? 'doughnut' : (['pie', 'doughnut'].includes(type) ? type : type),
|
||||
data: { labels: data.labels, datasets: datasets },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { labels: { color: '#8899bb' } },
|
||||
title: title ? { display: true, text: title, color: '#e8edf5', font: { size: 14 } } : undefined
|
||||
},
|
||||
scales: (['pie', 'doughnut'].includes(type) ? undefined : {
|
||||
x: { ticks: { color: '#8899bb' }, grid: { color: 'rgba(255,255,255,0.05)' } },
|
||||
y: { ticks: { color: '#8899bb' }, grid: { color: 'rgba(255,255,255,0.05)' } }
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function exportChart(){
|
||||
var canvas = document.getElementById('chart-canvas');
|
||||
if(!canvas) return;
|
||||
var link = document.createElement('a');
|
||||
link.download = 'chart_' + new Date().toISOString().slice(0,10) + '.png';
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
}
|
||||
|
||||
function saveCustomChart(){
|
||||
if(!currentChartConfig) return;
|
||||
var name = prompt('Название графика:');
|
||||
if(!name) return;
|
||||
var saved = JSON.parse(localStorage.getItem('custom_charts') || '[]');
|
||||
saved.push({ name: name, config: currentChartConfig });
|
||||
localStorage.setItem('custom_charts', JSON.stringify(saved));
|
||||
updateCustomChartList();
|
||||
}
|
||||
|
||||
function loadCustomChart(name){
|
||||
if(!name) return;
|
||||
var saved = JSON.parse(localStorage.getItem('custom_charts') || '[]');
|
||||
var found = saved.find(function(c){ return c.name === name; });
|
||||
if(!found) return;
|
||||
document.getElementById('metric-select').value = found.config.metric;
|
||||
document.getElementById('chart-type').value = found.config.type;
|
||||
document.getElementById('chart-group').value = found.config.group;
|
||||
document.getElementById('chart-title').value = found.config.title || '';
|
||||
loadChart();
|
||||
}
|
||||
|
||||
function deleteCustomChart(){
|
||||
var sel = document.getElementById('custom-chart-select');
|
||||
var name = sel.value;
|
||||
if(!name) return;
|
||||
if(!confirm('Удалить "' + name + '"?')) return;
|
||||
var saved = JSON.parse(localStorage.getItem('custom_charts') || '[]');
|
||||
saved = saved.filter(function(c){ return c.name !== name; });
|
||||
localStorage.setItem('custom_charts', JSON.stringify(saved));
|
||||
updateCustomChartList();
|
||||
sel.value = '';
|
||||
}
|
||||
|
||||
function updateCustomChartList(){
|
||||
var sel = document.getElementById('custom-chart-select');
|
||||
var saved = JSON.parse(localStorage.getItem('custom_charts') || '[]');
|
||||
sel.innerHTML = '<option value="">— Загрузить —</option>';
|
||||
saved.forEach(function(c){
|
||||
var opt = document.createElement('option');
|
||||
opt.value = c.name; opt.textContent = c.name;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
loadChart();
|
||||
updateCustomChartList();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user