159 lines
6.9 KiB
HTML
159 lines
6.9 KiB
HTML
{% 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 %}
|